xref: /reactos/dll/win32/comctl32/syslink.c (revision b99f0b49)
1 /*
2  * SysLink control
3  *
4  * Copyright 2004 - 2006 Thomas Weidenmueller <w3seek@reactos.com>
5  *
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.
10  *
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.
15  *
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * NOTES
21  *
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.
24  *
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.
28  */
29 
30 #include "comctl32.h"
31 
32 WINE_DEFAULT_DEBUG_CHANNEL(syslink);
33 
34 typedef struct
35 {
36     int nChars;
37     int nSkip;
38     RECT rc;
39 } DOC_TEXTBLOCK, *PDOC_TEXTBLOCK;
40 
41 #define LIF_FLAGSMASK   (LIF_STATE | LIF_ITEMID | LIF_URL)
42 #define LIS_MASK        (LIS_FOCUSED | LIS_ENABLED | LIS_VISITED)
43 
44 typedef enum
45 {
46     slText = 0,
47     slLink
48 } SL_ITEM_TYPE;
49 
50 typedef struct _DOC_ITEM
51 {
52     struct list entry;
53     UINT nText;             /* Number of characters of the text */
54     SL_ITEM_TYPE Type;      /* type of the item */
55     PDOC_TEXTBLOCK Blocks;  /* Array of text blocks */
56     union
57     {
58         struct
59         {
60             UINT state;     /* Link state */
61             WCHAR *szID;    /* Link ID string */
62             WCHAR *szUrl;   /* Link URL string */
63         } Link;
64         struct
65         {
66             UINT Dummy;
67         } Text;
68     } u;
69     WCHAR Text[1];          /* Text of the document item */
70 } DOC_ITEM, *PDOC_ITEM;
71 
72 typedef struct
73 {
74     HWND      Self;         /* The window handle for this control */
75     HWND      Notify;       /* The parent handle to receive notifications */
76     DWORD     Style;        /* Styles for this control */
77     struct list Items;      /* Document items list */
78     BOOL      HasFocus;     /* Whether the control has the input focus */
79     int       MouseDownID;  /* ID of the link that the mouse button first selected */
80     HFONT     Font;         /* Handle to the font for text */
81     HFONT     LinkFont;     /* Handle to the font for links */
82     COLORREF  TextColor;    /* Color of the text */
83     COLORREF  LinkColor;    /* Color of links */
84     COLORREF  VisitedColor; /* Color of visited links */
85     WCHAR     BreakChar;    /* Break Character for the current font */
86     BOOL      IgnoreReturn; /* (infoPtr->Style & LWS_IGNORERETURN) on creation */
87 } SYSLINK_INFO;
88 
89 /* Control configuration constants */
90 
91 #define SL_LEFTMARGIN   (0)
92 #define SL_TOPMARGIN    (0)
93 #define SL_RIGHTMARGIN  (0)
94 #define SL_BOTTOMMARGIN (0)
95 
96 /***********************************************************************
97  * SYSLINK_FreeDocItem
98  * Frees all data and gdi objects associated with a document item
99  */
100 static VOID SYSLINK_FreeDocItem (PDOC_ITEM DocItem)
101 {
102     if(DocItem->Type == slLink)
103     {
104         Free(DocItem->u.Link.szID);
105         Free(DocItem->u.Link.szUrl);
106     }
107 
108     Free(DocItem->Blocks);
109 
110     /* we don't free Text because it's just a pointer to a character in the
111        entire window text string */
112 
113     Free(DocItem);
114 }
115 
116 /***********************************************************************
117  * SYSLINK_AppendDocItem
118  * Create and append a new document item.
119  */
120 static PDOC_ITEM SYSLINK_AppendDocItem (SYSLINK_INFO *infoPtr, LPCWSTR Text, UINT textlen,
121                                         SL_ITEM_TYPE type, PDOC_ITEM LastItem)
122 {
123     PDOC_ITEM Item;
124 
125     textlen = min(textlen, strlenW(Text));
126     Item = Alloc(FIELD_OFFSET(DOC_ITEM, Text[textlen + 1]));
127     if(Item == NULL)
128     {
129         ERR("Failed to alloc DOC_ITEM structure!\n");
130         return NULL;
131     }
132 
133     Item->nText = textlen;
134     Item->Type = type;
135     Item->Blocks = NULL;
136     lstrcpynW(Item->Text, Text, textlen + 1);
137     if (LastItem)
138         list_add_after(&LastItem->entry, &Item->entry);
139     else
140         list_add_tail(&infoPtr->Items, &Item->entry);
141 
142     return Item;
143 }
144 
145 /***********************************************************************
146  * SYSLINK_ClearDoc
147  * Clears the document tree
148  */
149 static VOID SYSLINK_ClearDoc (SYSLINK_INFO *infoPtr)
150 {
151     DOC_ITEM *Item, *Item2;
152 
153     LIST_FOR_EACH_ENTRY_SAFE(Item, Item2, &infoPtr->Items, DOC_ITEM, entry)
154     {
155         list_remove(&Item->entry);
156         SYSLINK_FreeDocItem(Item);
157     }
158 }
159 
160 /***********************************************************************
161  * SYSLINK_ParseText
162  * Parses the window text string and creates a document. Returns the
163  * number of document items created.
164  */
165 static UINT SYSLINK_ParseText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
166 {
167     static const WCHAR SL_LINKOPEN[] =  { '<','a' };
168     static const WCHAR SL_HREF[] =      { 'h','r','e','f','=','\"' };
169     static const WCHAR SL_ID[] =        { 'i','d','=','\"' };
170     static const WCHAR SL_LINKCLOSE[] = { '<','/','a','>' };
171     LPCWSTR current, textstart = NULL, linktext = NULL, firsttag = NULL;
172     int taglen = 0, textlen = 0, linklen = 0, docitems = 0;
173     PDOC_ITEM Last = NULL;
174     SL_ITEM_TYPE CurrentType = slText;
175     LPCWSTR lpID, lpUrl;
176     UINT lenId, lenUrl;
177 
178     TRACE("(%p %s)\n", infoPtr, debugstr_w(Text));
179 
180     for(current = Text; *current != 0;)
181     {
182         if(*current == '<')
183         {
184             if(!strncmpiW(current, SL_LINKOPEN, sizeof(SL_LINKOPEN)/sizeof(SL_LINKOPEN[0])) && (CurrentType == slText))
185             {
186                 BOOL ValidParam = FALSE, ValidLink = FALSE;
187 
188                 if(*(current + 2) == '>')
189                 {
190                     /* we just have to deal with a <a> tag */
191                     taglen = 3;
192                     ValidLink = TRUE;
193                     ValidParam = TRUE;
194                     firsttag = current;
195                     linklen = 0;
196                     lpID = NULL;
197                     lpUrl = NULL;
198                 }
199                 else if(*(current + 2) == infoPtr->BreakChar)
200                 {
201                     /* we expect parameters, parse them */
202                     LPCWSTR *CurrentParameter = NULL, tmp;
203                     UINT *CurrentParameterLen = NULL;
204 
205                     taglen = 3;
206                     tmp = current + taglen;
207                     lpID = NULL;
208                     lpUrl = NULL;
209 
210 CheckParameter:
211                     /* compare the current position with all known parameters */
212                     if(!strncmpiW(tmp, SL_HREF, sizeof(SL_HREF)/sizeof(SL_HREF[0])))
213                     {
214                         taglen += 6;
215                         ValidParam = TRUE;
216                         CurrentParameter = &lpUrl;
217                         CurrentParameterLen = &lenUrl;
218                     }
219                     else if(!strncmpiW(tmp, SL_ID, sizeof(SL_ID)/sizeof(SL_ID[0])))
220                     {
221                         taglen += 4;
222                         ValidParam = TRUE;
223                         CurrentParameter = &lpID;
224                         CurrentParameterLen = &lenId;
225                     }
226                     else
227                     {
228                         ValidParam = FALSE;
229                     }
230 
231                     if(ValidParam)
232                     {
233                         /* we got a known parameter, now search until the next " character.
234                            If we can't find a " character, there's a syntax error and we just assume it's text */
235                         ValidParam = FALSE;
236                         *CurrentParameter = current + taglen;
237                         *CurrentParameterLen = 0;
238 
239                         for(tmp = *CurrentParameter; *tmp != 0; tmp++)
240                         {
241                             taglen++;
242                             if(*tmp == '\"')
243                             {
244                                 ValidParam = TRUE;
245                                 tmp++;
246                                 break;
247                             }
248                             (*CurrentParameterLen)++;
249                         }
250                     }
251                     if(ValidParam)
252                     {
253                         /* we're done with this parameter, now there are only 2 possibilities:
254                          * 1. another parameter is coming, so expect a ' ' (space) character
255                          * 2. the tag is being closed, so expect a '<' character
256                          */
257                         if(*tmp == infoPtr->BreakChar)
258                         {
259                             /* we expect another parameter, do the whole thing again */
260                             taglen++;
261                             tmp++;
262                             goto CheckParameter;
263                         }
264                         else if(*tmp == '>')
265                         {
266                             /* the tag is being closed, we're done */
267                             ValidLink = TRUE;
268                             taglen++;
269                         }
270                     }
271                 }
272 
273                 if(ValidLink && ValidParam)
274                 {
275                     /* the <a ...> tag appears to be valid. save all information
276                        so we can add the link if we find a valid </a> tag later */
277                     CurrentType = slLink;
278                     linktext = current + taglen;
279                     linklen = 0;
280                     firsttag = current;
281                 }
282                 else
283                 {
284                     taglen = 1;
285                     lpID = NULL;
286                     lpUrl = NULL;
287                     if(textstart == NULL)
288                     {
289                         textstart = current;
290                     }
291                 }
292             }
293             else if(!strncmpiW(current, SL_LINKCLOSE, sizeof(SL_LINKCLOSE)/sizeof(SL_LINKCLOSE[0])) && (CurrentType == slLink) && firsttag)
294             {
295                 /* there's a <a...> tag opened, first add the previous text, if present */
296                 if(textstart != NULL && textlen > 0 && firsttag > textstart)
297                 {
298                     Last = SYSLINK_AppendDocItem(infoPtr, textstart, firsttag - textstart, slText, Last);
299                     if(Last == NULL)
300                     {
301                         ERR("Unable to create new document item!\n");
302                         return docitems;
303                     }
304                     docitems++;
305                     textstart = NULL;
306                     textlen = 0;
307                 }
308 
309                 /* now it's time to add the link to the document */
310                 current += 4;
311                 if(linktext != NULL && linklen > 0)
312                 {
313                     Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slLink, Last);
314                     if(Last == NULL)
315                     {
316                         ERR("Unable to create new document item!\n");
317                         return docitems;
318                     }
319                     docitems++;
320                     if(CurrentType == slLink)
321                     {
322                         int nc;
323 
324                         if(!(infoPtr->Style & WS_DISABLED))
325                         {
326                             Last->u.Link.state |= LIS_ENABLED;
327                         }
328                         /* Copy the tag parameters */
329                         if(lpID != NULL)
330                         {
331                             nc = min(lenId, strlenW(lpID));
332                             nc = min(nc, MAX_LINKID_TEXT - 1);
333                             Last->u.Link.szID = Alloc((nc + 1) * sizeof(WCHAR));
334                             if(Last->u.Link.szID != NULL)
335                             {
336                                 lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
337                             }
338                         }
339                         else
340                             Last->u.Link.szID = NULL;
341                         if(lpUrl != NULL)
342                         {
343                             nc = min(lenUrl, strlenW(lpUrl));
344                             nc = min(nc, L_MAX_URL_LENGTH - 1);
345                             Last->u.Link.szUrl = Alloc((nc + 1) * sizeof(WCHAR));
346                             if(Last->u.Link.szUrl != NULL)
347                             {
348                                 lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
349                             }
350                         }
351                         else
352                             Last->u.Link.szUrl = NULL;
353                     }
354                     linktext = NULL;
355                 }
356                 CurrentType = slText;
357                 firsttag = NULL;
358                 textstart = NULL;
359                 continue;
360             }
361             else
362             {
363                 /* we don't know what tag it is, so just continue */
364                 taglen = 1;
365                 linklen++;
366                 if(CurrentType == slText && textstart == NULL)
367                 {
368                     textstart = current;
369                 }
370             }
371 
372             textlen += taglen;
373             current += taglen;
374         }
375         else
376         {
377             textlen++;
378             linklen++;
379 
380             /* save the pointer of the current text item if we couldn't find a tag */
381             if(textstart == NULL && CurrentType == slText)
382             {
383                 textstart = current;
384             }
385 
386             current++;
387         }
388     }
389 
390     if(textstart != NULL && textlen > 0)
391     {
392         Last = SYSLINK_AppendDocItem(infoPtr, textstart, textlen, CurrentType, Last);
393         if(Last == NULL)
394         {
395             ERR("Unable to create new document item!\n");
396             return docitems;
397         }
398         if(CurrentType == slLink)
399         {
400             int nc;
401 
402             if(!(infoPtr->Style & WS_DISABLED))
403             {
404                 Last->u.Link.state |= LIS_ENABLED;
405             }
406             /* Copy the tag parameters */
407             if(lpID != NULL)
408             {
409                 nc = min(lenId, strlenW(lpID));
410                 nc = min(nc, MAX_LINKID_TEXT - 1);
411                 Last->u.Link.szID = Alloc((nc + 1) * sizeof(WCHAR));
412                 if(Last->u.Link.szID != NULL)
413                 {
414                     lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
415                 }
416             }
417             else
418                 Last->u.Link.szID = NULL;
419             if(lpUrl != NULL)
420             {
421                 nc = min(lenUrl, strlenW(lpUrl));
422                 nc = min(nc, L_MAX_URL_LENGTH - 1);
423                 Last->u.Link.szUrl = Alloc((nc + 1) * sizeof(WCHAR));
424                 if(Last->u.Link.szUrl != NULL)
425                 {
426                     lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
427                 }
428             }
429             else
430                 Last->u.Link.szUrl = NULL;
431         }
432         docitems++;
433     }
434 
435     if(linktext != NULL && linklen > 0)
436     {
437         /* we got an unclosed link, just display the text */
438         Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slText, Last);
439         if(Last == NULL)
440         {
441             ERR("Unable to create new document item!\n");
442             return docitems;
443         }
444         docitems++;
445     }
446 
447     return docitems;
448 }
449 
450 /***********************************************************************
451  * SYSLINK_RepaintLink
452  * Repaints a link.
453  */
454 static VOID SYSLINK_RepaintLink (const SYSLINK_INFO *infoPtr, const DOC_ITEM *DocItem)
455 {
456     PDOC_TEXTBLOCK bl;
457     int n;
458 
459     if(DocItem->Type != slLink)
460     {
461         ERR("DocItem not a link!\n");
462         return;
463     }
464 
465     bl = DocItem->Blocks;
466     if (bl != NULL)
467     {
468         n = DocItem->nText;
469 
470         while(n > 0)
471         {
472             InvalidateRect(infoPtr->Self, &bl->rc, TRUE);
473             n -= bl->nChars + bl->nSkip;
474             bl++;
475         }
476     }
477 }
478 
479 /***********************************************************************
480  * SYSLINK_GetLinkItemByIndex
481  * Retrieves a document link by its index
482  */
483 static PDOC_ITEM SYSLINK_GetLinkItemByIndex (const SYSLINK_INFO *infoPtr, int iLink)
484 {
485     DOC_ITEM *Current;
486 
487     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
488     {
489         if ((Current->Type == slLink) && (iLink-- <= 0))
490             return Current;
491     }
492     return NULL;
493 }
494 
495 /***********************************************************************
496  * SYSLINK_GetFocusLink
497  * Retrieves the link that has the LIS_FOCUSED bit
498  */
499 static PDOC_ITEM SYSLINK_GetFocusLink (const SYSLINK_INFO *infoPtr, int *LinkId)
500 {
501     DOC_ITEM *Current;
502     int id = 0;
503 
504     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
505     {
506         if(Current->Type == slLink)
507         {
508             if(Current->u.Link.state & LIS_FOCUSED)
509             {
510                 if(LinkId != NULL)
511                     *LinkId = id;
512                 return Current;
513             }
514             id++;
515         }
516     }
517 
518     return NULL;
519 }
520 
521 /***********************************************************************
522  * SYSLINK_GetNextLink
523  * Gets the next link
524  */
525 static PDOC_ITEM SYSLINK_GetNextLink (const SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
526 {
527     DOC_ITEM *Next;
528 
529     LIST_FOR_EACH_ENTRY(Next, Current ? &Current->entry : &infoPtr->Items, DOC_ITEM, entry)
530     {
531         if (Next->Type == slLink)
532         {
533             return Next;
534         }
535     }
536     return NULL;
537 }
538 
539 /***********************************************************************
540  * SYSLINK_GetPrevLink
541  * Gets the previous link
542  */
543 static PDOC_ITEM SYSLINK_GetPrevLink (const SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
544 {
545     DOC_ITEM *Prev;
546 
547     LIST_FOR_EACH_ENTRY_REV(Prev, Current ? &Current->entry : list_tail(&infoPtr->Items), DOC_ITEM, entry)
548     {
549         if (Prev->Type == slLink)
550         {
551             return Prev;
552         }
553     }
554 
555     return NULL;
556 }
557 
558 /***********************************************************************
559  * SYSLINK_WrapLine
560  * Tries to wrap a line.
561  */
562 static BOOL SYSLINK_WrapLine (LPWSTR Text, WCHAR BreakChar, int x, int *LineLen,
563                              int nFit, LPSIZE Extent)
564 {
565     int i;
566 
567     for (i = 0; i < nFit; i++) if (Text[i] == '\n') break;
568 
569     if (i == *LineLen) return FALSE;
570 
571     /* check if we're in the middle of a word */
572     if (Text[i] != '\n' && Text[i] != BreakChar)
573     {
574         /* search for the beginning of the word */
575         while (i && Text[i - 1] != BreakChar) i--;
576 
577         if (i == 0)
578         {
579             Extent->cx = 0;
580             Extent->cy = 0;
581             if (x == SL_LEFTMARGIN) i = max( nFit, 1 );
582         }
583     }
584     *LineLen = i;
585     return TRUE;
586 }
587 
588 /***********************************************************************
589  * SYSLINK_Render
590  * Renders the document in memory
591  */
592 static VOID SYSLINK_Render (const SYSLINK_INFO *infoPtr, HDC hdc, PRECT pRect)
593 {
594     RECT rc;
595     PDOC_ITEM Current;
596     HGDIOBJ hOldFont;
597     int x, y, LineHeight;
598     SIZE szDoc;
599     TEXTMETRICW tm;
600 
601     szDoc.cx = szDoc.cy = 0;
602 
603     rc = *pRect;
604     rc.right -= SL_RIGHTMARGIN;
605     rc.bottom -= SL_BOTTOMMARGIN;
606 
607     if(rc.right - SL_LEFTMARGIN < 0)
608         rc.right = MAXLONG;
609     if (rc.bottom - SL_TOPMARGIN < 0)
610         rc.bottom = MAXLONG;
611 
612     hOldFont = SelectObject(hdc, infoPtr->Font);
613 
614     x = SL_LEFTMARGIN;
615     y = SL_TOPMARGIN;
616     GetTextMetricsW( hdc, &tm );
617     LineHeight = tm.tmHeight + tm.tmExternalLeading;
618 
619     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
620     {
621         int n, nBlocks;
622         LPWSTR tx;
623         PDOC_TEXTBLOCK bl, cbl;
624         INT nFit;
625         SIZE szDim;
626         int SkipChars = 0;
627 
628         if(Current->nText == 0)
629         {
630             continue;
631         }
632 
633         tx = Current->Text;
634         n = Current->nText;
635 
636         Free(Current->Blocks);
637         Current->Blocks = NULL;
638         bl = NULL;
639         nBlocks = 0;
640 
641         if(Current->Type == slText)
642         {
643             SelectObject(hdc, infoPtr->Font);
644         }
645         else if(Current->Type == slLink)
646         {
647             SelectObject(hdc, infoPtr->LinkFont);
648         }
649 
650         while(n > 0)
651         {
652             /* skip break characters unless they're the first of the doc item */
653             if(tx != Current->Text || x == SL_LEFTMARGIN)
654             {
655                 if (n && *tx == '\n')
656                 {
657                     tx++;
658                     SkipChars++;
659                     n--;
660                 }
661                 while(n > 0 && (*tx) == infoPtr->BreakChar)
662                 {
663                     tx++;
664                     SkipChars++;
665                     n--;
666                 }
667             }
668 
669             if((n == 0 && SkipChars != 0) ||
670                GetTextExtentExPointW(hdc, tx, n, rc.right - x, &nFit, NULL, &szDim))
671             {
672                 int LineLen = n;
673                 BOOL Wrap = FALSE;
674                 PDOC_TEXTBLOCK nbl;
675 
676                 if(n != 0)
677                 {
678                     Wrap = SYSLINK_WrapLine(tx, infoPtr->BreakChar, x, &LineLen, nFit, &szDim);
679 
680                     if(LineLen == 0)
681                     {
682                         /* move one line down, the word didn't fit into the line */
683                         x = SL_LEFTMARGIN;
684                         y += LineHeight;
685                         continue;
686                     }
687 
688                     if(LineLen != n)
689                     {
690                         if(!GetTextExtentExPointW(hdc, tx, LineLen, rc.right - x, NULL, NULL, &szDim))
691                         {
692                             if(bl != NULL)
693                             {
694                                 Free(bl);
695                                 bl = NULL;
696                                 nBlocks = 0;
697                             }
698                             break;
699                         }
700                     }
701                 }
702 
703                 nbl = ReAlloc(bl, (nBlocks + 1) * sizeof(DOC_TEXTBLOCK));
704                 if (nbl != NULL)
705                 {
706                     bl = nbl;
707                     nBlocks++;
708 
709                     cbl = bl + nBlocks - 1;
710 
711                     cbl->nChars = LineLen;
712                     cbl->nSkip = SkipChars;
713                     SetRect(&cbl->rc, x, y, x + szDim.cx, y + szDim.cy);
714 
715                     if (cbl->rc.right > szDoc.cx)
716                         szDoc.cx = cbl->rc.right;
717                     if (cbl->rc.bottom > szDoc.cy)
718                         szDoc.cy = cbl->rc.bottom;
719 
720                     if(LineLen != 0)
721                     {
722                         x += szDim.cx;
723                         if(Wrap)
724                         {
725                             x = SL_LEFTMARGIN;
726                             y += LineHeight;
727                         }
728                     }
729                 }
730                 else
731                 {
732                     Free(bl);
733                     bl = NULL;
734                     nBlocks = 0;
735 
736                     ERR("Failed to alloc DOC_TEXTBLOCK structure!\n");
737                     break;
738                 }
739                 n -= LineLen;
740                 tx += LineLen;
741                 SkipChars = 0;
742             }
743             else
744             {
745                 n--;
746             }
747         }
748 
749         if(nBlocks != 0)
750         {
751             Current->Blocks = bl;
752         }
753     }
754 
755     SelectObject(hdc, hOldFont);
756 
757     pRect->right = pRect->left + szDoc.cx;
758     pRect->bottom = pRect->top + szDoc.cy;
759 }
760 
761 /***********************************************************************
762  * SYSLINK_Draw
763  * Draws the SysLink control.
764  */
765 static LRESULT SYSLINK_Draw (const SYSLINK_INFO *infoPtr, HDC hdc)
766 {
767     RECT rc;
768     PDOC_ITEM Current;
769     HFONT hOldFont;
770     COLORREF OldTextColor, OldBkColor;
771     HBRUSH hBrush;
772     UINT text_flags = ETO_CLIPPED;
773     UINT mode = GetBkMode( hdc );
774 
775     hOldFont = SelectObject(hdc, infoPtr->Font);
776     OldTextColor = SetTextColor(hdc, infoPtr->TextColor);
777     OldBkColor = SetBkColor(hdc, comctl32_color.clrWindow);
778 
779     GetClientRect(infoPtr->Self, &rc);
780     rc.right -= SL_RIGHTMARGIN + SL_LEFTMARGIN;
781     rc.bottom -= SL_BOTTOMMARGIN + SL_TOPMARGIN;
782 
783     if(rc.right < 0 || rc.bottom < 0) return 0;
784 
785     hBrush = (HBRUSH)SendMessageW(infoPtr->Notify, WM_CTLCOLORSTATIC,
786                                   (WPARAM)hdc, (LPARAM)infoPtr->Self);
787     if (!(infoPtr->Style & LWS_TRANSPARENT))
788     {
789         FillRect(hdc, &rc, hBrush);
790         if (GetBkMode( hdc ) == OPAQUE) text_flags |= ETO_OPAQUE;
791     }
792     else SetBkMode( hdc, TRANSPARENT );
793 
794 #ifndef __REACTOS__
795     DeleteObject(hBrush);
796 #endif
797 
798     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
799     {
800         int n;
801         LPWSTR tx;
802         PDOC_TEXTBLOCK bl;
803 
804         bl = Current->Blocks;
805         if(bl != NULL)
806         {
807             tx = Current->Text;
808             n = Current->nText;
809 
810             if(Current->Type == slText)
811             {
812                  SelectObject(hdc, infoPtr->Font);
813                  SetTextColor(hdc, infoPtr->TextColor);
814             }
815             else
816             {
817                  SelectObject(hdc, infoPtr->LinkFont);
818                  SetTextColor(hdc, (!(Current->u.Link.state & LIS_VISITED) ? infoPtr->LinkColor : infoPtr->VisitedColor));
819             }
820 
821             while(n > 0)
822             {
823                 tx += bl->nSkip;
824                 ExtTextOutW(hdc, bl->rc.left, bl->rc.top, text_flags, &bl->rc, tx, bl->nChars, NULL);
825                 if((Current->Type == slLink) && (Current->u.Link.state & LIS_FOCUSED) && infoPtr->HasFocus)
826                 {
827                     COLORREF PrevTextColor;
828                     PrevTextColor = SetTextColor(hdc, infoPtr->TextColor);
829                     DrawFocusRect(hdc, &bl->rc);
830                     SetTextColor(hdc, PrevTextColor);
831                 }
832                 tx += bl->nChars;
833                 n -= bl->nChars + bl->nSkip;
834                 bl++;
835             }
836         }
837     }
838 
839     SetBkColor(hdc, OldBkColor);
840     SetTextColor(hdc, OldTextColor);
841     SelectObject(hdc, hOldFont);
842     SetBkMode(hdc, mode);
843     return 0;
844 }
845 
846 
847 /***********************************************************************
848  * SYSLINK_Paint
849  * Handles the WM_PAINT message.
850  */
851 static LRESULT SYSLINK_Paint (const SYSLINK_INFO *infoPtr, HDC hdcParam)
852 {
853     HDC hdc;
854     PAINTSTRUCT ps;
855 
856     hdc = hdcParam ? hdcParam : BeginPaint (infoPtr->Self, &ps);
857     if (hdc)
858     {
859         SYSLINK_Draw (infoPtr, hdc);
860         if (!hdcParam) EndPaint (infoPtr->Self, &ps);
861     }
862     return 0;
863 }
864 
865 /***********************************************************************
866  *           SYSLINK_SetFont
867  * Set new Font for the SysLink control.
868  */
869 static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
870 {
871     HDC hdc;
872     LOGFONTW lf;
873     TEXTMETRICW tm;
874     RECT rcClient;
875     HFONT hOldFont = infoPtr->Font;
876     infoPtr->Font = hFont;
877 
878     /* free the underline font */
879     if(infoPtr->LinkFont != NULL)
880     {
881         DeleteObject(infoPtr->LinkFont);
882         infoPtr->LinkFont = NULL;
883     }
884 
885     /* Render text position and word wrapping in memory */
886     if (GetClientRect(infoPtr->Self, &rcClient))
887     {
888         hdc = GetDC(infoPtr->Self);
889         if(hdc != NULL)
890         {
891             /* create a new underline font */
892             if(GetTextMetricsW(hdc, &tm) &&
893                GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
894             {
895                 lf.lfUnderline = TRUE;
896                 infoPtr->LinkFont = CreateFontIndirectW(&lf);
897                 infoPtr->BreakChar = tm.tmBreakChar;
898             }
899             else
900             {
901                 ERR("Failed to create link font!\n");
902             }
903 
904             SYSLINK_Render(infoPtr, hdc, &rcClient);
905             ReleaseDC(infoPtr->Self, hdc);
906         }
907     }
908 
909     if(bRedraw)
910     {
911         RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
912     }
913 
914     return hOldFont;
915 }
916 
917 /***********************************************************************
918  *           SYSLINK_SetText
919  * Set new text for the SysLink control.
920  */
921 static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
922 {
923     /* clear the document */
924     SYSLINK_ClearDoc(infoPtr);
925 
926     if(Text == NULL || *Text == 0)
927     {
928         return TRUE;
929     }
930 
931     /* let's parse the string and create a document */
932     if(SYSLINK_ParseText(infoPtr, Text) > 0)
933     {
934         RECT rcClient;
935 
936         /* Render text position and word wrapping in memory */
937         if (GetClientRect(infoPtr->Self, &rcClient))
938         {
939             HDC hdc = GetDC(infoPtr->Self);
940             if (hdc != NULL)
941             {
942                 SYSLINK_Render(infoPtr, hdc, &rcClient);
943                 ReleaseDC(infoPtr->Self, hdc);
944 
945                 InvalidateRect(infoPtr->Self, NULL, TRUE);
946             }
947         }
948     }
949 
950     return TRUE;
951 }
952 
953 /***********************************************************************
954  *           SYSLINK_SetFocusLink
955  * Updates the focus status bits and focusses the specified link.
956  * If no document item is specified, the focus bit will be removed from all links.
957  * Returns the previous focused item.
958  */
959 static PDOC_ITEM SYSLINK_SetFocusLink (const SYSLINK_INFO *infoPtr, const DOC_ITEM *DocItem)
960 {
961     PDOC_ITEM Current, PrevFocus = NULL;
962 
963     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
964     {
965         if(Current->Type == slLink)
966         {
967             if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
968             {
969                 PrevFocus = Current;
970             }
971 
972             if(Current == DocItem)
973             {
974                 Current->u.Link.state |= LIS_FOCUSED;
975             }
976             else
977             {
978                 Current->u.Link.state &= ~LIS_FOCUSED;
979             }
980         }
981     }
982 
983     return PrevFocus;
984 }
985 
986 /***********************************************************************
987  *           SYSLINK_SetItem
988  * Sets the states and attributes of a link item.
989  */
990 static LRESULT SYSLINK_SetItem (const SYSLINK_INFO *infoPtr, const LITEM *Item)
991 {
992     PDOC_ITEM di;
993     int nc;
994     PWSTR szId = NULL;
995     PWSTR szUrl = NULL;
996     BOOL Repaint = FALSE;
997 
998     if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
999     {
1000         ERR("Invalid Flags!\n");
1001         return FALSE;
1002     }
1003 
1004     di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1005     if(di == NULL)
1006     {
1007         ERR("Link %d couldn't be found\n", Item->iLink);
1008         return FALSE;
1009     }
1010 
1011     if(Item->mask & LIF_ITEMID)
1012     {
1013         nc = min(lstrlenW(Item->szID), MAX_LINKID_TEXT - 1);
1014         szId = Alloc((nc + 1) * sizeof(WCHAR));
1015         if(szId)
1016         {
1017             lstrcpynW(szId, Item->szID, nc + 1);
1018         }
1019         else
1020         {
1021             ERR("Unable to allocate memory for link id\n");
1022             return FALSE;
1023         }
1024     }
1025 
1026     if(Item->mask & LIF_URL)
1027     {
1028         nc = min(lstrlenW(Item->szUrl), L_MAX_URL_LENGTH - 1);
1029         szUrl = Alloc((nc + 1) * sizeof(WCHAR));
1030         if(szUrl)
1031         {
1032             lstrcpynW(szUrl, Item->szUrl, nc + 1);
1033         }
1034         else
1035         {
1036             Free(szId);
1037 
1038             ERR("Unable to allocate memory for link url\n");
1039             return FALSE;
1040         }
1041     }
1042 
1043     if(Item->mask & LIF_ITEMID)
1044     {
1045         Free(di->u.Link.szID);
1046         di->u.Link.szID = szId;
1047     }
1048 
1049     if(Item->mask & LIF_URL)
1050     {
1051         Free(di->u.Link.szUrl);
1052         di->u.Link.szUrl = szUrl;
1053     }
1054 
1055     if(Item->mask & LIF_STATE)
1056     {
1057         UINT oldstate = di->u.Link.state;
1058         /* clear the masked bits */
1059         di->u.Link.state &= ~(Item->stateMask & LIS_MASK);
1060         /* copy the bits */
1061         di->u.Link.state |= (Item->state & Item->stateMask) & LIS_MASK;
1062         Repaint = (oldstate != di->u.Link.state);
1063 
1064         /* update the focus */
1065         SYSLINK_SetFocusLink(infoPtr, ((di->u.Link.state & LIS_FOCUSED) ? di : NULL));
1066     }
1067 
1068     if(Repaint)
1069     {
1070         SYSLINK_RepaintLink(infoPtr, di);
1071     }
1072 
1073     return TRUE;
1074 }
1075 
1076 /***********************************************************************
1077  *           SYSLINK_GetItem
1078  * Retrieves the states and attributes of a link item.
1079  */
1080 static LRESULT SYSLINK_GetItem (const SYSLINK_INFO *infoPtr, PLITEM Item)
1081 {
1082     PDOC_ITEM di;
1083 
1084     if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1085     {
1086         ERR("Invalid Flags!\n");
1087         return FALSE;
1088     }
1089 
1090     di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1091     if(di == NULL)
1092     {
1093         ERR("Link %d couldn't be found\n", Item->iLink);
1094         return FALSE;
1095     }
1096 
1097     if(Item->mask & LIF_STATE)
1098     {
1099         Item->state = (di->u.Link.state & Item->stateMask);
1100         if(!infoPtr->HasFocus)
1101         {
1102             /* remove the LIS_FOCUSED bit if the control doesn't have focus */
1103             Item->state &= ~LIS_FOCUSED;
1104         }
1105     }
1106 
1107     if(Item->mask & LIF_ITEMID)
1108     {
1109         if(di->u.Link.szID)
1110         {
1111             lstrcpyW(Item->szID, di->u.Link.szID);
1112         }
1113         else
1114         {
1115             Item->szID[0] = 0;
1116         }
1117     }
1118 
1119     if(Item->mask & LIF_URL)
1120     {
1121         if(di->u.Link.szUrl)
1122         {
1123             lstrcpyW(Item->szUrl, di->u.Link.szUrl);
1124         }
1125         else
1126         {
1127             Item->szUrl[0] = 0;
1128         }
1129     }
1130 
1131     return TRUE;
1132 }
1133 
1134 /***********************************************************************
1135  *           SYSLINK_PtInDocItem
1136  * Determines if a point is in the region of a document item
1137  */
1138 static BOOL SYSLINK_PtInDocItem (const DOC_ITEM *DocItem, POINT pt)
1139 {
1140     PDOC_TEXTBLOCK bl;
1141     int n;
1142 
1143     bl = DocItem->Blocks;
1144     if (bl != NULL)
1145     {
1146         n = DocItem->nText;
1147 
1148         while(n > 0)
1149         {
1150             if (PtInRect(&bl->rc, pt))
1151             {
1152                 return TRUE;
1153             }
1154             n -= bl->nChars + bl->nSkip;
1155             bl++;
1156         }
1157     }
1158 
1159     return FALSE;
1160 }
1161 
1162 /***********************************************************************
1163  *           SYSLINK_HitTest
1164  * Determines the link the user clicked on.
1165  */
1166 static LRESULT SYSLINK_HitTest (const SYSLINK_INFO *infoPtr, PLHITTESTINFO HitTest)
1167 {
1168     PDOC_ITEM Current;
1169     int id = 0;
1170 
1171     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
1172     {
1173         if(Current->Type == slLink)
1174         {
1175             if(SYSLINK_PtInDocItem(Current, HitTest->pt))
1176             {
1177                 HitTest->item.mask = 0;
1178                 HitTest->item.iLink = id;
1179                 HitTest->item.state = 0;
1180                 HitTest->item.stateMask = 0;
1181                 if(Current->u.Link.szID)
1182                 {
1183                     lstrcpyW(HitTest->item.szID, Current->u.Link.szID);
1184                 }
1185                 else
1186                 {
1187                     HitTest->item.szID[0] = 0;
1188                 }
1189                 if(Current->u.Link.szUrl)
1190                 {
1191                     lstrcpyW(HitTest->item.szUrl, Current->u.Link.szUrl);
1192                 }
1193                 else
1194                 {
1195                     HitTest->item.szUrl[0] = 0;
1196                 }
1197                 return TRUE;
1198             }
1199             id++;
1200         }
1201     }
1202 
1203     return FALSE;
1204 }
1205 
1206 /***********************************************************************
1207  *           SYSLINK_GetIdealHeight
1208  * Returns the preferred height of a link at the current control's width.
1209  */
1210 static LRESULT SYSLINK_GetIdealHeight (const SYSLINK_INFO *infoPtr)
1211 {
1212     HDC hdc = GetDC(infoPtr->Self);
1213     if(hdc != NULL)
1214     {
1215         LRESULT height;
1216         TEXTMETRICW tm;
1217         HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
1218 
1219         if(GetTextMetricsW(hdc, &tm))
1220         {
1221             height = tm.tmHeight;
1222         }
1223         else
1224         {
1225             height = 0;
1226         }
1227         SelectObject(hdc, hOldFont);
1228         ReleaseDC(infoPtr->Self, hdc);
1229 
1230         return height;
1231     }
1232     return 0;
1233 }
1234 
1235 /***********************************************************************
1236  *           SYSLINK_SendParentNotify
1237  * Sends a WM_NOTIFY message to the parent window.
1238  */
1239 static LRESULT SYSLINK_SendParentNotify (const SYSLINK_INFO *infoPtr, UINT code, const DOC_ITEM *Link, int iLink)
1240 {
1241     NMLINK nml;
1242 
1243     nml.hdr.hwndFrom = infoPtr->Self;
1244     nml.hdr.idFrom = GetWindowLongPtrW(infoPtr->Self, GWLP_ID);
1245     nml.hdr.code = code;
1246 
1247     nml.item.mask = 0;
1248     nml.item.iLink = iLink;
1249     nml.item.state = 0;
1250     nml.item.stateMask = 0;
1251     if(Link->u.Link.szID)
1252     {
1253         lstrcpyW(nml.item.szID, Link->u.Link.szID);
1254     }
1255     else
1256     {
1257         nml.item.szID[0] = 0;
1258     }
1259     if(Link->u.Link.szUrl)
1260     {
1261         lstrcpyW(nml.item.szUrl, Link->u.Link.szUrl);
1262     }
1263     else
1264     {
1265         nml.item.szUrl[0] = 0;
1266     }
1267 
1268     return SendMessageW(infoPtr->Notify, WM_NOTIFY, nml.hdr.idFrom, (LPARAM)&nml);
1269 }
1270 
1271 /***********************************************************************
1272  *           SYSLINK_SetFocus
1273  * Handles receiving the input focus.
1274  */
1275 static LRESULT SYSLINK_SetFocus (SYSLINK_INFO *infoPtr)
1276 {
1277     PDOC_ITEM Focus;
1278 
1279     infoPtr->HasFocus = TRUE;
1280 
1281     /* We always select the first link, even if we activated the control using
1282        SHIFT+TAB. This is the default behavior */
1283     Focus = SYSLINK_GetNextLink(infoPtr, NULL);
1284     if(Focus != NULL)
1285     {
1286         SYSLINK_SetFocusLink(infoPtr, Focus);
1287         SYSLINK_RepaintLink(infoPtr, Focus);
1288     }
1289     return 0;
1290 }
1291 
1292 /***********************************************************************
1293  *           SYSLINK_KillFocus
1294  * Handles losing the input focus.
1295  */
1296 static LRESULT SYSLINK_KillFocus (SYSLINK_INFO *infoPtr)
1297 {
1298     PDOC_ITEM Focus;
1299 
1300     infoPtr->HasFocus = FALSE;
1301     Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1302 
1303     if(Focus != NULL)
1304     {
1305         SYSLINK_RepaintLink(infoPtr, Focus);
1306     }
1307 
1308     return 0;
1309 }
1310 
1311 /***********************************************************************
1312  *           SYSLINK_LinkAtPt
1313  * Returns a link at the specified position
1314  */
1315 static PDOC_ITEM SYSLINK_LinkAtPt (const SYSLINK_INFO *infoPtr, const POINT *pt, int *LinkId, BOOL MustBeEnabled)
1316 {
1317     PDOC_ITEM Current;
1318     int id = 0;
1319 
1320     LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
1321     {
1322         if((Current->Type == slLink) && SYSLINK_PtInDocItem(Current, *pt) &&
1323            (!MustBeEnabled || (Current->u.Link.state & LIS_ENABLED)))
1324         {
1325             if(LinkId != NULL)
1326             {
1327                 *LinkId = id;
1328             }
1329             return Current;
1330         }
1331         id++;
1332     }
1333 
1334     return NULL;
1335 }
1336 
1337 /***********************************************************************
1338  *           SYSLINK_LButtonDown
1339  * Handles mouse clicks
1340  */
1341 static LRESULT SYSLINK_LButtonDown (SYSLINK_INFO *infoPtr, const POINT *pt)
1342 {
1343     PDOC_ITEM Current, Old;
1344     int id;
1345 
1346     Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1347     if(Current != NULL)
1348     {
1349       SetFocus(infoPtr->Self);
1350 
1351       Old = SYSLINK_SetFocusLink(infoPtr, Current);
1352       if(Old != NULL && Old != Current)
1353       {
1354           SYSLINK_RepaintLink(infoPtr, Old);
1355       }
1356       infoPtr->MouseDownID = id;
1357       SYSLINK_RepaintLink(infoPtr, Current);
1358     }
1359 
1360     return 0;
1361 }
1362 
1363 /***********************************************************************
1364  *           SYSLINK_LButtonUp
1365  * Handles mouse clicks
1366  */
1367 static LRESULT SYSLINK_LButtonUp (SYSLINK_INFO *infoPtr, const POINT *pt)
1368 {
1369     if(infoPtr->MouseDownID > -1)
1370     {
1371         PDOC_ITEM Current;
1372         int id;
1373 
1374         Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1375         if((Current != NULL) && (Current->u.Link.state & LIS_FOCUSED) && (infoPtr->MouseDownID == id))
1376         {
1377             SYSLINK_SendParentNotify(infoPtr, NM_CLICK, Current, id);
1378         }
1379     }
1380 
1381     infoPtr->MouseDownID = -1;
1382 
1383     return 0;
1384 }
1385 
1386 /***********************************************************************
1387  *           SYSLINK_OnEnter
1388  * Handles ENTER key events
1389  */
1390 static BOOL SYSLINK_OnEnter (const SYSLINK_INFO *infoPtr)
1391 {
1392     if(infoPtr->HasFocus && !infoPtr->IgnoreReturn)
1393     {
1394         PDOC_ITEM Focus;
1395         int id;
1396 
1397         Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1398         if(Focus)
1399         {
1400             SYSLINK_SendParentNotify(infoPtr, NM_RETURN, Focus, id);
1401             return TRUE;
1402         }
1403     }
1404     return FALSE;
1405 }
1406 
1407 /***********************************************************************
1408  *           SYSKEY_SelectNextPrevLink
1409  * Changes the currently focused link
1410  */
1411 static BOOL SYSKEY_SelectNextPrevLink (const SYSLINK_INFO *infoPtr, BOOL Prev)
1412 {
1413     if(infoPtr->HasFocus)
1414     {
1415         PDOC_ITEM Focus;
1416         int id;
1417 
1418         Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1419         if(Focus != NULL)
1420         {
1421             PDOC_ITEM NewFocus, OldFocus;
1422 
1423             if(Prev)
1424                 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1425             else
1426                 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1427 
1428             if(NewFocus != NULL)
1429             {
1430                 OldFocus = SYSLINK_SetFocusLink(infoPtr, NewFocus);
1431 
1432                 if(OldFocus && OldFocus != NewFocus)
1433                 {
1434                     SYSLINK_RepaintLink(infoPtr, OldFocus);
1435                 }
1436                 SYSLINK_RepaintLink(infoPtr, NewFocus);
1437                 return TRUE;
1438             }
1439         }
1440     }
1441     return FALSE;
1442 }
1443 
1444 /***********************************************************************
1445  *           SYSKEY_SelectNextPrevLink
1446  * Determines if there's a next or previous link to decide whether the control
1447  * should capture the tab key message
1448  */
1449 static BOOL SYSLINK_NoNextLink (const SYSLINK_INFO *infoPtr, BOOL Prev)
1450 {
1451     PDOC_ITEM Focus, NewFocus;
1452 
1453     Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1454     if(Prev)
1455         NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1456     else
1457         NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1458 
1459     return NewFocus == NULL;
1460 }
1461 
1462 /***********************************************************************
1463  *           SYSLINK_GetIdealSize
1464  * Calculates the ideal size of a link control at a given maximum width.
1465  */
1466 static LONG SYSLINK_GetIdealSize (const SYSLINK_INFO *infoPtr, int cxMaxWidth, SIZE *lpSize)
1467 {
1468     RECT rc;
1469     HDC hdc;
1470 
1471     rc.left = rc.top = rc.bottom = 0;
1472     rc.right = cxMaxWidth;
1473 
1474     hdc = GetDC(infoPtr->Self);
1475     if (hdc != NULL)
1476     {
1477         HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
1478 
1479         SYSLINK_Render(infoPtr, hdc, &rc);
1480 
1481         SelectObject(hdc, hOldFont);
1482         ReleaseDC(infoPtr->Self, hdc);
1483 
1484         lpSize->cx = rc.right;
1485         lpSize->cy = rc.bottom;
1486     }
1487 
1488     return rc.bottom;
1489 }
1490 
1491 /***********************************************************************
1492  *           SysLinkWindowProc
1493  */
1494 static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
1495                                         WPARAM wParam, LPARAM lParam)
1496 {
1497     SYSLINK_INFO *infoPtr;
1498 
1499     TRACE("hwnd=%p msg=%04x wparam=%lx lParam=%lx\n", hwnd, message, wParam, lParam);
1500 
1501     infoPtr = (SYSLINK_INFO *)GetWindowLongPtrW(hwnd, 0);
1502 
1503     if (!infoPtr && message != WM_CREATE)
1504         return DefWindowProcW(hwnd, message, wParam, lParam);
1505 
1506     switch(message) {
1507     case WM_PRINTCLIENT:
1508     case WM_PAINT:
1509         return SYSLINK_Paint (infoPtr, (HDC)wParam);
1510 
1511     case WM_ERASEBKGND:
1512         if (!(infoPtr->Style & LWS_TRANSPARENT))
1513         {
1514             HDC hdc = (HDC)wParam;
1515             HBRUSH brush = CreateSolidBrush( comctl32_color.clrWindow );
1516             RECT rect;
1517 
1518             GetClipBox( hdc, &rect );
1519             FillRect( hdc, &rect, brush );
1520             DeleteObject( brush );
1521             return 1;
1522         }
1523         return 0;
1524 
1525     case WM_SETCURSOR:
1526     {
1527         LHITTESTINFO ht;
1528         DWORD mp = GetMessagePos();
1529 
1530         ht.pt.x = (short)LOWORD(mp);
1531         ht.pt.y = (short)HIWORD(mp);
1532 
1533         ScreenToClient(infoPtr->Self, &ht.pt);
1534         if(SYSLINK_HitTest (infoPtr, &ht))
1535         {
1536             SetCursor(LoadCursorW(0, (LPCWSTR)IDC_HAND));
1537             return TRUE;
1538         }
1539 
1540         return DefWindowProcW(hwnd, message, wParam, lParam);
1541     }
1542 
1543     case WM_SIZE:
1544     {
1545         RECT rcClient;
1546         if (GetClientRect(infoPtr->Self, &rcClient))
1547         {
1548             HDC hdc = GetDC(infoPtr->Self);
1549             if(hdc != NULL)
1550             {
1551                 SYSLINK_Render(infoPtr, hdc, &rcClient);
1552                 ReleaseDC(infoPtr->Self, hdc);
1553             }
1554         }
1555         return 0;
1556     }
1557 
1558     case WM_GETFONT:
1559         return (LRESULT)infoPtr->Font;
1560 
1561     case WM_SETFONT:
1562         return (LRESULT)SYSLINK_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1563 
1564     case WM_SETTEXT:
1565         SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1566         return DefWindowProcW(hwnd, message, wParam, lParam);
1567 
1568     case WM_LBUTTONDOWN:
1569     {
1570         POINT pt;
1571         pt.x = (short)LOWORD(lParam);
1572         pt.y = (short)HIWORD(lParam);
1573         return SYSLINK_LButtonDown(infoPtr, &pt);
1574     }
1575     case WM_LBUTTONUP:
1576     {
1577         POINT pt;
1578         pt.x = (short)LOWORD(lParam);
1579         pt.y = (short)HIWORD(lParam);
1580         return SYSLINK_LButtonUp(infoPtr, &pt);
1581     }
1582 
1583     case WM_KEYDOWN:
1584     {
1585         switch(wParam)
1586         {
1587         case VK_RETURN:
1588             SYSLINK_OnEnter(infoPtr);
1589             return 0;
1590         case VK_TAB:
1591         {
1592             BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1593             SYSKEY_SelectNextPrevLink(infoPtr, shift);
1594             return 0;
1595         }
1596         default:
1597             return DefWindowProcW(hwnd, message, wParam, lParam);
1598         }
1599     }
1600 
1601     case WM_GETDLGCODE:
1602     {
1603         LRESULT Ret = DLGC_HASSETSEL;
1604         int vk = (lParam != 0 ? (int)((LPMSG)lParam)->wParam : 0);
1605         switch(vk)
1606         {
1607         case VK_RETURN:
1608             Ret |= DLGC_WANTMESSAGE;
1609             break;
1610         case VK_TAB:
1611         {
1612             BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1613             if(!SYSLINK_NoNextLink(infoPtr, shift))
1614             {
1615                 Ret |= DLGC_WANTTAB;
1616             }
1617             else
1618             {
1619                 Ret |= DLGC_WANTCHARS;
1620             }
1621             break;
1622         }
1623         }
1624         return Ret;
1625     }
1626 
1627     case WM_NCHITTEST:
1628     {
1629         POINT pt;
1630         RECT rc;
1631         pt.x = (short)LOWORD(lParam);
1632         pt.y = (short)HIWORD(lParam);
1633 
1634         GetClientRect(infoPtr->Self, &rc);
1635         ScreenToClient(infoPtr->Self, &pt);
1636         if(pt.x < 0 || pt.y < 0 || pt.x > rc.right || pt.y > rc.bottom)
1637         {
1638             return HTNOWHERE;
1639         }
1640 
1641         if(SYSLINK_LinkAtPt(infoPtr, &pt, NULL, FALSE))
1642         {
1643             return HTCLIENT;
1644         }
1645 
1646         return HTTRANSPARENT;
1647     }
1648 
1649     case LM_HITTEST:
1650         return SYSLINK_HitTest(infoPtr, (PLHITTESTINFO)lParam);
1651 
1652     case LM_SETITEM:
1653         return SYSLINK_SetItem(infoPtr, (PLITEM)lParam);
1654 
1655     case LM_GETITEM:
1656         return SYSLINK_GetItem(infoPtr, (PLITEM)lParam);
1657 
1658     case LM_GETIDEALHEIGHT:
1659         if (lParam)
1660             return SYSLINK_GetIdealSize(infoPtr, (int)wParam, (SIZE *)lParam);
1661         else
1662             return SYSLINK_GetIdealHeight(infoPtr);
1663 
1664     case WM_SETFOCUS:
1665         return SYSLINK_SetFocus(infoPtr);
1666 
1667     case WM_KILLFOCUS:
1668         return SYSLINK_KillFocus(infoPtr);
1669 
1670     case WM_ENABLE:
1671         infoPtr->Style &= ~WS_DISABLED;
1672         infoPtr->Style |= (wParam ? 0 : WS_DISABLED);
1673         InvalidateRect (infoPtr->Self, NULL, FALSE);
1674         return 0;
1675 
1676     case WM_STYLECHANGED:
1677         if (wParam == GWL_STYLE)
1678         {
1679             infoPtr->Style = ((LPSTYLESTRUCT)lParam)->styleNew;
1680 
1681             InvalidateRect(infoPtr->Self, NULL, TRUE);
1682         }
1683         return 0;
1684 
1685     case WM_CREATE:
1686     {
1687         CREATESTRUCTW *cs = (CREATESTRUCTW*)lParam;
1688 
1689         /* allocate memory for info struct */
1690         infoPtr = Alloc (sizeof(SYSLINK_INFO));
1691         if (!infoPtr) return -1;
1692         SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1693 
1694         /* initialize the info struct */
1695         infoPtr->Self = hwnd;
1696         infoPtr->Notify = cs->hwndParent;
1697         infoPtr->Style = cs->style;
1698         infoPtr->Font = 0;
1699         infoPtr->LinkFont = 0;
1700         list_init(&infoPtr->Items);
1701         infoPtr->HasFocus = FALSE;
1702         infoPtr->MouseDownID = -1;
1703         infoPtr->TextColor = comctl32_color.clrWindowText;
1704         infoPtr->LinkColor = comctl32_color.clrHighlight;
1705         infoPtr->VisitedColor = comctl32_color.clrHighlight;
1706         infoPtr->BreakChar = ' ';
1707         infoPtr->IgnoreReturn = infoPtr->Style & LWS_IGNORERETURN;
1708         TRACE("SysLink Ctrl creation, hwnd=%p\n", hwnd);
1709         SYSLINK_SetText(infoPtr, cs->lpszName);
1710         return 0;
1711     }
1712     case WM_DESTROY:
1713         TRACE("SysLink Ctrl destruction, hwnd=%p\n", hwnd);
1714         SYSLINK_ClearDoc(infoPtr);
1715         if(infoPtr->Font != 0) DeleteObject(infoPtr->Font);
1716         if(infoPtr->LinkFont != 0) DeleteObject(infoPtr->LinkFont);
1717         SetWindowLongPtrW(hwnd, 0, 0);
1718         Free (infoPtr);
1719         return 0;
1720 
1721     case WM_SYSCOLORCHANGE:
1722         COMCTL32_RefreshSysColors();
1723         return 0;
1724 
1725     default:
1726         if ((message >= WM_USER) && (message < WM_APP) && !COMCTL32_IsReflectedMessage(message))
1727         {
1728             ERR("unknown msg %04x wp=%04lx lp=%08lx\n", message, wParam, lParam );
1729         }
1730         return DefWindowProcW(hwnd, message, wParam, lParam);
1731     }
1732 }
1733 
1734 
1735 /***********************************************************************
1736  * SYSLINK_Register [Internal]
1737  *
1738  * Registers the SysLink window class.
1739  */
1740 VOID SYSLINK_Register (void)
1741 {
1742     WNDCLASSW wndClass;
1743 
1744     ZeroMemory (&wndClass, sizeof(wndClass));
1745     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1746     wndClass.lpfnWndProc   = SysLinkWindowProc;
1747     wndClass.cbClsExtra    = 0;
1748     wndClass.cbWndExtra    = sizeof (SYSLINK_INFO *);
1749     wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1750     wndClass.lpszClassName = WC_LINK;
1751 
1752     RegisterClassW (&wndClass);
1753 }
1754 
1755 
1756 /***********************************************************************
1757  * SYSLINK_Unregister [Internal]
1758  *
1759  * Unregisters the SysLink window class.
1760  */
1761 VOID SYSLINK_Unregister (void)
1762 {
1763     UnregisterClassW (WC_LINK, NULL);
1764 }
1765