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