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