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