Fix indentation for consistency with the rest of the file.
[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  * Retreives a document link by it's 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  * Retreives 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)
883 {
884     HDC hdc;
885     PAINTSTRUCT ps;
886     hdc = BeginPaint (infoPtr->Self, &ps);
887     SYSLINK_Draw (infoPtr, hdc);
888     EndPaint (infoPtr->Self, &ps);
889     return 0;
890 }
891
892
893 /***********************************************************************
894  *           SYSLINK_SetFont
895  * Set new Font for the SysLink control.
896  */
897 static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
898 {
899     HDC hdc;
900     LOGFONTW lf;
901     HFONT hOldFont = infoPtr->Font;
902     infoPtr->Font = hFont;
903     
904     /* free the underline font */
905     if(infoPtr->LinkFont != NULL)
906     {
907         DeleteObject(infoPtr->LinkFont);
908         infoPtr->LinkFont = NULL;
909     }
910
911     /* Render text position and word wrapping in memory */
912     hdc = GetDC(infoPtr->Self);
913     if(hdc != NULL)
914     {
915         /* create a new underline font */
916         if(GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
917         {
918             lf.lfUnderline = TRUE;
919             infoPtr->LinkFont = CreateFontIndirectW(&lf);
920         }
921         else
922         {
923             ERR("Failed to create link font!\n");
924         }
925
926         SYSLINK_Render(infoPtr, hdc);
927         ReleaseDC(infoPtr->Self, hdc);
928     }
929     
930     if(bRedraw)
931     {
932         RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
933     }
934     
935     return hOldFont;
936 }
937
938 /***********************************************************************
939  *           SYSLINK_SetText
940  * Set new text for the SysLink control.
941  */
942 static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPWSTR Text)
943 {
944     int textlen;
945
946     /* clear the document */
947     SYSLINK_ClearDoc(infoPtr);
948     
949     textlen = lstrlenW(Text);
950     if(Text == NULL || textlen == 0)
951     {
952         return TRUE;
953     }
954     
955     /* let's parse the string and create a document */
956     if(SYSLINK_ParseText(infoPtr, Text) > 0)
957     {
958         /* Render text position and word wrapping in memory */
959         HDC hdc = GetDC(infoPtr->Self);
960         SYSLINK_Render(infoPtr, hdc);
961         SYSLINK_Draw(infoPtr, hdc);
962         ReleaseDC(infoPtr->Self, hdc);
963     }
964     
965     return TRUE;
966 }
967
968 /***********************************************************************
969  *           SYSLINK_SetFocusLink
970  * Updates the focus status bits and focusses the specified link.
971  * If no document item is specified, the focus bit will be removed from all links.
972  * Returns the previous focused item.
973  */
974 static PDOC_ITEM SYSLINK_SetFocusLink (SYSLINK_INFO *infoPtr, PDOC_ITEM DocItem)
975 {
976     PDOC_ITEM Current, PrevFocus = NULL;
977     
978     for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
979     {
980         if(Current->Type == slLink)
981         {
982             if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
983             {
984                 PrevFocus = Current;
985             }
986             
987             if(Current == DocItem)
988             {
989                 Current->u.Link.state |= LIS_FOCUSED;
990             }
991             else
992             {
993                 Current->u.Link.state &= ~LIS_FOCUSED;
994             }
995         }
996     }
997     
998     return PrevFocus;
999 }
1000
1001 /***********************************************************************
1002  *           SYSLINK_SetItem
1003  * Sets the states and attributes of a link item.
1004  */
1005 static LRESULT SYSLINK_SetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1006 {
1007     PDOC_ITEM di;
1008     BOOL Repaint = FALSE;
1009     BOOL Ret = TRUE;
1010
1011     if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1012     {
1013         ERR("Invalid Flags!\n");
1014         return FALSE;
1015     }
1016
1017     di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1018     if(di == NULL)
1019     {
1020         ERR("Link %d couldn't be found\n", Item->iLink);
1021         return FALSE;
1022     }
1023     
1024     if(Item->mask & LIF_STATE)
1025     {
1026         UINT oldstate = di->u.Link.state;
1027         /* clear the masked bits */
1028         di->u.Link.state &= ~(Item->stateMask & LIS_MASK);
1029         /* copy the bits */
1030         di->u.Link.state |= (Item->state & Item->stateMask) & LIS_MASK;
1031         Repaint = (oldstate != di->u.Link.state);
1032         
1033         /* update the focus */
1034         SYSLINK_SetFocusLink(infoPtr, ((di->u.Link.state & LIS_FOCUSED) ? di : NULL));
1035     }
1036
1037     if(Item->mask & LIF_ITEMID)
1038     {
1039         if(!di->u.Link.szID)
1040         {
1041             di->u.Link.szID = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1042             if(!Item->szID)
1043             {
1044                 ERR("Unable to allocate memory for link id\n");
1045                 Ret = FALSE;
1046             }
1047         }
1048         if(di->u.Link.szID)
1049         {
1050             lstrcpynW(di->u.Link.szID, Item->szID, MAX_LINKID_TEXT + 1);
1051         }
1052     }
1053
1054     if(Item->mask & LIF_URL)
1055     {
1056         if(!di->u.Link.szUrl)
1057         {
1058             di->u.Link.szUrl = SYSLINK_Alloc((MAX_LINKID_TEXT + 1) * sizeof(WCHAR));
1059             if(!Item->szUrl)
1060             {
1061                 ERR("Unable to allocate memory for link url\n");
1062                 Ret = FALSE;
1063             }
1064         }
1065         if(di->u.Link.szUrl)
1066         {
1067             lstrcpynW(di->u.Link.szUrl, Item->szUrl, MAX_LINKID_TEXT + 1);
1068         }
1069     }
1070     
1071     if(Repaint)
1072     {
1073         SYSLINK_RepaintLink(infoPtr, di);
1074     }
1075     
1076     return Ret;
1077 }
1078
1079 /***********************************************************************
1080  *           SYSLINK_GetItem
1081  * Retrieves the states and attributes of a link item.
1082  */
1083 static LRESULT SYSLINK_GetItem (SYSLINK_INFO *infoPtr, PLITEM Item)
1084 {
1085     PDOC_ITEM di;
1086     
1087     if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1088     {
1089         ERR("Invalid Flags!\n");
1090         return FALSE;
1091     }
1092     
1093     di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
1094     if(di == NULL)
1095     {
1096         ERR("Link %d couldn't be found\n", Item->iLink);
1097         return FALSE;
1098     }
1099     
1100     if(Item->mask & LIF_STATE)
1101     {
1102         Item->state = (di->u.Link.state & Item->stateMask);
1103         if(!infoPtr->HasFocus)
1104         {
1105             /* remove the LIS_FOCUSED bit if the control doesn't have focus */
1106             Item->state &= ~LIS_FOCUSED;
1107         }
1108     }
1109     
1110     if(Item->mask & LIF_ITEMID)
1111     {
1112         if(di->u.Link.szID)
1113         {
1114             lstrcpynW(Item->szID, di->u.Link.szID, MAX_LINKID_TEXT + 1);
1115         }
1116         else
1117         {
1118             Item->szID[0] = 0;
1119         }
1120     }
1121     
1122     if(Item->mask & LIF_URL)
1123     {
1124         if(di->u.Link.szUrl)
1125         {
1126             lstrcpynW(Item->szUrl, di->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1127         }
1128         else
1129         {
1130             Item->szUrl[0] = 0;
1131         }
1132     }
1133     
1134     return TRUE;
1135 }
1136
1137 /***********************************************************************
1138  *           SYSLINK_HitTest
1139  * Determines the link the user clicked on.
1140  */
1141 static LRESULT SYSLINK_HitTest (SYSLINK_INFO *infoPtr, PLHITTESTINFO HitTest)
1142 {
1143     PDOC_ITEM Current;
1144     int id = 0;
1145
1146     for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1147     {
1148         if(Current->Type == slLink)
1149         {
1150             if((Current->u.Link.hRgn != NULL) &&
1151                PtInRegion(Current->u.Link.hRgn, HitTest->pt.x, HitTest->pt.y))
1152             {
1153                 HitTest->item.mask = 0;
1154                 HitTest->item.iLink = id;
1155                 HitTest->item.state = 0;
1156                 HitTest->item.stateMask = 0;
1157                 if(Current->u.Link.szID)
1158                 {
1159                     lstrcpynW(HitTest->item.szID, Current->u.Link.szID, MAX_LINKID_TEXT + 1);
1160                 }
1161                 else
1162                 {
1163                     HitTest->item.szID[0] = 0;
1164                 }
1165                 if(Current->u.Link.szUrl)
1166                 {
1167                     lstrcpynW(HitTest->item.szUrl, Current->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1168                 }
1169                 else
1170                 {
1171                     HitTest->item.szUrl[0] = 0;
1172                 }
1173                 return TRUE;
1174             }
1175             id++;
1176         }
1177     }
1178     
1179     return FALSE;
1180 }
1181
1182 /***********************************************************************
1183  *           SYSLINK_GetIdealHeight
1184  * Returns the preferred height of a link at the current control's width.
1185  */
1186 static LRESULT SYSLINK_GetIdealHeight (SYSLINK_INFO *infoPtr)
1187 {
1188     HDC hdc = GetDC(infoPtr->Self);
1189     if(hdc != NULL)
1190     {
1191         LRESULT height;
1192         TEXTMETRICW tm;
1193         HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
1194         
1195         if(GetTextMetricsW(hdc, &tm))
1196         {
1197             height = tm.tmHeight;
1198         }
1199         else
1200         {
1201             height = 0;
1202         }
1203         SelectObject(hdc, hOldFont);
1204         ReleaseDC(infoPtr->Self, hdc);
1205         
1206         return height;
1207     }
1208     return 0;
1209 }
1210
1211 /***********************************************************************
1212  *           SYSLINK_SendParentNotify
1213  * Sends a WM_NOTIFY message to the parent window.
1214  */
1215 static LRESULT SYSLINK_SendParentNotify (SYSLINK_INFO *infoPtr, UINT code, PDOC_ITEM Link, int iLink)
1216 {
1217     NMLINK nml;
1218
1219     nml.hdr.hwndFrom = infoPtr->Self;
1220     nml.hdr.idFrom = GetWindowLongPtrW(infoPtr->Self, GWLP_ID);
1221     nml.hdr.code = code;
1222
1223     nml.item.mask = 0;
1224     nml.item.iLink = iLink;
1225     nml.item.state = 0;
1226     nml.item.stateMask = 0;
1227     if(Link->u.Link.szID)
1228     {
1229         lstrcpynW(nml.item.szID, Link->u.Link.szID, MAX_LINKID_TEXT + 1);
1230     }
1231     else
1232     {
1233         nml.item.szID[0] = 0;
1234     }
1235     if(Link->u.Link.szUrl)
1236     {
1237         lstrcpynW(nml.item.szUrl, Link->u.Link.szUrl, L_MAX_URL_LENGTH + 1);
1238     }
1239     else
1240     {
1241         nml.item.szUrl[0] = 0;
1242     }
1243
1244     return SendMessageW(GetParent(infoPtr->Self), WM_NOTIFY, (WPARAM)nml.hdr.idFrom, (LPARAM)&nml);
1245 }
1246
1247 /***********************************************************************
1248  *           SYSLINK_SetFocus
1249  * Handles receiving the input focus.
1250  */
1251 static LRESULT SYSLINK_SetFocus (SYSLINK_INFO *infoPtr, HWND PrevFocusWindow)
1252 {
1253     PDOC_ITEM Focus;
1254     
1255     infoPtr->HasFocus = TRUE;
1256
1257 #if 1
1258     /* FIXME - How to detect whether SHIFT+TAB or just TAB has been pressed?
1259      *         The problem is we could get this message without keyboard input, too
1260      */
1261     Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1262     
1263     if(Focus == NULL && (Focus = SYSLINK_GetNextLink(infoPtr, NULL)))
1264     {
1265         SYSLINK_SetFocusLink(infoPtr, Focus);
1266     }
1267 #else
1268     /* This is a temporary hack since I'm not really sure how to detect which link to select.
1269        See message above! */
1270     Focus = SYSLINK_GetNextLink(infoPtr, NULL);
1271     if(Focus != NULL)
1272     {
1273         SYSLINK_SetFocusLink(infoPtr, Focus);
1274     }
1275 #endif
1276     
1277     SYSLINK_RepaintLink(infoPtr, Focus);
1278     
1279     return 0;
1280 }
1281
1282 /***********************************************************************
1283  *           SYSLINK_KillFocus
1284  * Handles losing the input focus.
1285  */
1286 static LRESULT SYSLINK_KillFocus (SYSLINK_INFO *infoPtr, HWND NewFocusWindow)
1287 {
1288     PDOC_ITEM Focus;
1289     
1290     infoPtr->HasFocus = FALSE;
1291     Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1292     
1293     if(Focus != NULL)
1294     {
1295         SYSLINK_RepaintLink(infoPtr, Focus);
1296     }
1297
1298     return 0;
1299 }
1300
1301 /***********************************************************************
1302  *           SYSLINK_LinkAtPt
1303  * Returns a link at the specified position
1304  */
1305 static PDOC_ITEM SYSLINK_LinkAtPt (SYSLINK_INFO *infoPtr, POINT *pt, int *LinkId, BOOL MustBeEnabled)
1306 {
1307     PDOC_ITEM Current;
1308     int id = 0;
1309
1310     for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1311     {
1312         if((Current->Type == slLink) && (Current->u.Link.hRgn != NULL) &&
1313            PtInRegion(Current->u.Link.hRgn, pt->x, pt->y) &&
1314            (!MustBeEnabled || (MustBeEnabled && (Current->u.Link.state & LIS_ENABLED))))
1315         {
1316             if(LinkId != NULL)
1317             {
1318                 *LinkId = id;
1319             }
1320             return Current;
1321         }
1322         id++;
1323     }
1324
1325     return NULL;
1326 }
1327
1328 /***********************************************************************
1329  *           SYSLINK_LButtonDown
1330  * Handles mouse clicks
1331  */
1332 static LRESULT SYSLINK_LButtonDown (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1333 {
1334     PDOC_ITEM Current, Old;
1335     int id;
1336
1337     Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1338     if(Current != NULL)
1339     {
1340       Old = SYSLINK_SetFocusLink(infoPtr, Current);
1341       if(Old != NULL && Old != Current)
1342       {
1343           SYSLINK_RepaintLink(infoPtr, Old);
1344       }
1345       infoPtr->MouseDownID = id;
1346       SYSLINK_RepaintLink(infoPtr, Current);
1347       SetFocus(infoPtr->Self);
1348     }
1349
1350     return 0;
1351 }
1352
1353 /***********************************************************************
1354  *           SYSLINK_LButtonUp
1355  * Handles mouse clicks
1356  */
1357 static LRESULT SYSLINK_LButtonUp (SYSLINK_INFO *infoPtr, DWORD Buttons, POINT *pt)
1358 {
1359     if(infoPtr->MouseDownID > -1)
1360     {
1361         PDOC_ITEM Current;
1362         int id;
1363         
1364         Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
1365         if((Current != NULL) && (Current->u.Link.state & LIS_FOCUSED) && (infoPtr->MouseDownID == id))
1366         {
1367             SYSLINK_SendParentNotify(infoPtr, NM_CLICK, Current, id);
1368         }
1369     }
1370
1371     infoPtr->MouseDownID = -1;
1372
1373     return 0;
1374 }
1375
1376 /***********************************************************************
1377  *           SYSLINK_OnEnter
1378  * Handles ENTER key events
1379  */
1380 static BOOL SYSLINK_OnEnter (SYSLINK_INFO *infoPtr)
1381 {
1382     if(infoPtr->HasFocus)
1383     {
1384         PDOC_ITEM Focus;
1385         int id;
1386         
1387         Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1388         if(Focus != NULL)
1389         {
1390             SYSLINK_SendParentNotify(infoPtr, NM_RETURN, Focus, id);
1391             return TRUE;
1392         }
1393     }
1394     return FALSE;
1395 }
1396
1397 /***********************************************************************
1398  *           SYSKEY_SelectNextPrevLink
1399  * Changes the currently focused link
1400  */
1401 static BOOL SYSKEY_SelectNextPrevLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1402 {
1403     if(infoPtr->HasFocus)
1404     {
1405         PDOC_ITEM Focus;
1406         int id;
1407
1408         Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1409         if(Focus != NULL)
1410         {
1411             PDOC_ITEM NewFocus, OldFocus;
1412
1413             if(Prev)
1414                 NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1415             else
1416                 NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1417
1418             if(NewFocus != NULL)
1419             {
1420                 OldFocus = SYSLINK_SetFocusLink(infoPtr, NewFocus);
1421
1422                 if(OldFocus != NewFocus)
1423                 {
1424                     SYSLINK_RepaintLink(infoPtr, OldFocus);
1425                 }
1426                 SYSLINK_RepaintLink(infoPtr, NewFocus);
1427                 return TRUE;
1428             }
1429         }
1430     }
1431     return FALSE;
1432 }
1433
1434 /***********************************************************************
1435  *           SYSKEY_SelectNextPrevLink
1436  * Determines if there's a next or previous link to decide whether the control
1437  * should capture the tab key message
1438  */
1439 static BOOL SYSLINK_NoNextLink (SYSLINK_INFO *infoPtr, BOOL Prev)
1440 {
1441     PDOC_ITEM Focus, NewFocus;
1442
1443     Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
1444     if(Prev)
1445         NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
1446     else
1447         NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);
1448
1449     return NewFocus == NULL;
1450 }
1451
1452 /***********************************************************************
1453  *           SysLinkWindowProc
1454  */
1455 static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
1456                                         WPARAM wParam, LPARAM lParam)
1457 {
1458     SYSLINK_INFO *infoPtr;
1459
1460     TRACE("hwnd=%p msg=%04x wparam=%x lParam=%lx\n", hwnd, message, wParam, lParam);
1461
1462     infoPtr = (SYSLINK_INFO *)GetWindowLongPtrW(hwnd, 0);
1463
1464     if (!infoPtr && message != WM_CREATE)
1465         return DefWindowProcW( hwnd, message, wParam, lParam );
1466
1467     switch(message) {
1468     case WM_PAINT:
1469         return SYSLINK_Paint (infoPtr);
1470
1471     case WM_SETCURSOR:
1472     {
1473         LHITTESTINFO ht;
1474         DWORD mp = GetMessagePos();
1475         
1476         ht.pt.x = (short)LOWORD(mp);
1477         ht.pt.y = (short)HIWORD(mp);
1478         
1479         ScreenToClient(infoPtr->Self, &ht.pt);
1480         if(SYSLINK_HitTest (infoPtr, &ht))
1481         {
1482             SetCursor(LoadCursorW(0, (LPCWSTR)IDC_HAND));
1483             return TRUE;
1484         }
1485         /* let the default window proc handle this message */
1486         return DefWindowProcW(hwnd, message, wParam, lParam);
1487
1488     }
1489
1490     case WM_SIZE:
1491     {
1492         HDC hdc = GetDC(infoPtr->Self);
1493         if(hdc != NULL)
1494         {
1495           SYSLINK_Render(infoPtr, hdc);
1496           ReleaseDC(infoPtr->Self, hdc);
1497         }
1498         return 0;
1499     }
1500
1501     case WM_GETFONT:
1502         return (LRESULT)infoPtr->Font;
1503
1504     case WM_SETFONT:
1505         return (LRESULT)SYSLINK_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1506
1507     case WM_SETTEXT:
1508         SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1509         return DefWindowProcW(hwnd, message, wParam, lParam);
1510
1511     case WM_LBUTTONDOWN:
1512     {
1513         POINT pt;
1514         pt.x = LOWORD(lParam);
1515         pt.y = HIWORD(lParam);
1516         return SYSLINK_LButtonDown(infoPtr, wParam, &pt);
1517     }
1518     case WM_LBUTTONUP:
1519     {
1520         POINT pt;
1521         pt.x = LOWORD(lParam);
1522         pt.y = HIWORD(lParam);
1523         return SYSLINK_LButtonUp(infoPtr, wParam, &pt);
1524     }
1525     
1526     case WM_KEYDOWN:
1527     {
1528         switch(wParam)
1529         {
1530         case VK_RETURN:
1531             SYSLINK_OnEnter(infoPtr);
1532             return 0;
1533         case VK_TAB:
1534         {
1535             BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1536             SYSKEY_SelectNextPrevLink(infoPtr, shift);
1537             return 0;
1538         }
1539         }
1540         return DefWindowProcW(hwnd, message, wParam, lParam);
1541     }
1542     
1543     case WM_GETDLGCODE:
1544     {
1545         LRESULT Ret = DLGC_HASSETSEL;
1546         int vk = (lParam != 0 ? (int)((LPMSG)lParam)->wParam : 0);
1547         switch(vk)
1548         {
1549         case VK_RETURN:
1550             Ret |= DLGC_WANTMESSAGE;
1551             break;
1552         case VK_TAB:
1553         {
1554             BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
1555             if(!SYSLINK_NoNextLink(infoPtr, shift))
1556             {
1557                 Ret |= DLGC_WANTTAB;
1558             }
1559             else
1560             {
1561                 Ret |= DLGC_WANTCHARS;
1562             }
1563             break;
1564         }
1565         }
1566         return Ret;
1567     }
1568     
1569     case WM_NCHITTEST:
1570     {
1571         POINT pt;
1572         RECT rc;
1573         pt.x = LOWORD(lParam);
1574         pt.y = HIWORD(lParam);
1575         
1576         GetClientRect(infoPtr->Self, &rc);
1577         ScreenToClient(infoPtr->Self, &pt);
1578         if(pt.x < 0 || pt.y < 0 || pt.x > rc.right || pt.y > rc.bottom)
1579         {
1580             return HTNOWHERE;
1581         }
1582
1583         if(SYSLINK_LinkAtPt(infoPtr, &pt, NULL, FALSE))
1584         {
1585             return HTCLIENT;
1586         }
1587         
1588         return HTTRANSPARENT;
1589     }
1590
1591     case LM_HITTEST:
1592         return SYSLINK_HitTest(infoPtr, (PLHITTESTINFO)lParam);
1593
1594     case LM_SETITEM:
1595         return SYSLINK_SetItem(infoPtr, (PLITEM)lParam);
1596
1597     case LM_GETITEM:
1598         return SYSLINK_GetItem(infoPtr, (PLITEM)lParam);
1599
1600     case LM_GETIDEALHEIGHT:
1601         return SYSLINK_GetIdealHeight(infoPtr);
1602
1603     case WM_SETFOCUS:
1604         return SYSLINK_SetFocus(infoPtr, (HWND)wParam);
1605
1606     case WM_KILLFOCUS:
1607         return SYSLINK_KillFocus(infoPtr, (HWND)wParam);
1608
1609     case WM_CREATE:
1610         /* allocate memory for info struct */
1611         infoPtr = (SYSLINK_INFO *)SYSLINK_Alloc (sizeof(SYSLINK_INFO));
1612         if (!infoPtr) return -1;
1613         SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1614
1615         /* initialize the info struct */
1616         infoPtr->Self = hwnd;
1617         infoPtr->Font = 0;
1618         infoPtr->LinkFont = 0;
1619         infoPtr->Items = NULL;
1620         infoPtr->HasFocus = FALSE;
1621         infoPtr->MouseDownID = -1;
1622         infoPtr->TextColor = GetSysColor(COLOR_WINDOWTEXT);
1623         infoPtr->LinkColor = GetSysColor(COLOR_HIGHLIGHT);
1624         infoPtr->VisitedColor = GetSysColor(COLOR_HIGHLIGHT);
1625         TRACE("SysLink Ctrl creation, hwnd=%p\n", hwnd);
1626         lParam = (LPARAM)(((LPCREATESTRUCTW)lParam)->lpszName);
1627         SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1628         return 0;
1629
1630     case WM_DESTROY:
1631         TRACE("SysLink Ctrl destruction, hwnd=%p\n", hwnd);
1632         SYSLINK_ClearDoc(infoPtr);
1633         if(infoPtr->Font != 0) DeleteObject(infoPtr->Font);
1634         if(infoPtr->LinkFont != 0) DeleteObject(infoPtr->LinkFont);
1635         SYSLINK_Free (infoPtr);
1636         SetWindowLongPtrW(hwnd, 0, 0);
1637         return 0;
1638
1639     default:
1640         if ((message >= WM_USER) && (message < WM_APP))
1641             ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam );
1642         return DefWindowProcW(hwnd, message, wParam, lParam);
1643     }
1644 }
1645
1646
1647 /***********************************************************************
1648  * SYSLINK_Register [Internal]
1649  *
1650  * Registers the SysLink window class.
1651  */
1652 VOID SYSLINK_Register (void)
1653 {
1654     WNDCLASSW wndClass;
1655
1656     ZeroMemory (&wndClass, sizeof(wndClass));
1657     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1658     wndClass.lpfnWndProc   = SysLinkWindowProc;
1659     wndClass.cbClsExtra    = 0;
1660     wndClass.cbWndExtra    = sizeof (SYSLINK_INFO *);
1661     wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
1662     wndClass.lpszClassName = WC_LINK;
1663
1664     RegisterClassW (&wndClass);
1665 }
1666
1667
1668 /***********************************************************************
1669  * SYSLINK_Unregister [Internal]
1670  *
1671  * Unregisters the SysLink window class.
1672  */
1673 VOID SYSLINK_Unregister (void)
1674 {
1675     UnregisterClassW (WC_LINK, NULL);
1676 }