4 * Copyright 2004 Thomas Weidenmueller <w3seek@reactos.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Apr. 4, 2005, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
30 * - Fix SHIFT+TAB and TAB issue (wrong link is selected when control gets the focus)
31 * - Better string parsing
32 * - Improve word wrapping
45 #include "wine/unicode.h"
46 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(progress);
50 INT WINAPI StrCmpNIW(LPCWSTR,LPCWSTR,INT);
56 } DOC_TEXTBLOCK, *PDOC_TEXTBLOCK;
58 #define LIF_FLAGSMASK (LIF_STATE | LIF_ITEMID | LIF_URL)
59 #define LIS_MASK (LIS_FOCUSED | LIS_ENABLED | LIS_VISITED)
67 typedef struct _DOC_ITEM
69 struct _DOC_ITEM *Next; /* Address to the next item */
70 LPWSTR Text; /* Text of the document item */
71 UINT nText; /* Number of characters of the text */
72 SL_ITEM_TYPE Type; /* type of the item */
73 PDOC_TEXTBLOCK Blocks; /* Array of text blocks */
78 UINT state; /* Link state */
79 WCHAR *szID; /* Link ID string */
80 WCHAR *szUrl; /* Link URL string */
81 HRGN hRgn; /* Region of the link */
88 } DOC_ITEM, *PDOC_ITEM;
92 HWND Self; /* The window handle for this control */
93 HWND Notify; /* The parent handle to receive notifications */
94 DWORD Style; /* Styles for this control */
95 PDOC_ITEM Items; /* Address to the first document item */
96 BOOL HasFocus; /* Whether the control has the input focus */
97 int MouseDownID; /* ID of the link that the mouse button first selected */
98 HFONT Font; /* Handle to the font for text */
99 HFONT LinkFont; /* Handle to the font for links */
100 COLORREF TextColor; /* Color of the text */
101 COLORREF LinkColor; /* Color of links */
102 COLORREF VisitedColor; /* Color of visited links */
105 static const WCHAR SL_LINKOPEN[] = { '<','a', 0 };
106 static const WCHAR SL_HREF[] = { 'h','r','e','f','=','\"',0 };
107 static const WCHAR SL_ID[] = { 'i','d','=','\"',0 };
108 static const WCHAR SL_LINKCLOSE[] = { '<','/','a','>',0 };
110 /* Control configuration constants */
112 #define SL_LEFTMARGIN (0)
113 #define SL_TOPMARGIN (0)
114 #define SL_RIGHTMARGIN (0)
115 #define SL_BOTTOMMARGIN (0)
117 /***********************************************************************
118 * SYSLINK_FreeDocItem
119 * Frees all data and gdi objects associated with a document item
121 static VOID SYSLINK_FreeDocItem (PDOC_ITEM DocItem)
123 if(DocItem->Type == slLink)
125 Free(DocItem->u.Link.szID);
126 Free(DocItem->u.Link.szUrl);
129 if(DocItem->Type == slLink && DocItem->u.Link.hRgn != NULL)
131 DeleteObject(DocItem->u.Link.hRgn);
134 /* we don't free Text because it's just a pointer to a character in the
135 entire window text string */
140 /***********************************************************************
141 * SYSLINK_AppendDocItem
142 * Create and append a new document item.
144 static PDOC_ITEM SYSLINK_AppendDocItem (SYSLINK_INFO *infoPtr, LPCWSTR Text, UINT textlen,
145 SL_ITEM_TYPE type, PDOC_ITEM LastItem)
148 Item = Alloc(sizeof(DOC_ITEM) + ((textlen + 1) * sizeof(WCHAR)));
151 ERR("Failed to alloc DOC_ITEM structure!\n");
154 textlen = min(textlen, lstrlenW(Text));
157 Item->Text = (LPWSTR)(Item + 1);
158 Item->nText = textlen;
164 LastItem->Next = Item;
168 infoPtr->Items = Item;
171 lstrcpynW(Item->Text, Text, textlen + 1);
172 Item->Text[textlen] = 0;
177 /***********************************************************************
179 * Clears the document tree
181 static VOID SYSLINK_ClearDoc (SYSLINK_INFO *infoPtr)
183 PDOC_ITEM Item, Next;
185 Item = infoPtr->Items;
189 SYSLINK_FreeDocItem(Item);
193 infoPtr->Items = NULL;
196 /***********************************************************************
198 * Parses the window text string and creates a document. Returns the
199 * number of document items created.
201 static UINT SYSLINK_ParseText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
203 LPCWSTR current, textstart = NULL, linktext = NULL, firsttag = NULL;
204 int taglen = 0, textlen = 0, linklen = 0, docitems = 0;
205 PDOC_ITEM Last = NULL;
206 SL_ITEM_TYPE CurrentType = slText;
210 for(current = Text; *current != 0;)
214 if(!StrCmpNIW(current, SL_LINKOPEN, 2) && (CurrentType == slText))
216 BOOL ValidParam = FALSE, ValidLink = FALSE;
218 switch (*(current + 2))
221 /* we just have to deal with a <a> tag */
232 /* we expect parameters, parse them */
233 LPCWSTR *CurrentParameter = NULL, tmp;
234 UINT *CurrentParameterLen = NULL;
237 tmp = current + taglen;
242 /* compare the current position with all known parameters */
243 if(!StrCmpNIW(tmp, SL_HREF, 6))
247 CurrentParameter = &lpUrl;
248 CurrentParameterLen = &lenUrl;
250 else if(!StrCmpNIW(tmp, SL_ID, 4))
254 CurrentParameter = &lpID;
255 CurrentParameterLen = &lenId;
264 /* we got a known parameter, now search until the next " character.
265 If we can't find a " character, there's a syntax error and we just assume it's text */
267 *CurrentParameter = current + taglen;
268 *CurrentParameterLen = 0;
270 for(tmp = *CurrentParameter; *tmp != 0; tmp++)
279 (*CurrentParameterLen)++;
284 /* we're done with this parameter, now there are only 2 possibilities:
285 * 1. another parameter is coming, so expect a ' ' (space) character
286 * 2. the tag is being closed, so expect a '<' character
291 /* we expect another parameter, do the whole thing again */
297 /* the tag is being closed, we're done */
311 if(ValidLink && ValidParam)
313 /* the <a ...> tag appears to be valid. save all information
314 so we can add the link if we find a valid </a> tag later */
315 CurrentType = slLink;
316 linktext = current + taglen;
325 if(textstart == NULL)
331 else if(!StrCmpNIW(current, SL_LINKCLOSE, 4) && (CurrentType == slLink) && firsttag)
333 /* there's a <a...> tag opened, first add the previous text, if present */
334 if(textstart != NULL && textlen > 0 && firsttag > textstart)
336 Last = SYSLINK_AppendDocItem(infoPtr, textstart, firsttag - textstart, slText, Last);
339 ERR("Unable to create new document item!\n");
347 /* now it's time to add the link to the document */
349 if(linktext != NULL && linklen > 0)
351 Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slLink, Last);
354 ERR("Unable to create new document item!\n");
358 if(CurrentType == slLink)
362 if(!(infoPtr->Style & WS_DISABLED))
364 Last->u.Link.state |= LIS_ENABLED;
366 /* Copy the tag parameters */
369 nc = min(lenId, strlenW(lpID));
370 nc = min(nc, MAX_LINKID_TEXT);
371 Last->u.Link.szID = Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
372 if(Last->u.Link.szID != NULL)
374 lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
375 Last->u.Link.szID[nc] = 0;
379 Last->u.Link.szID = NULL;
382 nc = min(lenUrl, strlenW(lpUrl));
383 nc = min(nc, L_MAX_URL_LENGTH);
384 Last->u.Link.szUrl = Alloc((L_MAX_URL_LENGTH + 1) * sizeof(WCHAR));
385 if(Last->u.Link.szUrl != NULL)
387 lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
388 Last->u.Link.szUrl[nc] = 0;
392 Last->u.Link.szUrl = NULL;
396 CurrentType = slText;
403 /* we don't know what tag it is, so just continue */
406 if(CurrentType == slText && textstart == NULL)
420 /* save the pointer of the current text item if we couldn't find a tag */
421 if(textstart == NULL && CurrentType == slText)
430 if(textstart != NULL && textlen > 0)
432 Last = SYSLINK_AppendDocItem(infoPtr, textstart, textlen, CurrentType, Last);
435 ERR("Unable to create new document item!\n");
438 if(CurrentType == slLink)
442 if(!(infoPtr->Style & WS_DISABLED))
444 Last->u.Link.state |= LIS_ENABLED;
446 /* Copy the tag parameters */
449 nc = min(lenId, strlenW(lpID));
450 nc = min(nc, MAX_LINKID_TEXT);
451 Last->u.Link.szID = Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
452 if(Last->u.Link.szID != NULL)
454 lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
455 Last->u.Link.szID[nc] = 0;
459 Last->u.Link.szID = NULL;
462 nc = min(lenUrl, strlenW(lpUrl));
463 nc = min(nc, L_MAX_URL_LENGTH);
464 Last->u.Link.szUrl = Alloc((L_MAX_URL_LENGTH + 1) * sizeof(WCHAR));
465 if(Last->u.Link.szUrl != NULL)
467 lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
468 Last->u.Link.szUrl[nc] = 0;
472 Last->u.Link.szUrl = NULL;
477 if(linktext != NULL && linklen > 0)
479 /* we got an unclosed link, just display the text */
480 Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slText, Last);
483 ERR("Unable to create new document item!\n");
492 /***********************************************************************
493 * SYSLINK_RepaintLink
496 static VOID SYSLINK_RepaintLink (SYSLINK_INFO *infoPtr, PDOC_ITEM DocItem)
498 if(DocItem->Type != slLink)
500 ERR("DocItem not a link!\n");
504 if(DocItem->u.Link.hRgn != NULL)
506 /* repaint the region */
507 RedrawWindow(infoPtr->Self, NULL, DocItem->u.Link.hRgn, RDW_INVALIDATE | RDW_UPDATENOW);
511 /***********************************************************************
512 * SYSLINK_GetLinkItemByIndex
513 * Retrieves a document link by its index
515 static PDOC_ITEM SYSLINK_GetLinkItemByIndex (SYSLINK_INFO *infoPtr, int iLink)
517 PDOC_ITEM Current = infoPtr->Items;
519 while(Current != NULL)
521 if((Current->Type == slLink) && (iLink-- <= 0))
525 Current = Current->Next;
530 /***********************************************************************
531 * SYSLINK_GetFocusLink
532 * Retrieves the link that has the LIS_FOCUSED bit
534 static PDOC_ITEM SYSLINK_GetFocusLink (SYSLINK_INFO *infoPtr, int *LinkId)
536 PDOC_ITEM Current = infoPtr->Items;
539 while(Current != NULL)
541 if((Current->Type == slLink))
543 if(Current->u.Link.state & LIS_FOCUSED)
551 Current = Current->Next;
556 /***********************************************************************
557 * SYSLINK_GetNextLink
560 static PDOC_ITEM SYSLINK_GetNextLink (SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
562 for(Current = (Current != NULL ? Current->Next : infoPtr->Items);
564 Current = Current->Next)
566 if(Current->Type == slLink)
574 /***********************************************************************
575 * SYSLINK_GetPrevLink
576 * Gets the previous link
578 static PDOC_ITEM SYSLINK_GetPrevLink (SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
582 /* returns the last link */
583 PDOC_ITEM Last = NULL;
585 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
587 if(Current->Type == slLink)
596 /* returns the previous link */
597 PDOC_ITEM Cur, Prev = NULL;
599 for(Cur = infoPtr->Items; Cur != NULL; Cur = Cur->Next)
605 if(Cur->Type == slLink)
614 /***********************************************************************
616 * Tries to wrap a line.
618 static BOOL SYSLINK_WrapLine (HDC hdc, LPWSTR Text, WCHAR BreakChar, int *LineLen, int nFit, LPSIZE Extent, int Width)
629 Current = Text + nFit;
631 /* check if we're in the middle of a word */
632 if((*Current) != BreakChar)
634 /* search for the beginning of the word */
635 while(Current > Text && (*(Current - 1)) != BreakChar)
652 /***********************************************************************
654 * Renders the document in memory
656 static VOID SYSLINK_Render (SYSLINK_INFO *infoPtr, HDC hdc)
661 int x, y, LineHeight;
664 GetClientRect(infoPtr->Self, &rc);
665 rc.right -= SL_RIGHTMARGIN;
666 rc.bottom -= SL_BOTTOMMARGIN;
668 if(rc.right - SL_LEFTMARGIN < 0 || rc.bottom - SL_TOPMARGIN < 0) return;
670 hOldFont = SelectObject(hdc, infoPtr->Font);
671 GetTextMetricsW(hdc, &tm);
677 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
681 PDOC_TEXTBLOCK bl, cbl;
685 if(Current->nText == 0)
687 ERR("DOC_ITEM with no text?!\n");
693 bl = Current->Blocks;
696 if(Current->Type == slText)
698 SelectObject(hdc, infoPtr->Font);
700 else if(Current->Type == slLink)
702 SelectObject(hdc, infoPtr->LinkFont);
707 if(GetTextExtentExPointW(hdc, tx, n, rc.right - x, &nFit, NULL, &szDim))
710 BOOL Wrap = SYSLINK_WrapLine(hdc, tx, tm.tmBreakChar, &LineLen, nFit, &szDim, rc.right - x);
714 if(x > SL_LEFTMARGIN)
716 /* move one line down, the word didn't fit into the line */
724 /* the word starts at the beginning of the line and doesn't
725 fit into the line, so break it at the last character that fits */
726 LineLen = max(nFit, 1);
732 GetTextExtentExPointW(hdc, tx, LineLen, rc.right - x, NULL, NULL, &szDim);
737 bl = ReAlloc(bl, ++nBlocks * sizeof(DOC_TEXTBLOCK));
741 bl = Alloc(++nBlocks * sizeof(DOC_TEXTBLOCK));
746 cbl = bl + nBlocks - 1;
748 cbl->nChars = LineLen;
751 cbl->rc.right = x + szDim.cx;
752 cbl->rc.bottom = y + szDim.cy;
755 LineHeight = max(LineHeight, szDim.cy);
757 /* (re)calculate the link's region */
758 if(Current->Type == slLink)
762 if(Current->u.Link.hRgn != NULL)
764 DeleteObject(Current->u.Link.hRgn);
766 /* initialize the link's hRgn */
767 Current->u.Link.hRgn = CreateRectRgnIndirect(&cbl->rc);
769 else if(Current->u.Link.hRgn != NULL)
772 hrgn = CreateRectRgnIndirect(&cbl->rc);
773 /* add the rectangle */
774 CombineRgn(Current->u.Link.hRgn, Current->u.Link.hRgn, hrgn, RGN_OR);
788 ERR("Failed to alloc DOC_TEXTBLOCK structure!\n");
796 ERR("GetTextExtentExPoint() failed?!\n");
800 Current->Blocks = bl;
803 SelectObject(hdc, hOldFont);
806 /***********************************************************************
808 * Draws the SysLink control.
810 static LRESULT SYSLINK_Draw (SYSLINK_INFO *infoPtr, HDC hdc)
815 COLORREF OldTextColor, OldBkColor;
817 hOldFont = SelectObject(hdc, infoPtr->Font);
818 OldTextColor = SetTextColor(hdc, infoPtr->TextColor);
819 OldBkColor = SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
821 GetClientRect(infoPtr->Self, &rc);
822 rc.right -= SL_RIGHTMARGIN + SL_LEFTMARGIN;
823 rc.bottom -= SL_BOTTOMMARGIN + SL_TOPMARGIN;
825 if(rc.right < 0 || rc.bottom < 0) return 0;
827 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
833 bl = Current->Blocks;
839 if(Current->Type == slText)
841 SelectObject(hdc, infoPtr->Font);
842 SetTextColor(hdc, infoPtr->TextColor);
846 SelectObject(hdc, infoPtr->LinkFont);
847 SetTextColor(hdc, (!(Current->u.Link.state & LIS_VISITED) ? infoPtr->LinkColor : infoPtr->VisitedColor));
852 ExtTextOutW(hdc, bl->rc.left, bl->rc.top, ETO_OPAQUE | ETO_CLIPPED, &bl->rc, tx, bl->nChars, NULL);
853 if((Current->Type == slLink) && (Current->u.Link.state & LIS_FOCUSED) && infoPtr->HasFocus)
856 PrevColor = SetBkColor(hdc, OldBkColor);
857 DrawFocusRect(hdc, &bl->rc);
858 SetBkColor(hdc, PrevColor);
867 SetBkColor(hdc, OldBkColor);
868 SetTextColor(hdc, OldTextColor);
869 SelectObject(hdc, hOldFont);
875 /***********************************************************************
877 * Handles the WM_PAINT message.
879 static LRESULT SYSLINK_Paint (SYSLINK_INFO *infoPtr, HDC hdcParam)
884 hdc = hdcParam ? hdcParam : BeginPaint (infoPtr->Self, &ps);
885 SYSLINK_Draw (infoPtr, hdc);
886 if (!hdcParam) EndPaint (infoPtr->Self, &ps);
891 /***********************************************************************
893 * Set new Font for the SysLink control.
895 static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
899 HFONT hOldFont = infoPtr->Font;
900 infoPtr->Font = hFont;
902 /* free the underline font */
903 if(infoPtr->LinkFont != NULL)
905 DeleteObject(infoPtr->LinkFont);
906 infoPtr->LinkFont = NULL;
909 /* Render text position and word wrapping in memory */
910 hdc = GetDC(infoPtr->Self);
913 /* create a new underline font */
914 if(GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
916 lf.lfUnderline = TRUE;
917 infoPtr->LinkFont = CreateFontIndirectW(&lf);
921 ERR("Failed to create link font!\n");
924 SYSLINK_Render(infoPtr, hdc);
925 ReleaseDC(infoPtr->Self, hdc);
930 RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
936 /***********************************************************************
938 * Set new text for the SysLink control.
940 static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
944 /* clear the document */
945 SYSLINK_ClearDoc(infoPtr);
947 textlen = lstrlenW(Text);
948 if(Text == NULL || textlen == 0)
953 /* let's parse the string and create a document */
954 if(SYSLINK_ParseText(infoPtr, Text) > 0)
956 /* Render text position and word wrapping in memory */
957 HDC hdc = GetDC(infoPtr->Self);
958 SYSLINK_Render(infoPtr, hdc);
959 SYSLINK_Draw(infoPtr, hdc);
960 ReleaseDC(infoPtr->Self, hdc);
966 /***********************************************************************
967 * SYSLINK_SetFocusLink
968 * Updates the focus status bits and focusses the specified link.
969 * If no document item is specified, the focus bit will be removed from all links.
970 * Returns the previous focused item.
972 static PDOC_ITEM SYSLINK_SetFocusLink (SYSLINK_INFO *infoPtr, PDOC_ITEM DocItem)
974 PDOC_ITEM Current, PrevFocus = NULL;
976 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
978 if(Current->Type == slLink)
980 if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
985 if(Current == DocItem)
987 Current->u.Link.state |= LIS_FOCUSED;
991 Current->u.Link.state &= ~LIS_FOCUSED;
999 /***********************************************************************
1001 * Sets the states and attributes of a link item.
1003 static LRESULT SYSLINK_SetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1006 BOOL Repaint = FALSE;
1009 if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1011 ERR("Invalid Flags!\n");
1015 di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1018 ERR("Link %d couldn't be found\n", Item->iLink);
1022 if(Item->mask & LIF_STATE)
1024 UINT oldstate = di->u.Link.state;
1025 /* clear the masked bits */
1026 di->u.Link.state &= ~(Item->stateMask & LIS_MASK);
1028 di->u.Link.state |= (Item->state & Item->stateMask) & LIS_MASK;
1029 Repaint = (oldstate != di->u.Link.state);
1031 /* update the focus */
1032 SYSLINK_SetFocusLink(infoPtr, ((di->u.Link.state & LIS_FOCUSED) ? di : NULL));
1035 if(Item->mask & LIF_ITEMID)
1037 if(!di->u.Link.szID)
1039 di->u.Link.szID = Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1042 ERR("Unable to allocate memory for link id\n");
1048 lstrcpynW(di->u.Link.szID, Item->szID, MAX_LINKID_TEXT + 1);
1052 if(Item->mask & LIF_URL)
1054 if(!di->u.Link.szUrl)
1056 di->u.Link.szUrl = Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1059 ERR("Unable to allocate memory for link url\n");
1063 if(di->u.Link.szUrl)
1065 lstrcpynW(di->u.Link.szUrl, Item->szUrl, MAX_LINKID_TEXT + 1);
1071 SYSLINK_RepaintLink(infoPtr, di);
1077 /***********************************************************************
1079 * Retrieves the states and attributes of a link item.
1081 static LRESULT SYSLINK_GetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1085 if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1087 ERR("Invalid Flags!\n");
1091 di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1094 ERR("Link %d couldn't be found\n", Item->iLink);
1098 if(Item->mask & LIF_STATE)
1100 Item->state = (di->u.Link.state & Item->stateMask);
1101 if(!infoPtr->HasFocus)
1103 /* remove the LIS_FOCUSED bit if the control doesn't have focus */
1104 Item->state &= ~LIS_FOCUSED;
1108 if(Item->mask & LIF_ITEMID)
1112 lstrcpynW(Item->szID, di->u.Link.szID, MAX_LINKID_TEXT + 1);
1120 if(Item->mask & LIF_URL)
1122 if(di->u.Link.szUrl)
1124 lstrcpynW(Item->szUrl, di->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1135 /***********************************************************************
1137 * Determines the link the user clicked on.
1139 static LRESULT SYSLINK_HitTest (SYSLINK_INFO *infoPtr, PLHITTESTINFO HitTest)
1144 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1146 if(Current->Type == slLink)
1148 if((Current->u.Link.hRgn != NULL) &&
1149 PtInRegion(Current->u.Link.hRgn, HitTest->pt.x, HitTest->pt.y))
1151 HitTest->item.mask = 0;
1152 HitTest->item.iLink = id;
1153 HitTest->item.state = 0;
1154 HitTest->item.stateMask = 0;
1155 if(Current->u.Link.szID)
1157 lstrcpynW(HitTest->item.szID, Current->u.Link.szID, MAX_LINKID_TEXT + 1);
1161 HitTest->item.szID[0] = 0;
1163 if(Current->u.Link.szUrl)
1165 lstrcpynW(HitTest->item.szUrl, Current->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1169 HitTest->item.szUrl[0] = 0;
1180 /***********************************************************************
1181 * SYSLINK_GetIdealHeight
1182 * Returns the preferred height of a link at the current control's width.
1184 static LRESULT SYSLINK_GetIdealHeight (SYSLINK_INFO *infoPtr)
1186 HDC hdc = GetDC(infoPtr->Self);
1191 HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
1193 if(GetTextMetricsW(hdc, &tm))
1195 height = tm.tmHeight;
1201 SelectObject(hdc, hOldFont);
1202 ReleaseDC(infoPtr->Self, hdc);
1209 /***********************************************************************
1210 * SYSLINK_SendParentNotify
1211 * Sends a WM_NOTIFY message to the parent window.
1213 static LRESULT SYSLINK_SendParentNotify (SYSLINK_INFO *infoPtr, UINT code, PDOC_ITEM Link, int iLink)
1217 nml.hdr.hwndFrom = infoPtr->Self;
1218 nml.hdr.idFrom = GetWindowLongPtrW(infoPtr->Self, GWLP_ID);
1219 nml.hdr.code = code;
1222 nml.item.iLink = iLink;
1224 nml.item.stateMask = 0;
1225 if(Link->u.Link.szID)
1227 lstrcpynW(nml.item.szID, Link->u.Link.szID, MAX_LINKID_TEXT + 1);
1231 nml.item.szID[0] = 0;
1233 if(Link->u.Link.szUrl)
1235 lstrcpynW(nml.item.szUrl, Link->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1239 nml.item.szUrl[0] = 0;
1242 return SendMessageW(infoPtr->Notify, WM_NOTIFY, (WPARAM)nml.hdr.idFrom, (LPARAM)&nml);
1245 /***********************************************************************
1247 * Handles receiving the input focus.
1249 static LRESULT SYSLINK_SetFocus (SYSLINK_INFO *infoPtr, HWND PrevFocusWindow)
1253 infoPtr->HasFocus = TRUE;
1256 /* FIXME - How to detect whether SHIFT+TAB or just TAB has been pressed?
1257 * The problem is we could get this message without keyboard input, too
1259 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1261 if(Focus == NULL && (Focus = SYSLINK_GetNextLink(infoPtr, NULL)))
1263 SYSLINK_SetFocusLink(infoPtr, Focus);
1266 /* This is a temporary hack since I'm not really sure how to detect which link to select.
1267 See message above! */
1268 Focus = SYSLINK_GetNextLink(infoPtr, NULL);
1271 SYSLINK_SetFocusLink(infoPtr, Focus);
1275 SYSLINK_RepaintLink(infoPtr, Focus);
1280 /***********************************************************************
1282 * Handles losing the input focus.
1284 static LRESULT SYSLINK_KillFocus (SYSLINK_INFO *infoPtr, HWND NewFocusWindow)
1288 infoPtr->HasFocus = FALSE;
1289 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1293 SYSLINK_RepaintLink(infoPtr, Focus);
1299 /***********************************************************************
1301 * Returns a link at the specified position
1303 static PDOC_ITEM SYSLINK_LinkAtPt (SYSLINK_INFO *infoPtr, POINT *pt, int *LinkId, BOOL MustBeEnabled)
1308 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1310 if((Current->Type == slLink) && (Current->u.Link.hRgn != NULL) &&
1311 PtInRegion(Current->u.Link.hRgn, pt->x, pt->y) &&
1312 (!MustBeEnabled || (MustBeEnabled && (Current->u.Link.state & LIS_ENABLED))))
1326 /***********************************************************************
1327 * SYSLINK_LButtonDown
1328 * Handles mouse clicks
1330 static LRESULT SYSLINK_LButtonDown (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1332 PDOC_ITEM Current, Old;
1335 Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1338 Old = SYSLINK_SetFocusLink(infoPtr, Current);
1339 if(Old != NULL && Old != Current)
1341 SYSLINK_RepaintLink(infoPtr, Old);
1343 infoPtr->MouseDownID = id;
1344 SYSLINK_RepaintLink(infoPtr, Current);
1345 SetFocus(infoPtr->Self);
1351 /***********************************************************************
1353 * Handles mouse clicks
1355 static LRESULT SYSLINK_LButtonUp (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1357 if(infoPtr->MouseDownID > -1)
1362 Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1363 if((Current != NULL) && (Current->u.Link.state & LIS_FOCUSED) && (infoPtr->MouseDownID == id))
1365 SYSLINK_SendParentNotify(infoPtr, NM_CLICK, Current, id);
1369 infoPtr->MouseDownID = -1;
1374 /***********************************************************************
1376 * Handles ENTER key events
1378 static BOOL SYSLINK_OnEnter (SYSLINK_INFO *infoPtr)
1380 if(infoPtr->HasFocus)
1385 Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1388 SYSLINK_SendParentNotify(infoPtr, NM_RETURN, Focus, id);
1395 /***********************************************************************
1396 * SYSKEY_SelectNextPrevLink
1397 * Changes the currently focused link
1399 static BOOL SYSKEY_SelectNextPrevLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1401 if(infoPtr->HasFocus)
1406 Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1409 PDOC_ITEM NewFocus, OldFocus;
1412 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1414 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1416 if(NewFocus != NULL)
1418 OldFocus = SYSLINK_SetFocusLink(infoPtr, NewFocus);
1420 if(OldFocus != NewFocus)
1422 SYSLINK_RepaintLink(infoPtr, OldFocus);
1424 SYSLINK_RepaintLink(infoPtr, NewFocus);
1432 /***********************************************************************
1433 * SYSKEY_SelectNextPrevLink
1434 * Determines if there's a next or previous link to decide whether the control
1435 * should capture the tab key message
1437 static BOOL SYSLINK_NoNextLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1439 PDOC_ITEM Focus, NewFocus;
1441 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1443 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1445 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1447 return NewFocus == NULL;
1450 /***********************************************************************
1453 static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
1454 WPARAM wParam, LPARAM lParam)
1456 SYSLINK_INFO *infoPtr;
1458 TRACE("hwnd=%p msg=%04x wparam=%x lParam=%lx\n", hwnd, message, wParam, lParam);
1460 infoPtr = (SYSLINK_INFO *)GetWindowLongPtrW(hwnd, 0);
1462 if (!infoPtr && message != WM_CREATE)
1463 return DefWindowProcW( hwnd, message, wParam, lParam );
1467 return SYSLINK_Paint (infoPtr, (HDC)wParam);
1472 DWORD mp = GetMessagePos();
1474 ht.pt.x = (short)LOWORD(mp);
1475 ht.pt.y = (short)HIWORD(mp);
1477 ScreenToClient(infoPtr->Self, &ht.pt);
1478 if(SYSLINK_HitTest (infoPtr, &ht))
1480 SetCursor(LoadCursorW(0, (LPCWSTR)IDC_HAND));
1483 /* let the default window proc handle this message */
1484 return DefWindowProcW(hwnd, message, wParam, lParam);
1490 HDC hdc = GetDC(infoPtr->Self);
1493 SYSLINK_Render(infoPtr, hdc);
1494 ReleaseDC(infoPtr->Self, hdc);
1500 return (LRESULT)infoPtr->Font;
1503 return (LRESULT)SYSLINK_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1506 SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1507 return DefWindowProcW(hwnd, message, wParam, lParam);
1509 case WM_LBUTTONDOWN:
1512 pt.x = LOWORD(lParam);
1513 pt.y = HIWORD(lParam);
1514 return SYSLINK_LButtonDown(infoPtr, wParam, &pt);
1519 pt.x = LOWORD(lParam);
1520 pt.y = HIWORD(lParam);
1521 return SYSLINK_LButtonUp(infoPtr, wParam, &pt);
1529 SYSLINK_OnEnter(infoPtr);
1533 BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1534 SYSKEY_SelectNextPrevLink(infoPtr, shift);
1538 return DefWindowProcW(hwnd, message, wParam, lParam);
1543 LRESULT Ret = DLGC_HASSETSEL;
1544 int vk = (lParam != 0 ? (int)((LPMSG)lParam)->wParam : 0);
1548 Ret |= DLGC_WANTMESSAGE;
1552 BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1553 if(!SYSLINK_NoNextLink(infoPtr, shift))
1555 Ret |= DLGC_WANTTAB;
1559 Ret |= DLGC_WANTCHARS;
1571 pt.x = LOWORD(lParam);
1572 pt.y = HIWORD(lParam);
1574 GetClientRect(infoPtr->Self, &rc);
1575 ScreenToClient(infoPtr->Self, &pt);
1576 if(pt.x < 0 || pt.y < 0 || pt.x > rc.right || pt.y > rc.bottom)
1581 if(SYSLINK_LinkAtPt(infoPtr, &pt, NULL, FALSE))
1586 return HTTRANSPARENT;
1590 return SYSLINK_HitTest(infoPtr, (PLHITTESTINFO)lParam);
1593 return SYSLINK_SetItem(infoPtr, (PLITEM)lParam);
1596 return SYSLINK_GetItem(infoPtr, (PLITEM)lParam);
1598 case LM_GETIDEALHEIGHT:
1599 return SYSLINK_GetIdealHeight(infoPtr);
1602 return SYSLINK_SetFocus(infoPtr, (HWND)wParam);
1605 return SYSLINK_KillFocus(infoPtr, (HWND)wParam);
1608 infoPtr->Style &= ~WS_DISABLED;
1609 infoPtr->Style |= (wParam ? 0 : WS_DISABLED);
1610 InvalidateRect (infoPtr->Self, NULL, FALSE);
1613 case WM_STYLECHANGED:
1614 if (wParam == GWL_STYLE)
1616 infoPtr->Style = ((LPSTYLESTRUCT)lParam)->styleNew;
1618 InvalidateRect(infoPtr->Self, NULL, TRUE);
1623 /* allocate memory for info struct */
1624 infoPtr = Alloc (sizeof(SYSLINK_INFO));
1625 if (!infoPtr) return -1;
1626 SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1628 /* initialize the info struct */
1629 infoPtr->Self = hwnd;
1630 infoPtr->Notify = ((LPCREATESTRUCTW)lParam)->hwndParent;
1631 infoPtr->Style = ((LPCREATESTRUCTW)lParam)->style;
1633 infoPtr->LinkFont = 0;
1634 infoPtr->Items = NULL;
1635 infoPtr->HasFocus = FALSE;
1636 infoPtr->MouseDownID = -1;
1637 infoPtr->TextColor = GetSysColor(COLOR_WINDOWTEXT);
1638 infoPtr->LinkColor = GetSysColor(COLOR_HIGHLIGHT);
1639 infoPtr->VisitedColor = GetSysColor(COLOR_HIGHLIGHT);
1640 TRACE("SysLink Ctrl creation, hwnd=%p\n", hwnd);
1641 SYSLINK_SetText(infoPtr, ((LPCREATESTRUCTW)lParam)->lpszName);
1645 TRACE("SysLink Ctrl destruction, hwnd=%p\n", hwnd);
1646 SYSLINK_ClearDoc(infoPtr);
1647 if(infoPtr->Font != 0) DeleteObject(infoPtr->Font);
1648 if(infoPtr->LinkFont != 0) DeleteObject(infoPtr->LinkFont);
1649 SetWindowLongPtrW(hwnd, 0, 0);
1654 if ((message >= WM_USER) && (message < WM_APP))
1655 ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam );
1656 return DefWindowProcW(hwnd, message, wParam, lParam);
1661 /***********************************************************************
1662 * SYSLINK_Register [Internal]
1664 * Registers the SysLink window class.
1666 VOID SYSLINK_Register (void)
1670 ZeroMemory (&wndClass, sizeof(wndClass));
1671 wndClass.style = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1672 wndClass.lpfnWndProc = SysLinkWindowProc;
1673 wndClass.cbClsExtra = 0;
1674 wndClass.cbWndExtra = sizeof (SYSLINK_INFO *);
1675 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1676 wndClass.lpszClassName = WC_LINK;
1678 RegisterClassW (&wndClass);
1682 /***********************************************************************
1683 * SYSLINK_Unregister [Internal]
1685 * Unregisters the SysLink window class.
1687 VOID SYSLINK_Unregister (void)
1689 UnregisterClassW (WC_LINK, NULL);