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