~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Wine Cross Reference
wine/dlls/comctl32/syslink.c

Version: ~ [ wine-0.9.61 ] ~ [ wine-0.9.60 ] ~ [ wine-0.9.59 ] ~ [ wine-0.9.58 ] ~ [ wine-0.9.57 ] ~ [ wine-0.9.56 ] ~ [ wine-0.9.55 ] ~ [ wine-0.9.54 ] ~ [ wine-0.9.53 ] ~ [ wine-0.9.52 ] ~ [ wine-0.9.51 ] ~ [ wine-0.9.50 ] ~ [ wine-0.9.49 ] ~ [ wine-0.9.48 ] ~ [ wine-0.9.47 ] ~ [ wine-0.9.46 ] ~ [ wine-0.9.45 ] ~ [ wine-0.9.44 ] ~ [ wine-0.9.43 ] ~ [ wine-0.9.42 ] ~ [ wine-0.9.41 ] ~ [ wine-0.9.40 ] ~ [ wine-0.9.39 ] ~ [ wine-0.9.38 ] ~ [ wine-0.9.37 ] ~ [ wine-0.9.36 ] ~ [ wine-0.9.35 ] ~ [ wine-0.9.34 ] ~ [ wine-0.9.33 ] ~ [ wine-0.9.32 ] ~ [ wine-0.9.31 ] ~ [ wine-0.9.30 ] ~ [ wine-0.9.29 ] ~ [ wine-0.9.28 ] ~ [ wine-0.9.27 ] ~ [ wine-0.9.26 ] ~ [ wine-0.9.25 ] ~ [ wine-0.9.24 ] ~ [ wine-0.9.23 ] ~ [ wine-0.9.22 ] ~ [ wine-0.9.21 ] ~ [ wine-0.9.20 ] ~ [ wine-0.9.19 ] ~ [ wine-0.9.18 ] ~ [ wine-0.9.17 ] ~ [ wine-0.9.16 ] ~ [ wine-0.9.15 ] ~ [ wine-0.9.14 ] ~ [ wine-0.9.13 ] ~ [ wine-0.9.12 ] ~ [ wine-0.9.11 ] ~ [ wine-0.9.10 ] ~ [ wine-0.9.9 ] ~ [ wine-0.9.8 ] ~ [ wine-0.9.7 ] ~ [ wine-0.9.6 ] ~ [ wine-0.9.5 ] ~ [ wine-0.9.4 ] ~ [ wine-0.9.3 ] ~ [ wine-0.9.2 ] ~ [ wine-0.9.1 ] ~ [ wine-0.9 ] ~ [ wine20050930 ] ~ [ wine20050830 ] ~ [ wine20050725 ] ~ [ wine20050628 ] ~ [ wine20050524 ] ~ [ wine20050419 ] ~ [ wine20050310 ] ~ [ wine20050211 ] ~ [ wine20050111 ] ~ [ wine20041201 ] ~ [ wine20041019 ] ~ [ wine20040914 ] ~ [ wine20040813 ] ~ [ wine20040716 ] ~ [ wine20040615 ] ~ [ wine20040505 ] ~ [ wine20040408 ] ~ [ wine20040309 ] ~ [ wine20040213 ] ~ [ wine20040121 ] ~ [ wine20031212 ] ~ [ wine20031118 ] ~ [ wine20031016 ] ~ [ wine20030911 ] ~ [ wine20030813 ] ~ [ wine20030709 ] ~ [ wine20030618 ] ~ [ wine20030508 ] ~ [ wine20030408 ] ~ [ wine20030318 ] ~ [ wine20030219 ] ~ [ wine20030115 ] ~ [ wine20021219 ] ~ [ wine20021125 ] ~ [ wine20021031 ] ~ [ wine20021007 ] ~ [ wine20020904 ] ~ [ wine20020804 ] ~ [ wine20020710 ] ~ [ wine20020605 ] ~ [ wine20020509 ] ~ [ wine20020411 ] ~ [ wine20020310 ] ~ [ wine20020228 ] ~ [ wine20011226 ] ~ [ wine20011108 ] ~ [ wine20011004 ] ~ [ wine20010824 ] ~ [ wine20010731 ] ~ [ wine20010629 ] ~ [ wine20010510 ] ~ [ wine20010418 ] ~ [ wine20010326 ] ~ [ wine20010305 ] ~ [ wine20010216 ] ~ [ wine20010112 ] ~ [ wine20001222 ] ~ [ wine20001202 ] ~ [ wine20001026 ] ~ [ wine20001002 ] ~ [ wine20000909 ] ~ [ wine20000821 ] ~ [ wine20000801 ] ~ [ wine20000716 ] ~ [ wine20000326 ] ~ [ wine20000227 ] ~ [ wine20000130 ] ~ [ wine20000109 ] ~

  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 (const SYSLINK_INFO *infoPtr, const DOC_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 (const 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 (const 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 (const 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 (const 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 (const 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         Free(Current->Blocks);
688         Current->Blocks = NULL;
689         bl = NULL;
690         nBlocks = 0;
691 
692         if(Current->Type == slText)
693         {
694             SelectObject(hdc, infoPtr->Font);
695         }
696         else if(Current->Type == slLink)
697         {
698             SelectObject(hdc, infoPtr->LinkFont);
699         }
700         
701         while(n > 0)
702         {
703             int SkipChars = 0;
704 
705             /* skip break characters unless they're the first of the doc item */
706             if(tx != Current->Text || x == SL_LEFTMARGIN)
707             {
708                 while(n > 0 && (*tx) == infoPtr->BreakChar)
709                 {
710                     tx++;
711                     SkipChars++;
712                     n--;
713                 }
714             }
715 
716             if((n == 0 && SkipChars != 0) ||
717                GetTextExtentExPointW(hdc, tx, n, rc.right - x, &nFit, NULL, &szDim))
718             {
719                 int LineLen = n;
720                 BOOL Wrap = FALSE;
721                 PDOC_TEXTBLOCK nbl;
722                 
723                 if(n != 0)
724                 {
725                     Wrap = SYSLINK_WrapLine(hdc, tx, infoPtr->BreakChar, &LineLen, nFit, &szDim, rc.right - x);
726 
727                     if(LineLen == 0)
728                     {
729                         if(x > SL_LEFTMARGIN)
730                         {
731                             /* move one line down, the word didn't fit into the line */
732                             x = SL_LEFTMARGIN;
733                             y += LineHeight;
734                             LineHeight = 0;
735                             continue;
736                         }
737                         else
738                         {
739                             /* the word starts at the beginning of the line and doesn't
740                                fit into the line, so break it at the last character that fits */
741                             LineLen = max(nFit, 1);
742                         }
743                     }
744 
745                     if(LineLen != n)
746                     {
747                         if(!GetTextExtentExPointW(hdc, tx, LineLen, rc.right - x, NULL, NULL, &szDim))
748                         {
749                             if(bl != NULL)
750                             {
751                                 Free(bl);
752                                 bl = NULL;
753                                 nBlocks = 0;
754                             }
755                             break;
756                         }
757                     }
758                 }
759                 
760                 nbl = ReAlloc(bl, (nBlocks + 1) * sizeof(DOC_TEXTBLOCK));
761                 if (nbl != NULL)
762                 {
763                     bl = nbl;
764                     nBlocks++;
765 
766                     cbl = bl + nBlocks - 1;
767                     
768                     cbl->nChars = LineLen;
769                     cbl->nSkip = SkipChars;
770                     cbl->rc.left = x;
771                     cbl->rc.top = y;
772                     cbl->rc.right = x + szDim.cx;
773                     cbl->rc.bottom = y + szDim.cy;
774 
775                     if (cbl->rc.right > szDoc.cx)
776                         szDoc.cx = cbl->rc.right;
777                     if (cbl->rc.bottom > szDoc.cy)
778                         szDoc.cy = cbl->rc.bottom;
779 
780                     if(LineLen != 0)
781                     {
782                         x += szDim.cx;
783                         LineHeight = max(LineHeight, szDim.cy);
784 
785                         if(Wrap)
786                         {
787                             x = SL_LEFTMARGIN;
788                             y += LineHeight;
789                             LineHeight = 0;
790                         }
791                     }
792                 }
793                 else
794                 {
795                     Free(bl);
796                     bl = NULL;
797                     nBlocks = 0;
798 
799                     ERR("Failed to alloc DOC_TEXTBLOCK structure!\n");
800                     break;
801                 }
802                 n -= LineLen;
803                 tx += LineLen;
804             }
805             else
806             {
807                 n--;
808             }
809         }
810 
811         if(nBlocks != 0)
812         {
813             Current->Blocks = bl;
814         }
815     }
816     
817     SelectObject(hdc, hOldFont);
818 
819     pRect->right = pRect->left + szDoc.cx;
820     pRect->bottom = pRect->top + szDoc.cy;
821 }
822 
823 /***********************************************************************
824  * SYSLINK_Draw
825  * Draws the SysLink control.
826  */
827 static LRESULT SYSLINK_Draw (const SYSLINK_INFO *infoPtr, HDC hdc)
828 {
829     RECT rc;
830     PDOC_ITEM Current;
831     HFONT hOldFont;
832     COLORREF OldTextColor, OldBkColor;
833 
834     hOldFont = SelectObject(hdc, infoPtr->Font);
835     OldTextColor = SetTextColor(hdc, infoPtr->TextColor);
836     OldBkColor = SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
837     
838     GetClientRect(infoPtr->Self, &rc);
839     rc.right -= SL_RIGHTMARGIN + SL_LEFTMARGIN;
840     rc.bottom -= SL_BOTTOMMARGIN + SL_TOPMARGIN;
841 
842     if(rc.right < 0 || rc.bottom < 0) return 0;
843 
844     for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
845     {
846         int n;
847         LPWSTR tx;
848         PDOC_TEXTBLOCK bl;
849         
850         bl = Current->Blocks;
851         if(bl != NULL)
852         {
853             tx = Current->Text;
854             n = Current->nText;
855 
856             if(Current->Type == slText)
857             {
858                  SelectObject(hdc, infoPtr->Font);
859                  SetTextColor(hdc, infoPtr->TextColor);
860             }
861             else
862             {
863                  SelectObject(hdc, infoPtr->LinkFont);
864                  SetTextColor(hdc, (!(Current->u.Link.state & LIS_VISITED) ? infoPtr->LinkColor : infoPtr->VisitedColor));
865             }
866 
867             while(n > 0)
868             {
869                 tx += bl->nSkip;
870                 ExtTextOutW(hdc, bl->rc.left, bl->rc.top, ETO_OPAQUE | ETO_CLIPPED, &bl->rc, tx, bl->nChars, NULL);
871                 if((Current->Type == slLink) && (Current->u.Link.state & LIS_FOCUSED) && infoPtr->HasFocus)
872                 {
873                     COLORREF PrevTextColor;
874                     PrevTextColor = SetTextColor(hdc, infoPtr->TextColor);
875                     DrawFocusRect(hdc, &bl->rc);
876                     SetTextColor(hdc, PrevTextColor);
877                 }
878                 tx += bl->nChars;
879                 n -= bl->nChars + bl->nSkip;
880                 bl++;
881             }
882         }
883     }
884 
885     SetBkColor(hdc, OldBkColor);
886     SetTextColor(hdc, OldTextColor);
887     SelectObject(hdc, hOldFont);
888     
889     return 0;
890 }
891 
892 
893 /***********************************************************************
894  * SYSLINK_Paint
895  * Handles the WM_PAINT message.
896  */
897 static LRESULT SYSLINK_Paint (const SYSLINK_INFO *infoPtr, HDC hdcParam)
898 {
899     HDC hdc;
900     PAINTSTRUCT ps;
901 
902     hdc = hdcParam ? hdcParam : BeginPaint (infoPtr->Self, &ps);
903     if (hdc)
904     {
905         SYSLINK_Draw (infoPtr, hdc);
906         if (!hdcParam) EndPaint (infoPtr->Self, &ps);
907     }
908     return 0;
909 }
910 
911 
912 /***********************************************************************
913  *           SYSLINK_SetFont
914  * Set new Font for the SysLink control.
915  */
916 static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
917 {
918     HDC hdc;
919     LOGFONTW lf;
920     TEXTMETRICW tm;
921     RECT rcClient;
922     HFONT hOldFont = infoPtr->Font;
923     infoPtr->Font = hFont;
924     
925     /* free the underline font */
926     if(infoPtr->LinkFont != NULL)
927     {
928         DeleteObject(infoPtr->LinkFont);
929         infoPtr->LinkFont = NULL;
930     }
931 
932     /* Render text position and word wrapping in memory */
933     if (GetClientRect(infoPtr->Self, &rcClient))
934     {
935         hdc = GetDC(infoPtr->Self);
936         if(hdc != NULL)
937         {
938             /* create a new underline font */
939             if(GetTextMetricsW(hdc, &tm) &&
940                GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
941             {
942                 lf.lfUnderline = TRUE;
943                 infoPtr->LinkFont = CreateFontIndirectW(&lf);
944                 infoPtr->BreakChar = tm.tmBreakChar;
945             }
946             else
947             {
948                 ERR("Failed to create link font!\n");
949             }
950 
951             SYSLINK_Render(infoPtr, hdc, &rcClient);
952             ReleaseDC(infoPtr->Self, hdc);
953         }
954     }
955     
956     if(bRedraw)
957     {
958         RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
959     }
960     
961     return hOldFont;
962 }
963 
964 /***********************************************************************
965  *           SYSLINK_SetText
966  * Set new text for the SysLink control.
967  */
968 static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
969 {
970     /* clear the document */
971     SYSLINK_ClearDoc(infoPtr);
972 
973     if(Text == NULL || *Text == 0)
974     {
975         return TRUE;
976     }
977 
978     /* let's parse the string and create a document */
979     if(SYSLINK_ParseText(infoPtr, Text) > 0)
980     {
981         RECT rcClient;
982 
983         /* Render text position and word wrapping in memory */
984         if (GetClientRect(infoPtr->Self, &rcClient))
985         {
986             HDC hdc = GetDC(infoPtr->Self);
987             if (hdc != NULL)
988             {
989                 SYSLINK_Render(infoPtr, hdc, &rcClient);
990                 ReleaseDC(infoPtr->Self, hdc);
991 
992                 InvalidateRect(infoPtr->Self, NULL, TRUE);
993             }
994         }
995     }
996     
997     return TRUE;
998 }
999 
1000 /***********************************************************************
1001  *           SYSLINK_SetFocusLink
1002  * Updates the focus status bits and focusses the specified link.
1003  * If no document item is specified, the focus bit will be removed from all links.
1004  * Returns the previous focused item.
1005  */
1006 static PDOC_ITEM SYSLINK_SetFocusLink (const SYSLINK_INFO *infoPtr, const DOC_ITEM *DocItem)
1007 {
1008     PDOC_ITEM Current, PrevFocus = NULL;
1009     
1010     for(Current = infoPtr->Items; Current != NULL; Current = Current->Next)
1011     {
1012         if(Current->Type == slLink)
1013         {
1014             if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
1015             {
1016                 PrevFocus = Current;
1017             }
1018             
1019             if(Current == DocItem)
1020             {
1021                 Current->u.Link.state |= LIS_FOCUSED;
1022             }
1023             else
1024             {
1025                 Current->u.Link.state &= ~LIS_FOCUSED;
1026             }
1027         }
1028     }
1029     
1030     return PrevFocus;
1031 }
1032 
1033 /***********************************************************************
1034  *           SYSLINK_SetItem
1035  * Sets the states and attributes of a link item.
1036  */
1037 static LRESULT SYSLINK_SetItem (const SYSLINK_INFO *infoPtr, const LITEM *Item)
1038 {
1039     PDOC_ITEM di;
1040     int nc;
1041     PWSTR szId = NULL;
1042     PWSTR szUrl = NULL;
1043     BOOL Repaint = FALSE;
1044 
1045     if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
1046     {
1047         ERR("Invalid Flags!\n");
1048         return FALSE;
1049     }
1050