syslink.c 49.3 KB
Newer Older
1 2 3
/*
 * SysLink control
 *
4
 * Copyright 2004 - 2006 Thomas Weidenmueller <w3seek@reactos.com>
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 22 23 24 25 26 27
 * NOTES
 *
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on Apr. 4, 2005, by Dimitrie O. Paun.
 * 
 * Unless otherwise noted, we believe this code to be complete, as per
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
28 29 30 31 32 33 34 35 36 37
 */

#include <stdarg.h>
#include <string.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "commctrl.h"
38
#include "comctl32.h"
39
#include "wine/unicode.h"
40
#include "wine/debug.h"
41
#include "wine/list.h"
42

43
WINE_DEFAULT_DEBUG_CHANNEL(syslink);
44 45 46 47

typedef struct
{
    int nChars;
48
    int nSkip;
49 50 51 52 53 54 55 56 57 58 59 60 61 62
    RECT rc;
} DOC_TEXTBLOCK, *PDOC_TEXTBLOCK;

#define LIF_FLAGSMASK   (LIF_STATE | LIF_ITEMID | LIF_URL)
#define LIS_MASK        (LIS_FOCUSED | LIS_ENABLED | LIS_VISITED)

typedef enum
{
    slText = 0,
    slLink
} SL_ITEM_TYPE;

typedef struct _DOC_ITEM
{
63
    struct list entry;
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
    UINT nText;             /* Number of characters of the text */
    SL_ITEM_TYPE Type;      /* type of the item */
    PDOC_TEXTBLOCK Blocks;  /* Array of text blocks */
    union
    {
        struct
        {
            UINT state;     /* Link state */
            WCHAR *szID;    /* Link ID string */
            WCHAR *szUrl;   /* Link URL string */
        } Link;
        struct
        {
            UINT Dummy;
        } Text;
    } u;
80
    WCHAR Text[1];          /* Text of the document item */
81 82 83 84 85
} DOC_ITEM, *PDOC_ITEM;

typedef struct
{
    HWND      Self;         /* The window handle for this control */
86 87
    HWND      Notify;       /* The parent handle to receive notifications */
    DWORD     Style;        /* Styles for this control */
88
    struct list Items;      /* Document items list */
89 90 91 92 93 94 95
    BOOL      HasFocus;     /* Whether the control has the input focus */
    int       MouseDownID;  /* ID of the link that the mouse button first selected */
    HFONT     Font;         /* Handle to the font for text */
    HFONT     LinkFont;     /* Handle to the font for links */
    COLORREF  TextColor;    /* Color of the text */
    COLORREF  LinkColor;    /* Color of links */
    COLORREF  VisitedColor; /* Color of visited links */
96
    WCHAR     BreakChar;    /* Break Character for the current font */
97
    BOOL      IgnoreReturn; /* (infoPtr->Style & LWS_IGNORERETURN) on creation */
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
} SYSLINK_INFO;

/* Control configuration constants */

#define SL_LEFTMARGIN   (0)
#define SL_TOPMARGIN    (0)
#define SL_RIGHTMARGIN  (0)
#define SL_BOTTOMMARGIN (0)

/***********************************************************************
 * SYSLINK_FreeDocItem
 * Frees all data and gdi objects associated with a document item
 */
static VOID SYSLINK_FreeDocItem (PDOC_ITEM DocItem)
{
    if(DocItem->Type == slLink)
    {
115 116
        Free(DocItem->u.Link.szID);
        Free(DocItem->u.Link.szUrl);
117 118
    }

119 120
    Free(DocItem->Blocks);

121 122 123
    /* we don't free Text because it's just a pointer to a character in the
       entire window text string */

124
    Free(DocItem);
125 126 127 128 129 130
}

/***********************************************************************
 * SYSLINK_AppendDocItem
 * Create and append a new document item.
 */
131
static PDOC_ITEM SYSLINK_AppendDocItem (SYSLINK_INFO *infoPtr, LPCWSTR Text, UINT textlen,
132 133 134
                                        SL_ITEM_TYPE type, PDOC_ITEM LastItem)
{
    PDOC_ITEM Item;
135

136
    textlen = min(textlen, strlenW(Text));
137
    Item = Alloc(FIELD_OFFSET(DOC_ITEM, Text[textlen + 1]));
138 139 140 141 142 143 144 145 146 147
    if(Item == NULL)
    {
        ERR("Failed to alloc DOC_ITEM structure!\n");
        return NULL;
    }

    Item->nText = textlen;
    Item->Type = type;
    Item->Blocks = NULL;
    lstrcpynW(Item->Text, Text, textlen + 1);
148 149 150 151 152
    if (LastItem)
        list_add_after(&LastItem->entry, &Item->entry);
    else
        list_add_tail(&infoPtr->Items, &Item->entry);

153 154 155 156 157 158 159 160 161
    return Item;
}

/***********************************************************************
 * SYSLINK_ClearDoc
 * Clears the document tree
 */
static VOID SYSLINK_ClearDoc (SYSLINK_INFO *infoPtr)
{
162 163 164
    DOC_ITEM *Item, *Item2;

    LIST_FOR_EACH_ENTRY_SAFE(Item, Item2, &infoPtr->Items, DOC_ITEM, entry)
165
    {
166
        list_remove(&Item->entry);
167 168 169 170 171 172 173 174 175
        SYSLINK_FreeDocItem(Item);
    }
}

/***********************************************************************
 * SYSLINK_ParseText
 * Parses the window text string and creates a document. Returns the
 * number of document items created.
 */
