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