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