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