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
21 * - Fix SHIFT+TAB and TAB issue (wrong link is selected when control gets the focus)
22 * - Better string parsing
23 * - Improve word wrapping
37 #include "wine/unicode.h"
38 #include "wine/debug.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(progress);
42 INT WINAPI StrCmpNIW(LPCWSTR,LPCWSTR,INT);
44 #define SYSLINK_Alloc(size) HeapAlloc(GetProcessHeap(), 0, (size))
45 #define SYSLINK_Free(ptr) HeapFree(GetProcessHeap(), 0, (ptr))
46 #define SYSLINK_ReAlloc(ptr, size) HeapReAlloc(GetProcessHeap(), 0, ptr, (size))
52 } DOC_TEXTBLOCK, *PDOC_TEXTBLOCK;
54 #define LIF_FLAGSMASK (LIF_STATE | LIF_ITEMID | LIF_URL)
55 #define LIS_MASK (LIS_FOCUSED | LIS_ENABLED | LIS_VISITED)
63 typedef struct _DOC_ITEM
65 struct _DOC_ITEM *Next; /* Address to the next item */
66 LPWSTR Text; /* Text of the document item */
67 UINT nText; /* Number of characters of the text */
68 SL_ITEM_TYPE Type; /* type of the item */
69 PDOC_TEXTBLOCK Blocks; /* Array of text blocks */
74 UINT state; /* Link state */
75 WCHAR *szID; /* Link ID string */
76 WCHAR *szUrl; /* Link URL string */
77 HRGN hRgn; /* Region of the link */
84 } DOC_ITEM, *PDOC_ITEM;
88 HWND Self; /* The window handle for this control */
89 PDOC_ITEM Items; /* Address to the first document item */
90 BOOL HasFocus; /* Whether the control has the input focus */
91 int MouseDownID; /* ID of the link that the mouse button first selected */
92 HFONT Font; /* Handle to the font for text */
93 HFONT LinkFont; /* Handle to the font for links */
94 COLORREF TextColor; /* Color of the text */
95 COLORREF LinkColor; /* Color of links */
96 COLORREF VisitedColor; /* Color of visited links */
99 static const WCHAR SL_LINKOPEN[] = { '<','a', 0 };
100 static const WCHAR SL_HREF[] = { 'h','r','e','f','=','\"',0 };
101 static const WCHAR SL_ID[] = { 'i','d','=','\"',0 };
102 static const WCHAR SL_LINKCLOSE[] = { '<','/','a','>',0 };
104 /* Control configuration constants */
106 #define SL_LEFTMARGIN (0)
107 #define SL_TOPMARGIN (0)
108 #define SL_RIGHTMARGIN (0)
109 #define SL_BOTTOMMARGIN (0)
111 /***********************************************************************
112 * SYSLINK_FreeDocItem
113 * Frees all data and gdi objects associated with a document item
115 static VOID SYSLINK_FreeDocItem (PDOC_ITEM DocItem)
117 if(DocItem->Type == slLink)
119 if(DocItem->u.Link.szID != NULL)
121 SYSLINK_Free(DocItem->u.Link.szID);
123 if(DocItem->u.Link.szUrl != NULL)
125 SYSLINK_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 */
137 SYSLINK_Free(DocItem);
140 /***********************************************************************
141 * SYSLINK_AppendDocItem
142 * Create and append a new document item.
144 static PDOC_ITEM SYSLINK_AppendDocItem (SYSLINK_INFO *infoPtr, LPWSTR Text, UINT textlen,
145 SL_ITEM_TYPE type, PDOC_ITEM LastItem)
148 Item = SYSLINK_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, LPWSTR Text)
203 WCHAR *current, *textstart, *linktext, *firsttag;
204 int taglen = 0, textlen, linklen, docitems = 0;
205 PDOC_ITEM Last = NULL;
206 SL_ITEM_TYPE CurrentType = slText;
211 Style = GetWindowLongW(infoPtr->Self, GWL_STYLE);
219 for(current = (WCHAR*)Text; *current != 0;)
223 if(!StrCmpNIW(current, SL_LINKOPEN, 2) && (CurrentType == slText))
225 BOOL ValidParam = FALSE, ValidLink = FALSE;
227 switch (*(current + 2))
230 /* we just have to deal with a <a> tag */
241 /* we expect parameters, parse them */
242 LPWSTR *CurrentParameter = NULL;
243 UINT *CurrentParameterLen = NULL;
247 tmp = current + taglen;
252 /* compare the current position with all known parameters */
253 if(!StrCmpNIW(tmp, SL_HREF, 6))
257 CurrentParameter = &lpUrl;
258 CurrentParameterLen = &lenUrl;
260 else if(!StrCmpNIW(tmp, SL_ID, 4))
264 CurrentParameter = &lpID;
265 CurrentParameterLen = &lenId;
274 /* we got a known parameter, now search until the next " character.
275 If we can't find a " character, there's a syntax error and we just assume it's text */
277 *CurrentParameter = current + taglen;
278 *CurrentParameterLen = 0;
280 for(tmp = *CurrentParameter; *tmp != 0; tmp++)
289 (*CurrentParameterLen)++;
294 /* we're done with this parameter, now there are only 2 possibilities:
295 * 1. another parameter is coming, so expect a ' ' (space) character
296 * 2. the tag is being closed, so expect a '<' character
301 /* we expect another parameter, do the whole thing again */
307 /* the tag is being closed, we're done */
321 if(ValidLink && ValidParam)
323 /* the <a ...> tag appears to be valid. save all information
324 so we can add the link if we find a valid </a> tag later */
325 CurrentType = slLink;
326 linktext = current + taglen;
335 if(textstart == NULL)
341 else if(!StrCmpNIW(current, SL_LINKCLOSE, 4) && (CurrentType == slLink) && firsttag)
343 /* there's a <a...> tag opened, first add the previous text, if present */
344 if(textstart != NULL && textlen > 0 && firsttag > textstart)
346 Last = SYSLINK_AppendDocItem(infoPtr, textstart, firsttag - textstart, slText, Last);
349 ERR("Unable to create new document item!\n");
357 /* now it's time to add the link to the document */
359 if(linktext != NULL && linklen > 0)
361 Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slLink, Last);
364 ERR("Unable to create new document item!\n");
368 if(CurrentType == slLink)
372 if(!(Style & WS_DISABLED))
374 Last->u.Link.state |= LIS_ENABLED;
376 /* Copy the tag parameters */
379 nc = min(lenId, strlenW(lpID));
380 nc = min(nc, MAX_LINKID_TEXT);
381 Last->u.Link.szID = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
382 if(Last->u.Link.szID != NULL)
384 lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
385 Last->u.Link.szID[nc] = 0;
389 Last->u.Link.szID = NULL;
392 nc = min(lenUrl, strlenW(lpUrl));
393 nc = min(nc, L_MAX_URL_LENGTH);
394 Last->u.Link.szUrl = SYSLINK_Alloc((L_MAX_URL_LENGTH + 1) * sizeof(WCHAR));
395 if(Last->u.Link.szUrl != NULL)
397 lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
398 Last->u.Link.szUrl[nc] = 0;
402 Last->u.Link.szUrl = NULL;
406 CurrentType = slText;
413 /* we don't know what tag it is, so just continue */
416 if(CurrentType == slText && textstart == NULL)
430 /* save the pointer of the current text item if we couldn't find a tag */
431 if(textstart == NULL && CurrentType == slText)
440 if(textstart != NULL && textlen > 0)
442 Last = SYSLINK_AppendDocItem(infoPtr, textstart, textlen, CurrentType, Last);
445 ERR("Unable to create new document item!\n");
448 if(CurrentType == slLink)
452 if(!(Style & WS_DISABLED))
454 Last->u.Link.state |= LIS_ENABLED;
456 /* Copy the tag parameters */
459 nc = min(lenId, strlenW(lpID));
460 nc = min(nc, MAX_LINKID_TEXT);
461 Last->u.Link.szID = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
462 if(Last->u.Link.szID != NULL)
464 lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
465 Last->u.Link.szID[nc] = 0;
469 Last->u.Link.szID = NULL;
472 nc = min(lenUrl, strlenW(lpUrl));
473 nc = min(nc, L_MAX_URL_LENGTH);
474 Last->u.Link.szUrl = SYSLINK_Alloc((L_MAX_URL_LENGTH + 1) * sizeof(WCHAR));
475 if(Last->u.Link.szUrl != NULL)
477 lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
478 Last->u.Link.szUrl[nc] = 0;
482 Last->u.Link.szUrl = NULL;
487 if(linktext != NULL && linklen > 0)
489 /* we got a unclosed link, just display the text */
490 Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slText, Last);
493 ERR("Unable to create new document item!\n");
502 /***********************************************************************
503 * SYSLINK_RepaintLink
506 static VOID SYSLINK_RepaintLink (SYSLINK_INFO *infoPtr, PDOC_ITEM DocItem)
508 if(DocItem->Type != slLink)
510 ERR("DocItem not a link!\n");
514 if(DocItem->u.Link.hRgn != NULL)
516 /* repaint the region */
517 RedrawWindow(infoPtr->Self, NULL, DocItem->u.Link.hRgn, RDW_INVALIDATE | RDW_UPDATENOW);
521 /***********************************************************************
522 * SYSLINK_GetLinkItemByIndex
523 * Retreives a document link by it's index
525 static PDOC_ITEM SYSLINK_GetLinkItemByIndex (SYSLINK_INFO *infoPtr, int iLink)
527 PDOC_ITEM Current = infoPtr->Items;
529 while(Current != NULL)
531 if((Current->Type == slLink) && (iLink-- <= 0))
535 Current = Current->Next;
540 /***********************************************************************
541 * SYSLINK_GetFocusLink
542 * Retreives the link that has the LIS_FOCUSED bit
544 static PDOC_ITEM SYSLINK_GetFocusLink (SYSLINK_INFO *infoPtr, int *LinkId)
546 PDOC_ITEM Current = infoPtr->Items;
549 while(Current != NULL)
551 if((Current->Type == slLink))
553 if(Current->u.Link.state & LIS_FOCUSED)
561 Current = Current->Next;
566 /***********************************************************************
567 * SYSLINK_GetNextLink
570 static PDOC_ITEM SYSLINK_GetNextLink (SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
572 for(Current = (Current != NULL ? Current->Next : infoPtr->Items);
574 Current = Current->Next)
576 if(Current->Type == slLink)
584 /***********************************************************************
585 * SYSLINK_GetPrevLink
586 * Gets the previous link
588 static PDOC_ITEM SYSLINK_GetPrevLink (SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
592 /* returns the last link */
593 PDOC_ITEM Last = NULL;
595 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
597 if(Current->Type == slLink)
606 /* returns the previous link */
607 PDOC_ITEM Cur, Prev = NULL;
609 for(Cur = infoPtr->Items; Cur != NULL; Cur = Cur->Next)
615 if(Cur->Type == slLink)
624 /***********************************************************************
626 * Tries to wrap a line.
628 static BOOL SYSLINK_WrapLine (HDC hdc, LPWSTR Text, WCHAR BreakChar, int *LineLen, int nFit, LPSIZE Extent, int Width)
639 Current = Text + nFit;
641 /* check if we're in the middle of a word */
642 if((*Current) != BreakChar)
644 /* search for the beginning of the word */
645 while(Current > Text && (*(Current - 1)) != BreakChar)
662 /***********************************************************************
664 * Renders the document in memory
666 static VOID SYSLINK_Render (SYSLINK_INFO *infoPtr, HDC hdc)
671 int x, y, LineHeight;
674 GetClientRect(infoPtr->Self, &rc);
675 rc.right -= SL_RIGHTMARGIN;
676 rc.bottom -= SL_BOTTOMMARGIN;
678 if(rc.right - SL_LEFTMARGIN < 0 || rc.bottom - SL_TOPMARGIN < 0) return;
680 hOldFont = SelectObject(hdc, infoPtr->Font);
681 GetTextMetricsW(hdc, &tm);
687 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
691 PDOC_TEXTBLOCK bl, cbl;
695 if(Current->nText == 0)
697 ERR("DOC_ITEM with no text?!\n");
703 bl = Current->Blocks;
706 if(Current->Type == slText)
708 SelectObject(hdc, infoPtr->Font);
710 else if(Current->Type == slLink)
712 SelectObject(hdc, infoPtr->LinkFont);
717 if(GetTextExtentExPointW(hdc, tx, n, rc.right - x, &nFit, NULL, &szDim))
720 BOOL Wrap = SYSLINK_WrapLine(hdc, tx, tm.tmBreakChar, &LineLen, nFit, &szDim, rc.right - x);
724 if(x > SL_LEFTMARGIN)
726 /* move one line down, the word didn't fit into the line */
734 /* the word starts at the beginning of the line and doesn't
735 fit into the line, so break it at the last character that fits */
736 LineLen = max(nFit, 1);
742 GetTextExtentExPointW(hdc, tx, LineLen, rc.right - x, NULL, NULL, &szDim);
747 bl = SYSLINK_ReAlloc(bl, ++nBlocks * sizeof(DOC_TEXTBLOCK));
751 bl = SYSLINK_Alloc(++nBlocks * sizeof(DOC_TEXTBLOCK));
756 cbl = bl + nBlocks - 1;
758 cbl->nChars = LineLen;
761 cbl->rc.right = x + szDim.cx;
762 cbl->rc.bottom = y + szDim.cy;
765 LineHeight = max(LineHeight, szDim.cy);
767 /* (re)calculate the link's region */
768 if(Current->Type == slLink)
772 if(Current->u.Link.hRgn != NULL)
774 DeleteObject(Current->u.Link.hRgn);
776 /* initialize the link's hRgn */
777 Current->u.Link.hRgn = CreateRectRgnIndirect(&cbl->rc);
779 else if(Current->u.Link.hRgn != NULL)
782 hrgn = CreateRectRgnIndirect(&cbl->rc);
783 /* add the rectangle */
784 CombineRgn(Current->u.Link.hRgn, Current->u.Link.hRgn, hrgn, RGN_OR);
798 ERR("Failed to alloc DOC_TEXTBLOCK structure!\n");
806 ERR("GetTextExtentExPoint() failed?!\n");
810 Current->Blocks = bl;
813 SelectObject(hdc, hOldFont);
816 /***********************************************************************
818 * Draws the SysLink control.
820 static LRESULT SYSLINK_Draw (SYSLINK_INFO *infoPtr, HDC hdc)
825 COLORREF OldTextColor, OldBkColor;
827 hOldFont = SelectObject(hdc, infoPtr->Font);
828 OldTextColor = SetTextColor(hdc, infoPtr->TextColor);
829 OldBkColor = SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
831 GetClientRect(infoPtr->Self, &rc);
832 rc.right -= SL_RIGHTMARGIN + SL_LEFTMARGIN;
833 rc.bottom -= SL_BOTTOMMARGIN + SL_TOPMARGIN;
835 if(rc.right < 0 || rc.bottom < 0) return 0;
837 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
843 bl = Current->Blocks;
849 if(Current->Type == slText)
851 SelectObject(hdc, infoPtr->Font);
852 SetTextColor(hdc, infoPtr->TextColor);
856 SelectObject(hdc, infoPtr->LinkFont);
857 SetTextColor(hdc, (!(Current->u.Link.state & LIS_VISITED) ? infoPtr->LinkColor : infoPtr->VisitedColor));
862 ExtTextOutW(hdc, bl->rc.left, bl->rc.top, ETO_OPAQUE | ETO_CLIPPED, &bl->rc, tx, bl->nChars, NULL);
863 if((Current->Type == slLink) && (Current->u.Link.state & LIS_FOCUSED) && infoPtr->HasFocus)
866 PrevColor = SetBkColor(hdc, OldBkColor);
867 DrawFocusRect(hdc, &bl->rc);
868 SetBkColor(hdc, PrevColor);
877 SetBkColor(hdc, OldBkColor);
878 SetTextColor(hdc, OldTextColor);
879 SelectObject(hdc, hOldFont);
885 /***********************************************************************
887 * Handles the WM_PAINT message.
889 static LRESULT SYSLINK_Paint (SYSLINK_INFO *infoPtr)
893 hdc = BeginPaint (infoPtr->Self, &ps);
894 SYSLINK_Draw (infoPtr, hdc);
895 EndPaint (infoPtr->Self, &ps);
900 /***********************************************************************
902 * Set new Font for the SysLink control.
904 static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
908 HFONT hOldFont = infoPtr->Font;
909 infoPtr->Font = hFont;
911 /* free the underline font */
912 if(infoPtr->LinkFont != NULL)
914 DeleteObject(infoPtr->LinkFont);
915 infoPtr->LinkFont = NULL;
918 /* Render text position and word wrapping in memory */
919 hdc = GetDC(infoPtr->Self);
922 /* create a new underline font */
923 if(GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
925 lf.lfUnderline = TRUE;
926 infoPtr->LinkFont = CreateFontIndirectW(&lf);
930 ERR("Failed to create link font!\n");
933 SYSLINK_Render(infoPtr, hdc);
934 ReleaseDC(infoPtr->Self, hdc);
939 RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
945 /***********************************************************************
947 * Set new text for the SysLink control.
949 static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPWSTR Text)
953 /* clear the document */
954 SYSLINK_ClearDoc(infoPtr);
956 textlen = lstrlenW(Text);
957 if(Text == NULL || textlen == 0)
962 /* let's parse the string and create a document */
963 if(SYSLINK_ParseText(infoPtr, Text) > 0)
965 /* Render text position and word wrapping in memory */
966 HDC hdc = GetDC(infoPtr->Self);
967 SYSLINK_Render(infoPtr, hdc);
968 SYSLINK_Draw(infoPtr, hdc);
969 ReleaseDC(infoPtr->Self, hdc);
975 /***********************************************************************
976 * SYSLINK_SetFocusLink
977 * Updates the focus status bits and focusses the specified link.
978 * If no document item is specified, the focus bit will be removed from all links.
979 * Returns the previous focused item.
981 static PDOC_ITEM SYSLINK_SetFocusLink (SYSLINK_INFO *infoPtr, PDOC_ITEM DocItem)
983 PDOC_ITEM Current, PrevFocus = NULL;
985 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
987 if(Current->Type == slLink)
989 if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
994 if(Current == DocItem)
996 Current->u.Link.state |= LIS_FOCUSED;
1000 Current->u.Link.state &= ~LIS_FOCUSED;
1008 /***********************************************************************
1010 * Sets the states and attributes of a link item.
1012 static LRESULT SYSLINK_SetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1015 BOOL Repaint = FALSE;
1018 if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1020 ERR("Invalid Flags!\n");
1024 di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1027 ERR("Link %d couldn't be found\n", Item->iLink);
1031 if(Item->mask & LIF_STATE)
1033 UINT oldstate = di->u.Link.state;
1034 /* clear the masked bits */
1035 di->u.Link.state &= ~(Item->stateMask & LIS_MASK);
1037 di->u.Link.state |= (Item->state & Item->stateMask) & LIS_MASK;
1038 Repaint = (oldstate != di->u.Link.state);
1040 /* update the focus */
1041 SYSLINK_SetFocusLink(infoPtr, ((di->u.Link.state & LIS_FOCUSED) ? di : NULL));
1044 if(Item->mask & LIF_ITEMID)
1046 if(!di->u.Link.szID)
1048 di->u.Link.szID = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1051 ERR("Unable to allocate memory for link id\n");
1057 lstrcpynW(di->u.Link.szID, Item->szID, MAX_LINKID_TEXT + 1);
1061 if(Item->mask & LIF_URL)
1063 if(!di->u.Link.szUrl)
1065 di->u.Link.szUrl = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1068 ERR("Unable to allocate memory for link url\n");
1072 if(di->u.Link.szUrl)
1074 lstrcpynW(di->u.Link.szUrl, Item->szUrl, MAX_LINKID_TEXT + 1);
1080 SYSLINK_RepaintLink(infoPtr, di);
1086 /***********************************************************************
1088 * Retrieves the states and attributes of a link item.
1090 static LRESULT SYSLINK_GetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1094 if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1096 ERR("Invalid Flags!\n");
1100 di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1103 ERR("Link %d couldn't be found\n", Item->iLink);
1107 if(Item->mask & LIF_STATE)
1109 Item->state = (di->u.Link.state & Item->stateMask);
1110 if(!infoPtr->HasFocus)
1112 /* remove the LIS_FOCUSED bit if the control doesn't have focus */
1113 Item->state &= ~LIS_FOCUSED;
1117 if(Item->mask & LIF_ITEMID)
1121 lstrcpynW(Item->szID, di->u.Link.szID, MAX_LINKID_TEXT + 1);
1129 if(Item->mask & LIF_URL)
1131 if(di->u.Link.szUrl)
1133 lstrcpynW(Item->szUrl, di->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1144 /***********************************************************************
1146 * Determines the link the user clicked on.
1148 static LRESULT SYSLINK_HitTest (SYSLINK_INFO *infoPtr, PLHITTESTINFO HitTest)
1153 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1155 if(Current->Type == slLink)
1157 if((Current->u.Link.hRgn != NULL) &&
1158 PtInRegion(Current->u.Link.hRgn, HitTest->pt.x, HitTest->pt.y))
1160 HitTest->item.mask = 0;
1161 HitTest->item.iLink = id;
1162 HitTest->item.state = 0;
1163 HitTest->item.stateMask = 0;
1164 if(Current->u.Link.szID)
1166 lstrcpynW(HitTest->item.szID, Current->u.Link.szID, MAX_LINKID_TEXT + 1);
1170 HitTest->item.szID[0] = 0;
1172 if(Current->u.Link.szUrl)
1174 lstrcpynW(HitTest->item.szUrl, Current->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1178 HitTest->item.szUrl[0] = 0;
1189 /***********************************************************************
1190 * SYSLINK_GetIdealHeight
1191 * Returns the preferred height of a link at the current control's width.
1193 static LRESULT SYSLINK_GetIdealHeight (SYSLINK_INFO *infoPtr)
1195 HDC hdc = GetDC(infoPtr->Self);
1200 HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
1202 if(GetTextMetricsW(hdc, &tm))
1204 height = tm.tmHeight;
1210 SelectObject(hdc, hOldFont);
1211 ReleaseDC(infoPtr->Self, hdc);
1218 /***********************************************************************
1219 * SYSLINK_SendParentNotify
1220 * Sends a WM_NOTIFY message to the parent window.
1222 static LRESULT SYSLINK_SendParentNotify (SYSLINK_INFO *infoPtr, UINT code, PDOC_ITEM Link, int iLink)
1226 nml.hdr.hwndFrom = infoPtr->Self;
1227 nml.hdr.idFrom = GetWindowLongPtrW(infoPtr->Self, GWLP_ID);
1228 nml.hdr.code = code;
1231 nml.item.iLink = iLink;
1233 nml.item.stateMask = 0;
1234 if(Link->u.Link.szID)
1236 lstrcpynW(nml.item.szID, Link->u.Link.szID, MAX_LINKID_TEXT + 1);
1240 nml.item.szID[0] = 0;
1242 if(Link->u.Link.szUrl)
1244 lstrcpynW(nml.item.szUrl, Link->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1248 nml.item.szUrl[0] = 0;
1251 return SendMessageW(GetParent(infoPtr->Self), WM_NOTIFY, (WPARAM)nml.hdr.idFrom, (LPARAM)&nml);
1254 /***********************************************************************
1256 * Handles receiving the input focus.
1258 static LRESULT SYSLINK_SetFocus (SYSLINK_INFO *infoPtr, HWND PrevFocusWindow)
1262 infoPtr->HasFocus = TRUE;
1265 /* FIXME - How to detect whether SHIFT+TAB or just TAB has been pressed?
1266 * The problem is we could get this message without keyboard input, too
1268 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1270 if(Focus == NULL && (Focus = SYSLINK_GetNextLink(infoPtr, NULL)))
1272 SYSLINK_SetFocusLink(infoPtr, Focus);
1275 /* This is a temporary hack since I'm not really sure how to detect which link to select.
1276 See message above! */
1277 Focus = SYSLINK_GetNextLink(infoPtr, NULL);
1280 SYSLINK_SetFocusLink(infoPtr, Focus);
1284 SYSLINK_RepaintLink(infoPtr, Focus);
1289 /***********************************************************************
1291 * Handles losing the input focus.
1293 static LRESULT SYSLINK_KillFocus (SYSLINK_INFO *infoPtr, HWND NewFocusWindow)
1297 infoPtr->HasFocus = FALSE;
1298 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1302 SYSLINK_RepaintLink(infoPtr, Focus);
1308 /***********************************************************************
1310 * Returns a link at the specified position
1312 static PDOC_ITEM SYSLINK_LinkAtPt (SYSLINK_INFO *infoPtr, POINT *pt, int *LinkId, BOOL MustBeEnabled)
1317 for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1319 if((Current->Type == slLink) && (Current->u.Link.hRgn != NULL) &&
1320 PtInRegion(Current->u.Link.hRgn, pt->x, pt->y) &&
1321 (!MustBeEnabled || (MustBeEnabled && (Current->u.Link.state & LIS_ENABLED))))
1335 /***********************************************************************
1336 * SYSLINK_LButtonDown
1337 * Handles mouse clicks
1339 static LRESULT SYSLINK_LButtonDown (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1341 PDOC_ITEM Current, Old;
1344 Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1347 Old = SYSLINK_SetFocusLink(infoPtr, Current);
1348 if(Old != NULL && Old != Current)
1350 SYSLINK_RepaintLink(infoPtr, Old);
1352 infoPtr->MouseDownID = id;
1353 SYSLINK_RepaintLink(infoPtr, Current);
1354 SetFocus(infoPtr->Self);
1360 /***********************************************************************
1362 * Handles mouse clicks
1364 static LRESULT SYSLINK_LButtonUp (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1366 if(infoPtr->MouseDownID > -1)
1371 Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1372 if((Current != NULL) && (Current->u.Link.state & LIS_FOCUSED) && (infoPtr->MouseDownID == id))
1374 SYSLINK_SendParentNotify(infoPtr, NM_CLICK, Current, id);
1378 infoPtr->MouseDownID = -1;
1383 /***********************************************************************
1385 * Handles ENTER key events
1387 static BOOL SYSLINK_OnEnter (SYSLINK_INFO *infoPtr)
1389 if(infoPtr->HasFocus)
1394 Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1397 SYSLINK_SendParentNotify(infoPtr, NM_RETURN, Focus, id);
1404 /***********************************************************************
1405 * SYSKEY_SelectNextPrevLink
1406 * Changes the currently focused link
1408 static BOOL SYSKEY_SelectNextPrevLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1410 if(infoPtr->HasFocus)
1415 Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1418 PDOC_ITEM NewFocus, OldFocus;
1421 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1423 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1425 if(NewFocus != NULL)
1427 OldFocus = SYSLINK_SetFocusLink(infoPtr, NewFocus);
1429 if(OldFocus != NewFocus)
1431 SYSLINK_RepaintLink(infoPtr, OldFocus);
1433 SYSLINK_RepaintLink(infoPtr, NewFocus);
1441 /***********************************************************************
1442 * SYSKEY_SelectNextPrevLink
1443 * Determines if there's a next or previous link to decide whether the control
1444 * should capture the tab key message
1446 static BOOL SYSLINK_NoNextLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1448 PDOC_ITEM Focus, NewFocus;
1450 Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1452 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1454 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1456 return NewFocus == NULL;
1459 /***********************************************************************
1462 static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
1463 WPARAM wParam, LPARAM lParam)
1465 SYSLINK_INFO *infoPtr;
1467 TRACE("hwnd=%p msg=%04x wparam=%x lParam=%lx\n", hwnd, message, wParam, lParam);
1469 infoPtr = (SYSLINK_INFO *)GetWindowLongPtrW(hwnd, 0);
1471 if (!infoPtr && message != WM_CREATE)
1472 return DefWindowProcW( hwnd, message, wParam, lParam );
1476 return SYSLINK_Paint (infoPtr);
1482 DWORD mp = GetMessagePos();
1484 pt = MAKEPOINTS(mp);
1488 ScreenToClient(infoPtr->Self, &ht.pt);
1489 if(SYSLINK_HitTest (infoPtr, &ht))
1491 SetCursor(LoadCursorW(0, (LPCWSTR)IDC_HAND));
1494 /* let the default window proc handle this message */
1495 return DefWindowProcW(hwnd, message, wParam, lParam);
1501 HDC hdc = GetDC(infoPtr->Self);
1504 SYSLINK_Render(infoPtr, hdc);
1505 ReleaseDC(infoPtr->Self, hdc);
1511 return (LRESULT)infoPtr->Font;
1514 return (LRESULT)SYSLINK_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1517 SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1518 return DefWindowProcW(hwnd, message, wParam, lParam);
1520 case WM_LBUTTONDOWN:
1523 pt.x = LOWORD(lParam);
1524 pt.y = HIWORD(lParam);
1525 return SYSLINK_LButtonDown(infoPtr, wParam, &pt);
1530 pt.x = LOWORD(lParam);
1531 pt.y = HIWORD(lParam);
1532 return SYSLINK_LButtonUp(infoPtr, wParam, &pt);
1540 SYSLINK_OnEnter(infoPtr);
1544 BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1545 SYSKEY_SelectNextPrevLink(infoPtr, shift);
1549 return DefWindowProcW(hwnd, message, wParam, lParam);
1554 LRESULT Ret = DLGC_HASSETSEL;
1555 int vk = (lParam != 0 ? (int)((LPMSG)lParam)->wParam : 0);
1559 Ret |= DLGC_WANTMESSAGE;
1563 BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1564 if(!SYSLINK_NoNextLink(infoPtr, shift))
1566 Ret |= DLGC_WANTTAB;
1570 Ret |= DLGC_WANTCHARS;
1582 pt.x = LOWORD(lParam);
1583 pt.y = HIWORD(lParam);
1585 GetClientRect(infoPtr->Self, &rc);
1586 ScreenToClient(infoPtr->Self, &pt);
1587 if(pt.x < 0 || pt.y < 0 || pt.x > rc.right || pt.y > rc.bottom)
1592 if(SYSLINK_LinkAtPt(infoPtr, &pt, NULL, FALSE))
1597 return HTTRANSPARENT;
1601 return SYSLINK_HitTest(infoPtr, (PLHITTESTINFO)lParam);
1604 return SYSLINK_SetItem(infoPtr, (PLITEM)lParam);
1607 return SYSLINK_GetItem(infoPtr, (PLITEM)lParam);
1609 case LM_GETIDEALHEIGHT:
1610 return SYSLINK_GetIdealHeight(infoPtr);
1613 return SYSLINK_SetFocus(infoPtr, (HWND)wParam);
1616 return SYSLINK_KillFocus(infoPtr, (HWND)wParam);
1619 /* allocate memory for info struct */
1620 infoPtr = (SYSLINK_INFO *)SYSLINK_Alloc (sizeof(SYSLINK_INFO));
1621 if (!infoPtr) return -1;
1622 SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1624 /* initialize the info struct */
1625 infoPtr->Self = hwnd;
1627 infoPtr->LinkFont = 0;
1628 infoPtr->Items = NULL;
1629 infoPtr->HasFocus = FALSE;
1630 infoPtr->MouseDownID = -1;
1631 infoPtr->TextColor = GetSysColor(COLOR_WINDOWTEXT);
1632 infoPtr->LinkColor = GetSysColor(COLOR_HIGHLIGHT);
1633 infoPtr->VisitedColor = GetSysColor(COLOR_HIGHLIGHT);
1634 TRACE("SysLink Ctrl creation, hwnd=%p\n", hwnd);
1635 lParam = (LPARAM)(((LPCREATESTRUCTW)lParam)->lpszName);
1636 SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1640 TRACE("SysLink Ctrl destruction, hwnd=%p\n", hwnd);
1641 SYSLINK_ClearDoc(infoPtr);
1642 SYSLINK_Free (infoPtr);
1643 SetWindowLongPtrW(hwnd, 0, 0);
1647 if ((message >= WM_USER) && (message < WM_APP))
1648 ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam );
1649 return DefWindowProcW(hwnd, message, wParam, lParam);
1654 /***********************************************************************
1655 * SYSLINK_Register [Internal]
1657 * Registers the SysLink window class.
1659 VOID SYSLINK_Register (void)
1663 ZeroMemory (&wndClass, sizeof(wndClass));
1664 wndClass.style = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1665 wndClass.lpfnWndProc = SysLinkWindowProc;
1666 wndClass.cbClsExtra = 0;
1667 wndClass.cbWndExtra = sizeof (SYSLINK_INFO *);
1668 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1669 wndClass.lpszClassName = WC_LINK;
1671 RegisterClassW (&wndClass);
1675 /***********************************************************************
1676 * SYSLINK_Unregister [Internal]
1678 * Unregisters the SysLink window class.
1680 VOID SYSLINK_Unregister (void)
1682 UnregisterClassW (WC_LINK, NULL);