176
static UINT SYSLINK_ParseText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
177
{
178 179 180 181
    static const WCHAR SL_LINKOPEN[] =  { '<','a' };
    static const WCHAR SL_HREF[] =      { 'h','r','e','f','=','\"' };
    static const WCHAR SL_ID[] =        { 'i','d','=','\"' };
    static const WCHAR SL_LINKCLOSE[] = { '<','/','a','>' };
182 183
    LPCWSTR current, textstart = NULL, linktext = NULL, firsttag = NULL;
    int taglen = 0, textlen = 0, linklen = 0, docitems = 0;
184 185
    PDOC_ITEM Last = NULL;
    SL_ITEM_TYPE CurrentType = slText;
186
    LPCWSTR lpID, lpUrl;
187 188
    UINT lenId, lenUrl;

189 190
    TRACE("(%p %s)\n", infoPtr, debugstr_w(Text));

191
    for(current = Text; *current != 0;)
192 193 194
    {
        if(*current == '<')
        {
195
            if(!strncmpiW(current, SL_LINKOPEN, sizeof(SL_LINKOPEN)/sizeof(SL_LINKOPEN[0])) && (CurrentType == slText))
196 197 198
            {
                BOOL ValidParam = FALSE, ValidLink = FALSE;

199
                if(*(current + 2) == '>')
200 201 202 203 204 205 206 207 208
                {
                    /* we just have to deal with a <a> tag */
                    taglen = 3;
                    ValidLink = TRUE;
                    ValidParam = TRUE;
                    firsttag = current;
                    linklen = 0;
                    lpID = NULL;
                    lpUrl = NULL;
209 210
                }
                else if(*(current + 2) == infoPtr->BreakChar)
211 212
                {
                    /* we expect parameters, parse them */
213
                    LPCWSTR *CurrentParameter = NULL, tmp;
214 215 216 217 218 219 220 221 222
                    UINT *CurrentParameterLen = NULL;

                    taglen = 3;
                    tmp = current + taglen;
                    lpID = NULL;
                    lpUrl = NULL;
                    
CheckParameter:
                    /* compare the current position with all known parameters */
223
                    if(!strncmpiW(tmp, SL_HREF, sizeof(SL_HREF)/sizeof(SL_HREF[0])))
224 225 226 227 228 229
                    {
                        taglen += 6;
                        ValidParam = TRUE;
                        CurrentParameter = &lpUrl;
                        CurrentParameterLen = &lenUrl;
                    }
230
                    else if(!strncmpiW(tmp, SL_ID, sizeof(SL_ID)/sizeof(SL_ID[0])))
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
                    {
                        taglen += 4;
                        ValidParam = TRUE;
                        CurrentParameter = &lpID;
                        CurrentParameterLen = &lenId;
                    }
                    else
                    {
                        ValidParam = FALSE;
                    }
                    
                    if(ValidParam)
                    {
                        /* we got a known parameter, now search until the next " character.
                           If we can't find a " character, there's a syntax error and we just assume it's text */
                        ValidParam = FALSE;
                        *CurrentParameter = current + taglen;
                        *CurrentParameterLen = 0;

                        for(tmp = *CurrentParameter; *tmp != 0; tmp++)
                        {
                            taglen++;
                            if(*tmp == '\"')
                            {
                                ValidParam = TRUE;
                                tmp++;
                                break;
                            }
                            (*CurrentParameterLen)++;
                        }
                    }
                    if(ValidParam)
                    {
                        /* we're done with this parameter, now there are only 2 possibilities:
                         * 1. another parameter is coming, so expect a ' ' (space) character
                         * 2. the tag is being closed, so expect a '<' character
                         */
268
                        if(*tmp == infoPtr->BreakChar)
269 270 271 272 273
                        {
                            /* we expect another parameter, do the whole thing again */
                            taglen++;
                            tmp++;
                            goto CheckParameter;
274 275 276
                        }
                        else if(*tmp == '>')
                        {
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
                            /* the tag is being closed, we're done */
                            ValidLink = TRUE;
                            taglen++;
                        }
                    }
                }
                
                if(ValidLink && ValidParam)
                {
                    /* the <a ...> tag appears to be valid. save all information
                       so we can add the link if we find a valid </a> tag later */
                    CurrentType = slLink;
                    linktext = current + taglen;
                    linklen = 0;
                    firsttag = current;
                }
                else
                {
                    taglen = 1;
                    lpID = NULL;
                    lpUrl = NULL;
                    if(textstart == NULL)
                    {
                        textstart = current;
                    }
                }
            }
304
            else if(!strncmpiW(current, SL_LINKCLOSE, sizeof(SL_LINKCLOSE)/sizeof(SL_LINKCLOSE[0])) && (CurrentType == slLink) && firsttag)
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
            {
                /* there's a <a...> tag opened, first add the previous text, if present */
                if(textstart != NULL && textlen > 0 && firsttag > textstart)
                {
                    Last = SYSLINK_AppendDocItem(infoPtr, textstart, firsttag - textstart, slText, Last);
                    if(Last == NULL)
                    {
                        ERR("Unable to create new document item!\n");
                        return docitems;
                    }
                    docitems++;
                    textstart = NULL;
                    textlen = 0;
                }
                
                /* now it's time to add the link to the document */
                current += 4;
                if(linktext != NULL && linklen > 0)
                {
                    Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slLink, Last);
                    if(Last == NULL)
                    {
                        ERR("Unable to create new document item!\n");
                        return docitems;
                    }
                    docitems++;
                    if(CurrentType == slLink)
                    {
                        int nc;

335
                        if(!(infoPtr->Style & WS_DISABLED))
336 337 338 339 340 341
                        {
                            Last->u.Link.state |= LIS_ENABLED;
                        }
                        /* Copy the tag parameters */
                        if(lpID != NULL)
                        {
342
                            nc = min(lenId, strlenW(lpID));
343 344
                            nc = min(nc, MAX_LINKID_TEXT - 1);
                            Last->u.Link.szID = Alloc((nc + 1) * sizeof(WCHAR));
345 346 347 348 349 350 351 352 353
                            if(Last->u.Link.szID != NULL)
                            {
                                lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
                            }
                        }
                        else
                            Last->u.Link.szID = NULL;
                        if(lpUrl != NULL)
                        {
354
                            nc = min(lenUrl, strlenW(lpUrl));
355 356
                            nc = min(nc, L_MAX_URL_LENGTH - 1);
                            Last->u.Link.szUrl = Alloc((nc + 1) * sizeof(WCHAR));
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
                            if(Last->u.Link.szUrl != NULL)
                            {
                                lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
                            }
                        }
                        else
                            Last->u.Link.szUrl = NULL;
                    }
                    linktext = NULL;
                }
                CurrentType = slText;
                firsttag = NULL;
                textstart = NULL;
                continue;
            }
            else
            {
                /* we don't know what tag it is, so just continue */
                taglen = 1;
                linklen++;
                if(CurrentType == slText && textstart == NULL)
                {
                    textstart = current;
                }
            }
            
            textlen += taglen;
            current += taglen;
        }
        else
        {
            textlen++;
            linklen++;

            /* save the pointer of the current text item if we couldn't find a tag */
            if(textstart == NULL && CurrentType == slText)
            {
                textstart = current;
            }
            
            current++;
        }
    }
    
    if(textstart != NULL && textlen > 0)
    {
        Last = SYSLINK_AppendDocItem(infoPtr, textstart, textlen, CurrentType, Last);
        if(Last == NULL)
        {
            ERR("Unable to create new document item!\n");
            return docitems;
        }
        if(CurrentType == slLink)
        {
            int nc;

413
            if(!(infoPtr->Style & WS_DISABLED))
414 415 416 417 418 419
            {
                Last->u.Link.state |= LIS_ENABLED;
            }
            /* Copy the tag parameters */
            if(lpID != NULL)
            {
420
                nc = min(lenId, strlenW(lpID));
421 422
                nc = min(nc, MAX_LINKID_TEXT - 1);
                Last->u.Link.szID = Alloc((nc + 1) * sizeof(WCHAR));
423 424 425 426 427 428 429 430 431
                if(Last->u.Link.szID != NULL)
                {
                    lstrcpynW(Last->u.Link.szID, lpID, nc + 1);
                }
            }
            else
                Last->u.Link.szID = NULL;
            if(lpUrl != NULL)
            {
432
                nc = min(lenUrl, strlenW(lpUrl));
433 434
                nc = min(nc, L_MAX_URL_LENGTH - 1);
                Last->u.Link.szUrl = Alloc((nc + 1) * sizeof(WCHAR));
435 436 437 438 439 440 441 442 443 444 445 446 447
                if(Last->u.Link.szUrl != NULL)
                {
                    lstrcpynW(Last->u.Link.szUrl, lpUrl, nc + 1);
                }
            }
            else
                Last->u.Link.szUrl = NULL;
        }
        docitems++;
    }

    if(linktext != NULL && linklen > 0)
    {
448
        /* we got an unclosed link, just display the text */
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
        Last = SYSLINK_AppendDocItem(infoPtr, linktext, linklen, slText, Last);
        if(Last == NULL)
        {
            ERR("Unable to create new document item!\n");
            return docitems;
        }
        docitems++;
    }

    return docitems;
}

