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