/***********************************************************************
 * SYSLINK_RepaintLink
 * Repaints a link.
 */
465
static VOID SYSLINK_RepaintLink (const SYSLINK_INFO *infoPtr, const DOC_ITEM *DocItem)
466
{
467 468 469
    PDOC_TEXTBLOCK bl;
    int n;

470 471 472 473 474 475
    if(DocItem->Type != slLink)
    {
        ERR("DocItem not a link!\n");
        return;
    }
    
476 477
    bl = DocItem->Blocks;
    if (bl != NULL)
478
    {
479 480 481 482 483 484 485 486
        n = DocItem->nText;
        
        while(n > 0)
        {
            InvalidateRect(infoPtr->Self, &bl->rc, TRUE);
            n -= bl->nChars + bl->nSkip;
            bl++;
        }
487 488 489 490 491
    }
}

/***********************************************************************
 * SYSLINK_GetLinkItemByIndex
492
 * Retrieves a document link by its index
493
 */
494
static PDOC_ITEM SYSLINK_GetLinkItemByIndex (const SYSLINK_INFO *infoPtr, int iLink)
495
{
496
    DOC_ITEM *Current;
497

498
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
499
    {
500
        if ((Current->Type == slLink) && (iLink-- <= 0))
501 502 503 504 505 506 507
            return Current;
    }
    return NULL;
}

/***********************************************************************
 * SYSLINK_GetFocusLink
508
 * Retrieves the link that has the LIS_FOCUSED bit
509
 */
510
static PDOC_ITEM SYSLINK_GetFocusLink (const SYSLINK_INFO *infoPtr, int *LinkId)
511
{
512
    DOC_ITEM *Current;
513 514
    int id = 0;

515
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
516
    {
517
        if(Current->Type == slLink)
518 519 520 521 522 523 524 525 526 527
        {
            if(Current->u.Link.state & LIS_FOCUSED)
            {
                if(LinkId != NULL)
                    *LinkId = id;
                return Current;
            }
            id++;
        }
    }
528

529 530 531 532 533 534 535
    return NULL;
}

/***********************************************************************
 * SYSLINK_GetNextLink
 * Gets the next link
 */
536
static PDOC_ITEM SYSLINK_GetNextLink (const SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
537
{
538 539 540
    DOC_ITEM *Next;

    LIST_FOR_EACH_ENTRY(Next, Current ? &Current->entry : &infoPtr->Items, DOC_ITEM, entry)
541
    {
542
        if (Next->Type == slLink)
543
        {
544
            return Next;
545 546 547 548 549 550 551 552 553
        }
    }
    return NULL;
}

/***********************************************************************
 * SYSLINK_GetPrevLink
 * Gets the previous link
 */
554
static PDOC_ITEM SYSLINK_GetPrevLink (const SYSLINK_INFO *infoPtr, PDOC_ITEM Current)
555
{
556 557 558
    DOC_ITEM *Prev;

    LIST_FOR_EACH_ENTRY_REV(Prev, Current ? &Current->entry : list_tail(&infoPtr->Items), DOC_ITEM, entry)
559
    {
560
        if (Prev->Type == slLink)
561
        {
562
            return Prev;
563 564
        }
    }
565 566

    return NULL;
567 568 569 570 571 572
}

/***********************************************************************
 * SYSLINK_WrapLine
 * Tries to wrap a line.
 */
573
static BOOL SYSLINK_WrapLine (LPWSTR Text, WCHAR BreakChar, int x, int *LineLen,
574
                             int nFit, LPSIZE Extent)
575
{
576
    int i;
577

578
    for (i = 0; i < nFit; i++) if (Text[i] == '\n') break;
579

580
    if (i == *LineLen) return FALSE;
581 582

    /* check if we're in the middle of a word */
583
    if (Text[i] != '\n' && Text[i] != BreakChar)
584 585
    {
        /* search for the beginning of the word */
586 587 588
        while (i && Text[i - 1] != BreakChar) i--;

        if (i == 0)
589 590 591
        {
            Extent->cx = 0;
            Extent->cy = 0;
592
            if (x == SL_LEFTMARGIN) i = max( nFit, 1 );
593 594
        }
    }
595
    *LineLen = i;
596 597 598 599 600 601 602
    return TRUE;
}

/***********************************************************************
 * SYSLINK_Render
 * Renders the document in memory
 */
603
static VOID SYSLINK_Render (const SYSLINK_INFO *infoPtr, HDC hdc, PRECT pRect)
604 605 606 607 608
{
    RECT rc;
    PDOC_ITEM Current;
    HGDIOBJ hOldFont;
    int x, y, LineHeight;
609
    SIZE szDoc;
610
    TEXTMETRICW tm;
611 612 613 614

    szDoc.cx = szDoc.cy = 0;

    rc = *pRect;
615 616
    rc.right -= SL_RIGHTMARGIN;
    rc.bottom -= SL_BOTTOMMARGIN;
617 618 619 620 621

    if(rc.right - SL_LEFTMARGIN < 0)
        rc.right = MAXLONG;
    if (rc.bottom - SL_TOPMARGIN < 0)
        rc.bottom = MAXLONG;
622 623 624 625 626
    
    hOldFont = SelectObject(hdc, infoPtr->Font);
    
    x = SL_LEFTMARGIN;
    y = SL_TOPMARGIN;
627 628 629
    GetTextMetricsW( hdc, &tm );
    LineHeight = tm.tmHeight + tm.tmExternalLeading;

630
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
631 632 633 634 635 636
    {
        int n, nBlocks;
        LPWSTR tx;
        PDOC_TEXTBLOCK bl, cbl;
        INT nFit;
        SIZE szDim;
637
        int SkipChars = 0;
638

639 640 641 642
        if(Current->nText == 0)
        {
            continue;
        }
643

644 645
        tx = Current->Text;
        n = Current->nText;
646

647 648
        Free(Current->Blocks);
        Current->Blocks = NULL;
649
        bl = NULL;
650
        nBlocks = 0;
651

652 653 654 655 656 657 658 659 660 661 662
        if(Current->Type == slText)
        {
            SelectObject(hdc, infoPtr->Font);
        }
        else if(Current->Type == slLink)
        {
            SelectObject(hdc, infoPtr->LinkFont);
        }
        
        while(n > 0)
        {
663 664 665
            /* skip break characters unless they're the first of the doc item */
            if(tx != Current->Text || x == SL_LEFTMARGIN)
            {
666 667 668 669 670 671
                if (n && *tx == '\n')
                {
                    tx++;
                    SkipChars++;
                    n--;
                }
672 673 674 675 676 677 678 679 680 681
                while(n > 0 && (*tx) == infoPtr->BreakChar)
                {
                    tx++;
                    SkipChars++;
                    n--;
                }
            }

            if((n == 0 && SkipChars != 0) ||
               GetTextExtentExPointW(hdc, tx, n, rc.right - x, &nFit, NULL, &szDim))
682 683
            {
                int LineLen = n;
684
                BOOL Wrap = FALSE;
685
                PDOC_TEXTBLOCK nbl;
686
                
687
                if(n != 0)
688
                {
689
                    Wrap = SYSLINK_WrapLine(tx, infoPtr->BreakChar, x, &LineLen, nFit, &szDim);
690 691

                    if(LineLen == 0)
692
                    {
693 694 695 696
                        /* move one line down, the word didn't fit into the line */
                        x = SL_LEFTMARGIN;
                        y += LineHeight;
                        continue;
697
                    }
698 699

                    if(LineLen != n)
700
                    {
701 702 703 704 705 706
                        if(!GetTextExtentExPointW(hdc, tx, LineLen, rc.right - x, NULL, NULL, &szDim))
                        {
                            if(bl != NULL)
                            {
                                Free(bl);
                                bl = NULL;
707
                                nBlocks = 0;
708 709 710
                            }
                            break;
                        }
711 712 713
                    }
                }
                
714 715
                nbl = ReAlloc(bl, (nBlocks + 1) * sizeof(DOC_TEXTBLOCK));
                if (nbl != NULL)
716
                {
717 718 719
                    bl = nbl;
                    nBlocks++;

720 721 722
                    cbl = bl + nBlocks - 1;
                    
                    cbl->nChars = LineLen;
723
                    cbl->nSkip = SkipChars;
724
                    SetRect(&cbl->rc, x, y, x + szDim.cx, y + szDim.cy);
725 726 727 728 729 730

                    if (cbl->rc.right > szDoc.cx)
                        szDoc.cx = cbl->rc.right;
                    if (cbl->rc.bottom > szDoc.cy)
                        szDoc.cy = cbl->rc.bottom;

731
                    if(LineLen != 0)
732
                    {
733 734
                        x += szDim.cx;
                        if(Wrap)
735
                        {
736 737
                            x = SL_LEFTMARGIN;
                            y += LineHeight;
738 739 740 741 742
                        }
                    }
                }
                else
                {
743 744 745 746
                    Free(bl);
                    bl = NULL;
                    nBlocks = 0;

747 748 749 750 751
                    ERR("Failed to alloc DOC_TEXTBLOCK structure!\n");
                    break;
                }
                n -= LineLen;
                tx += LineLen;
752
                SkipChars = 0;
753 754 755 756 757 758
            }
            else
            {
                n--;
            }
        }
759 760 761 762 763

        if(nBlocks != 0)
        {
            Current->Blocks = bl;
        }
764 765 766
    }
    
    SelectObject(hdc, hOldFont);
767 768 769

    pRect->right = pRect->left + szDoc.cx;
    pRect->bottom = pRect->top + szDoc.cy;
770 771 772 773 774 775
}

/***********************************************************************
 * SYSLINK_Draw
 * Draws the SysLink control.
 */
776
static LRESULT SYSLINK_Draw (const SYSLINK_INFO *infoPtr, HDC hdc)
777 778 779 780 781
{
    RECT rc;
    PDOC_ITEM Current;
    HFONT hOldFont;
    COLORREF OldTextColor, OldBkColor;
782
    HBRUSH hBrush;
783 784
    UINT text_flags = ETO_CLIPPED;
    UINT mode = GetBkMode( hdc );
785 786 787

    hOldFont = SelectObject(hdc, infoPtr->Font);
    OldTextColor = SetTextColor(hdc, infoPtr->TextColor);
788 789
    OldBkColor = SetBkColor(hdc, comctl32_color.clrWindow);

790 791 792 793 794 795
    GetClientRect(infoPtr->Self, &rc);
    rc.right -= SL_RIGHTMARGIN + SL_LEFTMARGIN;
    rc.bottom -= SL_BOTTOMMARGIN + SL_TOPMARGIN;

    if(rc.right < 0 || rc.bottom < 0) return 0;

796 797
    hBrush = (HBRUSH)SendMessageW(infoPtr->Notify, WM_CTLCOLORSTATIC,
                                  (WPARAM)hdc, (LPARAM)infoPtr->Self);
798 799 800 801 802 803 804
    if (!(infoPtr->Style & LWS_TRANSPARENT))
    {
        FillRect(hdc, &rc, hBrush);
        if (GetBkMode( hdc ) == OPAQUE) text_flags |= ETO_OPAQUE;
    }
    else SetBkMode( hdc, TRANSPARENT );

805 806
    DeleteObject(hBrush);

807
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
    {
        int n;
        LPWSTR tx;
        PDOC_TEXTBLOCK bl;
        
        bl = Current->Blocks;
        if(bl != NULL)
        {
            tx = Current->Text;
            n = Current->nText;

            if(Current->Type == slText)
            {
                 SelectObject(hdc, infoPtr->Font);
                 SetTextColor(hdc, infoPtr->TextColor);
            }
            else
            {
                 SelectObject(hdc, infoPtr->LinkFont);
                 SetTextColor(hdc, (!(Current->u.Link.state & LIS_VISITED) ? infoPtr->LinkColor : infoPtr->VisitedColor));
            }

            while(n > 0)
            {
832
                tx += bl->nSkip;
833
                ExtTextOutW(hdc, bl->rc.left, bl->rc.top, text_flags, &bl->rc, tx, bl->nChars, NULL);
834 835
                if((Current->Type == slLink) && (Current->u.Link.state & LIS_FOCUSED) && infoPtr->HasFocus)
                {
836 837
                    COLORREF PrevTextColor;
                    PrevTextColor = SetTextColor(hdc, infoPtr->TextColor);
838
                    DrawFocusRect(hdc, &bl->rc);
839
                    SetTextColor(hdc, PrevTextColor);
840 841
                }
                tx += bl->nChars;
842
                n -= bl->nChars + bl->nSkip;
843 844 845 846 847 848 849 850
                bl++;
            }
        }
    }

    SetBkColor(hdc, OldBkColor);
    SetTextColor(hdc, OldTextColor);
    SelectObject(hdc, hOldFont);
851
    SetBkMode(hdc, mode);
852 853 854 855 856 857 858 859
    return 0;
}


/***********************************************************************
 * SYSLINK_Paint
 * Handles the WM_PAINT message.
 */
860
static LRESULT SYSLINK_Paint (const SYSLINK_INFO *infoPtr, HDC hdcParam)
861 862 863
{
    HDC hdc;
    PAINTSTRUCT ps;
864 865

    hdc = hdcParam ? hdcParam : BeginPaint (infoPtr->Self, &ps);
866 867 868 869 870
    if (hdc)
    {
        SYSLINK_Draw (infoPtr, hdc);
        if (!hdcParam) EndPaint (infoPtr->Self, &ps);
    }
871 872 873 874 875 876 877 878 879 880 881
    return 0;
}

/***********************************************************************
 *           SYSLINK_SetFont
 * Set new Font for the SysLink control.
 */
static HFONT SYSLINK_SetFont (SYSLINK_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
{
    HDC hdc;
    LOGFONTW lf;
882
    TEXTMETRICW tm;
883
    RECT rcClient;
884 885 886 887 888 889 890 891 892 893 894
    HFONT hOldFont = infoPtr->Font;
    infoPtr->Font = hFont;
    
    /* free the underline font */
    if(infoPtr->LinkFont != NULL)
    {
        DeleteObject(infoPtr->LinkFont);
        infoPtr->LinkFont = NULL;
    }

    /* Render text position and word wrapping in memory */
895
    if (GetClientRect(infoPtr->Self, &rcClient))
896
    {
897 898
        hdc = GetDC(infoPtr->Self);
        if(hdc != NULL)
899
        {
900 901 902 903 904 905 906 907 908 909 910 911
            /* create a new underline font */
            if(GetTextMetricsW(hdc, &tm) &&
               GetObjectW(infoPtr->Font, sizeof(LOGFONTW), &lf))
            {
                lf.lfUnderline = TRUE;
                infoPtr->LinkFont = CreateFontIndirectW(&lf);
                infoPtr->BreakChar = tm.tmBreakChar;
            }
            else
            {
                ERR("Failed to create link font!\n");
            }
912

913 914 915
            SYSLINK_Render(infoPtr, hdc, &rcClient);
            ReleaseDC(infoPtr->Self, hdc);
        }
916 917 918 919 920 921 922 923 924 925 926 927 928 929
    }
    
    if(bRedraw)
    {
        RedrawWindow(infoPtr->Self, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
    }
    
    return hOldFont;
}

/***********************************************************************
 *           SYSLINK_SetText
 * Set new text for the SysLink control.
 */
930
static LRESULT SYSLINK_SetText (SYSLINK_INFO *infoPtr, LPCWSTR Text)
931 932 933
{
    /* clear the document */
    SYSLINK_ClearDoc(infoPtr);
934 935

    if(Text == NULL || *Text == 0)
936 937 938
    {
        return TRUE;
    }
939

940 941 942
    /* let's parse the string and create a document */
    if(SYSLINK_ParseText(infoPtr, Text) > 0)
    {
943 944
        RECT rcClient;

945
        /* Render text position and word wrapping in memory */
946
        if (GetClientRect(infoPtr->Self, &rcClient))
947
        {
948 949 950 951 952
            HDC hdc = GetDC(infoPtr->Self);
            if (hdc != NULL)
            {
                SYSLINK_Render(infoPtr, hdc, &rcClient);
                ReleaseDC(infoPtr->Self, hdc);
953

954 955
                InvalidateRect(infoPtr->Self, NULL, TRUE);
            }
956
        }
957 958 959 960 961 962 963 964 965 966 967
    }
    
    return TRUE;
}

/***********************************************************************
 *           SYSLINK_SetFocusLink
 * Updates the focus status bits and focusses the specified link.
 * If no document item is specified, the focus bit will be removed from all links.
 * Returns the previous focused item.
 */
968
static PDOC_ITEM SYSLINK_SetFocusLink (const SYSLINK_INFO *infoPtr, const DOC_ITEM *DocItem)
969 970
{
    PDOC_ITEM Current, PrevFocus = NULL;
971 972

    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
    {
        if(Current->Type == slLink)
        {
            if((PrevFocus == NULL) && (Current->u.Link.state & LIS_FOCUSED))
            {
                PrevFocus = Current;
            }
            
            if(Current == DocItem)
            {
                Current->u.Link.state |= LIS_FOCUSED;
            }
            else
            {
                Current->u.Link.state &= ~LIS_FOCUSED;
            }
        }
    }
    
    return PrevFocus;
}

/***********************************************************************
 *           SYSLINK_SetItem
 * Sets the states and attributes of a link item.
 */
999
static LRESULT SYSLINK_SetItem (const SYSLINK_INFO *infoPtr, const LITEM *Item)
1000 1001
{
    PDOC_ITEM di;
1002 1003 1004
    int nc;
    PWSTR szId = NULL;
    PWSTR szUrl = NULL;
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
    BOOL Repaint = FALSE;

    if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
    {
        ERR("Invalid Flags!\n");
        return FALSE;
    }

    di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
    if(di == NULL)
    {
        ERR("Link %d couldn't be found\n", Item->iLink);
        return FALSE;
    }

    if(Item->mask & LIF_ITEMID)
    {
1022 1023 1024
        nc = min(lstrlenW(Item->szID), MAX_LINKID_TEXT - 1);
        szId = Alloc((nc + 1) * sizeof(WCHAR));
        if(szId)
1025
        {
1026
            lstrcpynW(szId, Item->szID, nc + 1);
1027
        }
1028
        else
1029
        {
1030 1031
            ERR("Unable to allocate memory for link id\n");
            return FALSE;
1032 1033 1034 1035 1036
        }
    }

    if(Item->mask & LIF_URL)
    {
1037 1038 1039
        nc = min(lstrlenW(Item->szUrl), L_MAX_URL_LENGTH - 1);
        szUrl = Alloc((nc + 1) * sizeof(WCHAR));
        if(szUrl)
1040
        {
1041 1042 1043 1044
            lstrcpynW(szUrl, Item->szUrl, nc + 1);
        }
        else
        {
1045
            Free(szId);
1046

1047 1048 1049 1050 1051 1052 1053
            ERR("Unable to allocate memory for link url\n");
            return FALSE;
        }
    }

    if(Item->mask & LIF_ITEMID)
    {
1054
        Free(di->u.Link.szID);
1055 1056 1057 1058 1059
        di->u.Link.szID = szId;
    }

    if(Item->mask & LIF_URL)
    {
1060
        Free(di->u.Link.szUrl);
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
        di->u.Link.szUrl = szUrl;
    }

    if(Item->mask & LIF_STATE)
    {
        UINT oldstate = di->u.Link.state;
        /* clear the masked bits */
        di->u.Link.state &= ~(Item->stateMask & LIS_MASK);
        /* copy the bits */
        di->u.Link.state |= (Item->state & Item->stateMask) & LIS_MASK;
        Repaint = (oldstate != di->u.Link.state);
        
        /* update the focus */
        SYSLINK_SetFocusLink(infoPtr, ((di->u.Link.state & LIS_FOCUSED) ? di : NULL));
1075 1076 1077 1078 1079 1080 1081
    }
    
    if(Repaint)
    {
        SYSLINK_RepaintLink(infoPtr, di);
    }
    
1082
    return TRUE;
1083 1084 1085 1086 1087 1088
}

/***********************************************************************
 *           SYSLINK_GetItem
 * Retrieves the states and attributes of a link item.
 */
1089
static LRESULT SYSLINK_GetItem (const SYSLINK_INFO *infoPtr, PLITEM Item)
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
{
    PDOC_ITEM di;
    
    if(!(Item->mask & LIF_ITEMINDEX) || !(Item->mask & (LIF_FLAGSMASK)))
    {
        ERR("Invalid Flags!\n");
        return FALSE;
    }
    
    di = SYSLINK_GetLinkItemByIndex(infoPtr, Item->iLink);
    if(di == NULL)
    {
        ERR("Link %d couldn't be found\n", Item->iLink);
        return FALSE;
    }
    
    if(Item->mask & LIF_STATE)
    {
        Item->state = (di->u.Link.state & Item->stateMask);
        if(!infoPtr->HasFocus)
        {
            /* remove the LIS_FOCUSED bit if the control doesn't have focus */
            Item->state &= ~LIS_FOCUSED;
        }
    }
    
    if(Item->mask & LIF_ITEMID)
    {
        if(di->u.Link.szID)
        {
1120
            lstrcpyW(Item->szID, di->u.Link.szID);
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
        }
        else
        {
            Item->szID[0] = 0;
        }
    }
    
    if(Item->mask & LIF_URL)
    {
        if(di->u.Link.szUrl)
        {
1132
            lstrcpyW(Item->szUrl, di->u.Link.szUrl);
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
        }
        else
        {
            Item->szUrl[0] = 0;
        }
    }
    
    return TRUE;
}

1143 1144 1145 1146
/***********************************************************************
 *           SYSLINK_PtInDocItem
 * Determines if a point is in the region of a document item
 */
1147
static BOOL SYSLINK_PtInDocItem (const DOC_ITEM *DocItem, POINT pt)
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
{
    PDOC_TEXTBLOCK bl;
    int n;

    bl = DocItem->Blocks;
    if (bl != NULL)
    {
        n = DocItem->nText;

        while(n > 0)
        {
            if (PtInRect(&bl->rc, pt))
            {
                return TRUE;
            }
            n -= bl->nChars + bl->nSkip;
            bl++;
        }
    }
    
    return FALSE;
}

1171 1172 1173 1174
/***********************************************************************
 *           SYSLINK_HitTest
 * Determines the link the user clicked on.
 */
1175
static LRESULT SYSLINK_HitTest (const SYSLINK_INFO *infoPtr, PLHITTESTINFO HitTest)
1176 1177 1178 1179
{
    PDOC_ITEM Current;
    int id = 0;

1180
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
1181 1182 1183
    {
        if(Current->Type == slLink)
        {
1184
            if(SYSLINK_PtInDocItem(Current, HitTest->pt))
1185 1186 1187 1188 1189 1190 1191
            {
                HitTest->item.mask = 0;
                HitTest->item.iLink = id;
                HitTest->item.state = 0;
                HitTest->item.stateMask = 0;
                if(Current->u.Link.szID)
                {
1192
                    lstrcpyW(HitTest->item.szID, Current->u.Link.szID);
1193 1194 1195 1196 1197 1198 1199
                }
                else
                {
                    HitTest->item.szID[0] = 0;
                }
                if(Current->u.Link.szUrl)
                {
1200
                    lstrcpyW(HitTest->item.szUrl, Current->u.Link.szUrl);
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
                }
                else
                {
                    HitTest->item.szUrl[0] = 0;
                }
                return TRUE;
            }
            id++;
        }
    }
    
    return FALSE;
}

/***********************************************************************
 *           SYSLINK_GetIdealHeight
 * Returns the preferred height of a link at the current control's width.
 */
1219
static LRESULT SYSLINK_GetIdealHeight (const SYSLINK_INFO *infoPtr)
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247
{
    HDC hdc = GetDC(infoPtr->Self);
    if(hdc != NULL)
    {
        LRESULT height;
        TEXTMETRICW tm;
        HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);
        
        if(GetTextMetricsW(hdc, &tm))
        {
            height = tm.tmHeight;
        }
        else
        {
            height = 0;
        }
        SelectObject(hdc, hOldFont);
        ReleaseDC(infoPtr->Self, hdc);
        
        return height;
    }
    return 0;
}

/***********************************************************************
 *           SYSLINK_SendParentNotify
 * Sends a WM_NOTIFY message to the parent window.
 */
1248
static LRESULT SYSLINK_SendParentNotify (const SYSLINK_INFO *infoPtr, UINT code, const DOC_ITEM *Link, int iLink)
1249 1250 1251 1252
{
    NMLINK nml;

    nml.hdr.hwndFrom = infoPtr->Self;
1253
    nml.hdr.idFrom = GetWindowLongPtrW(infoPtr->Self, GWLP_ID);
1254 1255 1256 1257 1258 1259 1260 1261
    nml.hdr.code = code;

    nml.item.mask = 0;
    nml.item.iLink = iLink;
    nml.item.state = 0;
    nml.item.stateMask = 0;
    if(Link->u.Link.szID)
    {
1262
        lstrcpyW(nml.item.szID, Link->u.Link.szID);
1263 1264 1265 1266 1267 1268 1269
    }
    else
    {
        nml.item.szID[0] = 0;
    }
    if(Link->u.Link.szUrl)
    {
1270
        lstrcpyW(nml.item.szUrl, Link->u.Link.szUrl);
1271 1272 1273 1274 1275 1276
    }
    else
    {
        nml.item.szUrl[0] = 0;
    }

1277
    return SendMessageW(infoPtr->Notify, WM_NOTIFY, nml.hdr.idFrom, (LPARAM)&nml);
1278 1279 1280 1281 1282 1283
}

/***********************************************************************
 *           SYSLINK_SetFocus
 * Handles receiving the input focus.
 */
1284
static LRESULT SYSLINK_SetFocus (SYSLINK_INFO *infoPtr)
1285 1286 1287 1288 1289
{
    PDOC_ITEM Focus;
    
    infoPtr->HasFocus = TRUE;

1290 1291
    /* We always select the first link, even if we activated the control using
       SHIFT+TAB. This is the default behavior */
1292 1293 1294 1295
    Focus = SYSLINK_GetNextLink(infoPtr, NULL);
    if(Focus != NULL)
    {
        SYSLINK_SetFocusLink(infoPtr, Focus);
1296
        SYSLINK_RepaintLink(infoPtr, Focus);
1297 1298 1299 1300 1301 1302 1303 1304
    }
    return 0;
}

/***********************************************************************
 *           SYSLINK_KillFocus
 * Handles losing the input focus.
 */
1305
static LRESULT SYSLINK_KillFocus (SYSLINK_INFO *infoPtr)
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323
{
    PDOC_ITEM Focus;
    
    infoPtr->HasFocus = FALSE;
    Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
    
    if(Focus != NULL)
    {
        SYSLINK_RepaintLink(infoPtr, Focus);
    }

    return 0;
}

/***********************************************************************
 *           SYSLINK_LinkAtPt
 * Returns a link at the specified position
 */
1324
static PDOC_ITEM SYSLINK_LinkAtPt (const SYSLINK_INFO *infoPtr, const POINT *pt, int *LinkId, BOOL MustBeEnabled)
1325 1326 1327 1328
{
    PDOC_ITEM Current;
    int id = 0;

1329
    LIST_FOR_EACH_ENTRY(Current, &infoPtr->Items, DOC_ITEM, entry)
1330
    {
1331
        if((Current->Type == slLink) && SYSLINK_PtInDocItem(Current, *pt) &&
1332
           (!MustBeEnabled || (Current->u.Link.state & LIS_ENABLED)))
1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
        {
            if(LinkId != NULL)
            {
                *LinkId = id;
            }
            return Current;
        }
        id++;
    }

    return NULL;
}

/***********************************************************************
 *           SYSLINK_LButtonDown
 * Handles mouse clicks
 */
1350
static LRESULT SYSLINK_LButtonDown (SYSLINK_INFO *infoPtr, const POINT *pt)
1351 1352 1353 1354 1355 1356 1357
{
    PDOC_ITEM Current, Old;
    int id;

    Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
    if(Current != NULL)
    {
1358 1359
      SetFocus(infoPtr->Self);

1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
      Old = SYSLINK_SetFocusLink(infoPtr, Current);
      if(Old != NULL && Old != Current)
      {
          SYSLINK_RepaintLink(infoPtr, Old);
      }
      infoPtr->MouseDownID = id;
      SYSLINK_RepaintLink(infoPtr, Current);
    }

    return 0;
}

/***********************************************************************
 *           SYSLINK_LButtonUp
 * Handles mouse clicks
 */
1376
static LRESULT SYSLINK_LButtonUp (SYSLINK_INFO *infoPtr, const POINT *pt)
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
{
    if(infoPtr->MouseDownID > -1)
    {
        PDOC_ITEM Current;
        int id;
        
        Current = SYSLINK_LinkAtPt(infoPtr, pt, &id, TRUE);
        if((Current != NULL) && (Current->u.Link.state & LIS_FOCUSED) && (infoPtr->MouseDownID == id))
        {
            SYSLINK_SendParentNotify(infoPtr, NM_CLICK, Current, id);
        }
    }

    infoPtr->MouseDownID = -1;

    return 0;
}

/***********************************************************************
 *           SYSLINK_OnEnter
 * Handles ENTER key events
 */
1399
static BOOL SYSLINK_OnEnter (const SYSLINK_INFO *infoPtr)
1400
{
1401
    if(infoPtr->HasFocus && !infoPtr->IgnoreReturn)
1402 1403 1404 1405 1406
    {
        PDOC_ITEM Focus;
        int id;
        
        Focus = SYSLINK_GetFocusLink(infoPtr, &id);
1407
        if(Focus)
1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419
        {
            SYSLINK_SendParentNotify(infoPtr, NM_RETURN, Focus, id);
            return TRUE;
        }
    }
    return FALSE;
}

/***********************************************************************
 *           SYSKEY_SelectNextPrevLink
 * Changes the currently focused link
 */
1420
static BOOL SYSKEY_SelectNextPrevLink (const SYSLINK_INFO *infoPtr, BOOL Prev)
1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440
{
    if(infoPtr->HasFocus)
    {
        PDOC_ITEM Focus;
        int id;

        Focus = SYSLINK_GetFocusLink(infoPtr, &id);
        if(Focus != NULL)
        {
            PDOC_ITEM NewFocus, OldFocus;

            if(Prev)
                NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
            else
                NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);

            if(NewFocus != NULL)
            {
                OldFocus = SYSLINK_SetFocusLink(infoPtr, NewFocus);

1441
                if(OldFocus && OldFocus != NewFocus)
1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457
                {
                    SYSLINK_RepaintLink(infoPtr, OldFocus);
                }
                SYSLINK_RepaintLink(infoPtr, NewFocus);
                return TRUE;
            }
        }
    }
    return FALSE;
}

/***********************************************************************
 *           SYSKEY_SelectNextPrevLink
 * Determines if there's a next or previous link to decide whether the control
 * should capture the tab key message
 */
1458
static BOOL SYSLINK_NoNextLink (const SYSLINK_INFO *infoPtr, BOOL Prev)
1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470
{
    PDOC_ITEM Focus, NewFocus;

    Focus = SYSLINK_GetFocusLink(infoPtr, NULL);
    if(Prev)
        NewFocus = SYSLINK_GetPrevLink(infoPtr, Focus);
    else
        NewFocus = SYSLINK_GetNextLink(infoPtr, Focus);

    return NewFocus == NULL;
}

1471 1472 1473 1474
/***********************************************************************
 *           SYSLINK_GetIdealSize
 * Calculates the ideal size of a link control at a given maximum width.
 */
1475
static VOID SYSLINK_GetIdealSize (const SYSLINK_INFO *infoPtr, int cxMaxWidth, LPSIZE lpSize)
1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497
{
    RECT rc;
    HDC hdc;

    rc.left = rc.top = rc.bottom = 0;
    rc.right = cxMaxWidth;

    hdc = GetDC(infoPtr->Self);
    if (hdc != NULL)
    {
        HGDIOBJ hOldFont = SelectObject(hdc, infoPtr->Font);

        SYSLINK_Render(infoPtr, hdc, &rc);

        SelectObject(hdc, hOldFont);
        ReleaseDC(infoPtr->Self, hdc);

        lpSize->cx = rc.right;
        lpSize->cy = rc.bottom;
    }
}

1498 1499 1500 1501 1502 1503 1504 1505
/***********************************************************************
 *           SysLinkWindowProc
 */
static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
                                        WPARAM wParam, LPARAM lParam)
{
    SYSLINK_INFO *infoPtr;

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 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529
        if (!(infoPtr->Style & LWS_TRANSPARENT))
        {
            HDC hdc = (HDC)wParam;
            HBRUSH brush = CreateSolidBrush( comctl32_color.clrWindow );
            RECT rect;

            GetClipBox( hdc, &rect );
            FillRect( hdc, &rect, brush );
            DeleteObject( brush );
            return 1;
        }
1530
        return 0;
1531

1532 1533 1534 1535 1536
    case WM_SETCURSOR:
    {
        LHITTESTINFO ht;
        DWORD mp = GetMessagePos();
        
1537 1538
        ht.pt.x = (short)LOWORD(mp);
        ht.pt.y = (short)HIWORD(mp);
1539 1540 1541 1542 1543 1544 1545
        
        ScreenToClient(infoPtr->Self, &ht.pt);
        if(SYSLINK_HitTest (infoPtr, &ht))
        {
            SetCursor(LoadCursorW(0, (LPCWSTR)IDC_HAND));
            return TRUE;
        }
1546 1547

        return DefWindowProcW(hwnd, message, wParam, lParam);
1548 1549 1550 1551
    }

    case WM_SIZE:
    {
1552 1553
        RECT rcClient;
        if (GetClientRect(infoPtr->Self, &rcClient))
1554
        {
1555 1556 1557 1558 1559 1560
            HDC hdc = GetDC(infoPtr->Self);
            if(hdc != NULL)
            {
                SYSLINK_Render(infoPtr, hdc, &rcClient);
                ReleaseDC(infoPtr->Self, hdc);
            }
1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
        }
        return 0;
    }

    case WM_GETFONT:
        return (LRESULT)infoPtr->Font;

    case WM_SETFONT:
        return (LRESULT)SYSLINK_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

    case WM_SETTEXT:
        SYSLINK_SetText(infoPtr, (LPWSTR)lParam);
1573
        return DefWindowProcW(hwnd, message, wParam, lParam);
1574 1575 1576 1577

    case WM_LBUTTONDOWN:
    {
        POINT pt;
1578 1579
        pt.x = (short)LOWORD(lParam);
        pt.y = (short)HIWORD(lParam);
1580
        return SYSLINK_LButtonDown(infoPtr, &pt);
1581 1582 1583 1584
    }
    case WM_LBUTTONUP:
    {
        POINT pt;
1585 1586
        pt.x = (short)LOWORD(lParam);
        pt.y = (short)HIWORD(lParam);
1587
        return SYSLINK_LButtonUp(infoPtr, &pt);
1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602
    }
    
    case WM_KEYDOWN:
    {
        switch(wParam)
        {
        case VK_RETURN:
            SYSLINK_OnEnter(infoPtr);
            return 0;
        case VK_TAB:
        {
            BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
            SYSKEY_SelectNextPrevLink(infoPtr, shift);
            return 0;
        }
1603 1604
        default:
            return DefWindowProcW(hwnd, message, wParam, lParam);
1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637
        }
    }
    
    case WM_GETDLGCODE:
    {
        LRESULT Ret = DLGC_HASSETSEL;
        int vk = (lParam != 0 ? (int)((LPMSG)lParam)->wParam : 0);
        switch(vk)
        {
        case VK_RETURN:
            Ret |= DLGC_WANTMESSAGE;
            break;
        case VK_TAB:
        {
            BOOL shift = GetKeyState(VK_SHIFT) & 0x8000;
            if(!SYSLINK_NoNextLink(infoPtr, shift))
            {
                Ret |= DLGC_WANTTAB;
            }
            else
            {
                Ret |= DLGC_WANTCHARS;
            }
            break;
        }
        }
        return Ret;
    }
    
    case WM_NCHITTEST:
    {
        POINT pt;
        RECT rc;
1638 1639
        pt.x = (short)LOWORD(lParam);
        pt.y = (short)HIWORD(lParam);
1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665
        
        GetClientRect(infoPtr->Self, &rc);
        ScreenToClient(infoPtr->Self, &pt);
        if(pt.x < 0 || pt.y < 0 || pt.x > rc.right || pt.y > rc.bottom)
        {
            return HTNOWHERE;
        }

        if(SYSLINK_LinkAtPt(infoPtr, &pt, NULL, FALSE))
        {
            return HTCLIENT;
        }
        
        return HTTRANSPARENT;
    }

    case LM_HITTEST:
        return SYSLINK_HitTest(infoPtr, (PLHITTESTINFO)lParam);

    case LM_SETITEM:
        return SYSLINK_SetItem(infoPtr, (PLITEM)lParam);

    case LM_GETITEM:
        return SYSLINK_GetItem(infoPtr, (PLITEM)lParam);

    case LM_GETIDEALHEIGHT:
1666 1667 1668 1669 1670
        if (lParam)
        {
            /* LM_GETIDEALSIZE */
            SYSLINK_GetIdealSize(infoPtr, (int)wParam, (LPSIZE)lParam);
        }
1671 1672 1673
        return SYSLINK_GetIdealHeight(infoPtr);

    case WM_SETFOCUS:
1674
        return SYSLINK_SetFocus(infoPtr);
1675 1676

    case WM_KILLFOCUS:
1677
        return SYSLINK_KillFocus(infoPtr);
1678

1679
    case WM_ENABLE:
1680 1681 1682 1683
        infoPtr->Style &= ~WS_DISABLED;
        infoPtr->Style |= (wParam ? 0 : WS_DISABLED);
        InvalidateRect (infoPtr->Self, NULL, FALSE);
        return 0;
1684 1685 1686 1687 1688 1689 1690 1691 1692 1693

    case WM_STYLECHANGED:
        if (wParam == GWL_STYLE)
        {
            infoPtr->Style = ((LPSTYLESTRUCT)lParam)->styleNew;

            InvalidateRect(infoPtr->Self, NULL, TRUE);
        }
        return 0;

1694
    case WM_CREATE:
1695 1696 1697
    {
        CREATESTRUCTW *cs = (CREATESTRUCTW*)lParam;

1698
        /* allocate memory for info struct */
1699
        infoPtr = Alloc (sizeof(SYSLINK_INFO));
1700
        if (!infoPtr) return -1;
1701
        SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1702 1703 1704

        /* initialize the info struct */
        infoPtr->Self = hwnd;
1705 1706
        infoPtr->Notify = cs->hwndParent;
        infoPtr->Style = cs->style;
1707 1708
        infoPtr->Font = 0;
        infoPtr->LinkFont = 0;
1709
        list_init(&infoPtr->Items);
1710 1711
        infoPtr->HasFocus = FALSE;
        infoPtr->MouseDownID = -1;
1712
        infoPtr->TextColor = comctl32_color.clrWindowText;
1713
        infoPtr->LinkColor = comctl32_color.clrHighlight;
1714
        infoPtr->VisitedColor = comctl32_color.clrHighlight;
1715
        infoPtr->BreakChar = ' ';
1716
        infoPtr->IgnoreReturn = infoPtr->Style & LWS_IGNORERETURN;
1717
        TRACE("SysLink Ctrl creation, hwnd=%p\n", hwnd);
1718
        SYSLINK_SetText(infoPtr, cs->lpszName);
1719
        return 0;
1720
    }
1721 1722 1723
    case WM_DESTROY:
        TRACE("SysLink Ctrl destruction, hwnd=%p\n", hwnd);
        SYSLINK_ClearDoc(infoPtr);
1724 1725
        if(infoPtr->Font != 0) DeleteObject(infoPtr->Font);
        if(infoPtr->LinkFont != 0) DeleteObject(infoPtr->LinkFont);
1726
        SetWindowLongPtrW(hwnd, 0, 0);
1727
        Free (infoPtr);
1728 1729
        return 0;

1730 1731 1732 1733
    case WM_SYSCOLORCHANGE:
        COMCTL32_RefreshSysColors();
        return 0;

1734
    default:
1735
        if ((message >= WM_USER) && (message < WM_APP) && !COMCTL32_IsReflectedMessage(message))
1736
        {
1737
            ERR("unknown msg %04x wp=%04lx lp=%08lx\n", message, wParam, lParam );
1738
        }
1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754
        return DefWindowProcW(hwnd, message, wParam, lParam);
    }
}


/***********************************************************************
 * SYSLINK_Register [Internal]
 *
 * Registers the SysLink window class.
 */
VOID SYSLINK_Register (void)
{
    WNDCLASSW wndClass;

    ZeroMemory (&wndClass, sizeof(wndClass));
    wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1755
    wndClass.lpfnWndProc   = SysLinkWindowProc;
1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof (SYSLINK_INFO *);
    wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
    wndClass.lpszClassName = WC_LINK;

    RegisterClassW (&wndClass);
}


/***********************************************************************
 * SYSLINK_Unregister [Internal]
 *
 * Unregisters the SysLink window class.
 */
VOID SYSLINK_Unregister (void)
{
    UnregisterClassW (WC_LINK, NULL);
}