/*
 * Help Viewer
 *
 * Copyright    1996 Ulrich Schmid <uschmid@mail.hh.provi.de>
 *              2002 Sylvain Petreolle <spetreolle@yahoo.fr>
 *              2002 Eric Pouech <eric.pouech@wanadoo.fr>
 *              2004 Ken Belleau <jamez@ivic.qc.ca>
 *
 * 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
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winhelp.h"
#include "winhelp_res.h"
#include "shellapi.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(winhelp);

static BOOL    WINHELP_RegisterWinClasses(void);
static LRESULT CALLBACK WINHELP_MainWndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK WINHELP_TextWndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK WINHELP_ButtonBoxWndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK WINHELP_ButtonWndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK WINHELP_HistoryWndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK WINHELP_ShadowWndProc(HWND, UINT, WPARAM, LPARAM);
static void    WINHELP_CheckPopup(UINT);
static BOOL    WINHELP_SplitLines(HWND hWnd, LPSIZE);
static void    WINHELP_InitFonts(HWND hWnd);
static void    WINHELP_DeleteLines(WINHELP_WINDOW*);
static void    WINHELP_DeleteWindow(WINHELP_WINDOW*);
static void    WINHELP_SetupText(HWND hWnd);
static WINHELP_LINE_PART* WINHELP_IsOverLink(WINHELP_WINDOW*, WPARAM, LPARAM);

WINHELP_GLOBALS Globals = {3, NULL, NULL, 0, TRUE, NULL, NULL, NULL, NULL};

/***********************************************************************
 *
 *           WINHELP_LookupHelpFile
 */
HLPFILE* WINHELP_LookupHelpFile(LPCSTR lpszFile)
{
    HLPFILE*        hlpfile;

    hlpfile = HLPFILE_ReadHlpFile(lpszFile);

    /* Add Suffix `.hlp' */
    if (!hlpfile && lstrcmpi(lpszFile + strlen(lpszFile) - 4, ".hlp") != 0)
    {
        char      szFile_hlp[MAX_PATHNAME_LEN];

        lstrcpyn(szFile_hlp, lpszFile, sizeof(szFile_hlp) - 4);
        szFile_hlp[sizeof(szFile_hlp) - 5] = '\0';
        lstrcat(szFile_hlp, ".hlp");

        hlpfile = HLPFILE_ReadHlpFile(szFile_hlp);
    }
    if (!hlpfile)
    {
        WINHELP_MessageBoxIDS_s(STID_HLPFILE_ERROR_s, lpszFile, STID_WHERROR, MB_OK);
        if (Globals.win_list) return NULL;
    }
    return hlpfile;
}

/******************************************************************
 *		WINHELP_GetWindowInfo
 *
 *
 */
HLPFILE_WINDOWINFO*     WINHELP_GetWindowInfo(HLPFILE* hlpfile, LPCSTR name)
{
    static      HLPFILE_WINDOWINFO      mwi;
    unsigned int     i;

    if (!name || !name[0])
        name = Globals.active_win->lpszName;

    if (hlpfile)
        for (i = 0; i < hlpfile->numWindows; i++)
            if (!strcmp(hlpfile->windows[i].name, name))
                return &hlpfile->windows[i];

    if (strcmp(name, "main") != 0)
    {
        WINE_FIXME("Couldn't find window info for %s\n", name);
        assert(0);
        return NULL;
    }
    if (!mwi.name[0])
    {
        strcpy(mwi.type, "primary");
        strcpy(mwi.name, "main");
        if (!LoadString(Globals.hInstance, STID_WINE_HELP, 
                        mwi.caption, sizeof(mwi.caption)))
            strcpy(mwi.caption, hlpfile->lpszTitle);
        mwi.origin.x = mwi.origin.y = mwi.size.cx = mwi.size.cy = CW_USEDEFAULT;
        mwi.style = SW_SHOW;
        mwi.win_style = WS_OVERLAPPEDWINDOW;
        mwi.sr_color = mwi.sr_color = 0xFFFFFF;
    }
    return &mwi;
}

/******************************************************************
 *		HLPFILE_GetPopupWindowInfo
 *
 *
 */
static HLPFILE_WINDOWINFO*     WINHELP_GetPopupWindowInfo(HLPFILE* hlpfile, HWND hParentWnd, POINT* mouse)
{
    static      HLPFILE_WINDOWINFO      wi;

    RECT parent_rect;
    
    wi.type[0] = wi.name[0] = wi.caption[0] = '\0';

    /* Calculate horizontal size and position of a popup window */
    GetWindowRect(hParentWnd, &parent_rect);
    wi.size.cx = (parent_rect.right  - parent_rect.left) / 2;
    wi.size.cy = 10; /* need a non null value, so that border are taken into account while computing */

    wi.origin = *mouse;
    ClientToScreen(hParentWnd, &wi.origin);
    wi.origin.x -= wi.size.cx / 2;
    wi.origin.x  = min(wi.origin.x, GetSystemMetrics(SM_CXSCREEN) - wi.size.cx);
    wi.origin.x  = max(wi.origin.x, 0);

    wi.style = SW_SHOW;
    wi.win_style = WS_POPUPWINDOW;
    wi.sr_color = wi.sr_color = 0xFFFFFF;

    return &wi;
}

/***********************************************************************
 *
 *           WinMain
 */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR cmdline, int show)
{
    MSG         msg;
    LONG        lHash = 0;
    HLPFILE*    hlpfile;
    CHAR*       quote;
    WINHELP_DLL*        dll;

    Globals.hInstance = hInstance;
    
    /* Get options */
    while (*cmdline && (*cmdline == ' ' || *cmdline == '-'))
    {
        CHAR   option;
        LPCSTR topic_id;
        if (*cmdline++ == ' ') continue;

        option = *cmdline;
        if (option) cmdline++;
        while (*cmdline && *cmdline == ' ') cmdline++;
        switch (option)
	{
	case 'i':
	case 'I':
            topic_id = cmdline;
            while (*cmdline && *cmdline != ' ') cmdline++;
            if (*cmdline) *cmdline++ = '\0';
            lHash = HLPFILE_Hash(topic_id);
            break;

	case '3':
	case '4':
            Globals.wVersion = option - '0';
            break;

        case 'x':
            show = SW_HIDE; 
            Globals.isBook = FALSE;
            break;

        default:
            WINE_FIXME("Unsupported cmd line: %s\n", cmdline);
            break;
	}
    }

    /* Create primary window */
    if (!WINHELP_RegisterWinClasses())
    {
        WINE_FIXME("Couldn't register classes\n");
        return 0;
    }

    if (*cmdline)
    {
        if ((*cmdline == '"') && (quote = strchr(cmdline+1, '"')))
        {
            cmdline++;
            *quote = '\0';
        }
        hlpfile = WINHELP_LookupHelpFile(cmdline);
        if (!hlpfile) return 0;
    }
    else hlpfile = NULL;
    WINHELP_CreateHelpWindowByHash(hlpfile, lHash, 
                                   WINHELP_GetWindowInfo(hlpfile, "main"), show);

    /* Message loop */
    while (GetMessage(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    for (dll = Globals.dlls; dll; dll = dll->next)
    {
        if (dll->class & DC_INITTERM) dll->handler(DW_TERM, 0, 0);
    }
    return 0;
}

/***********************************************************************
 *
 *           RegisterWinClasses
 */
static BOOL WINHELP_RegisterWinClasses(void)
{
    WNDCLASS class_main, class_button_box, class_text, class_shadow, class_history;

    class_main.style               = CS_HREDRAW | CS_VREDRAW;
    class_main.lpfnWndProc         = WINHELP_MainWndProc;
    class_main.cbClsExtra          = 0;
    class_main.cbWndExtra          = sizeof(LONG);
    class_main.hInstance           = Globals.hInstance;
    class_main.hIcon               = LoadIcon(0, IDI_APPLICATION);
    class_main.hCursor             = LoadCursor(0, IDC_ARROW);
    class_main.hbrBackground       = GetStockObject(WHITE_BRUSH);
    class_main.lpszMenuName        = 0;
    class_main.lpszClassName       = MAIN_WIN_CLASS_NAME;

    class_button_box               = class_main;
    class_button_box.lpfnWndProc   = WINHELP_ButtonBoxWndProc;
    class_button_box.hbrBackground = GetStockObject(GRAY_BRUSH);
    class_button_box.lpszClassName = BUTTON_BOX_WIN_CLASS_NAME;

    class_text = class_main;
    class_text.lpfnWndProc         = WINHELP_TextWndProc;
    class_text.lpszClassName       = TEXT_WIN_CLASS_NAME;

    class_shadow = class_main;
    class_shadow.lpfnWndProc       = WINHELP_ShadowWndProc;
    class_shadow.hbrBackground     = GetStockObject(GRAY_BRUSH);
    class_shadow.lpszClassName     = SHADOW_WIN_CLASS_NAME;

    class_history = class_main;
    class_history.lpfnWndProc      = WINHELP_HistoryWndProc;
    class_history.lpszClassName    = HISTORY_WIN_CLASS_NAME;

    return (RegisterClass(&class_main) &&
            RegisterClass(&class_button_box) &&
            RegisterClass(&class_text) &&
            RegisterClass(&class_shadow) &&
            RegisterClass(&class_history));
}

typedef struct
{
    WORD size;
    WORD command;
    LONG data;
    LONG reserved;
    WORD ofsFilename;
    WORD ofsData;
} WINHELP,*LPWINHELP;

/******************************************************************
 *		WINHELP_HandleCommand
 *
 *
 */
static LRESULT  WINHELP_HandleCommand(HWND hSrcWnd, LPARAM lParam)
{
    COPYDATASTRUCT*     cds = (COPYDATASTRUCT*)lParam;
    WINHELP*            wh;

    if (cds->dwData != 0xA1DE505)
    {
        WINE_FIXME("Wrong magic number (%08lx)\n", cds->dwData);
        return 0;
    }

    wh = (WINHELP*)cds->lpData;

    if (wh)
    {
        char*   ptr = (wh->ofsFilename) ? (LPSTR)wh + wh->ofsFilename : NULL;

        WINE_TRACE("Got[%u]: cmd=%u data=%08lx fn=%s\n", 
                   wh->size, wh->command, wh->data, ptr);
        switch (wh->command)
        {
        case HELP_CONTEXT:
            if (ptr)
            {
                MACRO_JumpContext(ptr, "main", wh->data);
            }
            break;
        case HELP_QUIT:
            MACRO_Exit();
            break;
        case HELP_CONTENTS:
            if (ptr)
            {
                MACRO_JumpContents(ptr, "main");
            }
            break;
        case HELP_HELPONHELP:
            MACRO_HelpOn();
            break;
        /* case HELP_SETINDEX: */
        case HELP_SETCONTENTS:
            if (ptr)
            {
                MACRO_SetContents(ptr, wh->data);
            }
            break;
        case HELP_CONTEXTPOPUP:
            if (ptr)
            {
                MACRO_PopupContext(ptr, wh->data);
            }
            break;
        /* case HELP_FORCEFILE:*/
        /* case HELP_CONTEXTMENU: */
        case HELP_FINDER:
            /* in fact, should be the topic dialog box */
            if (ptr)
            {
                MACRO_JumpHash(ptr, "main", 0);
            }
            break;
        /* case HELP_WM_HELP: */
        /* case HELP_SETPOPUP_POS: */
        /* case HELP_KEY: */
        /* case HELP_COMMAND: */
        /* case HELP_PARTIALKEY: */
        /* case HELP_MULTIKEY: */
        /* case HELP_SETWINPOS: */
            WINE_FIXME("Unknown command (%x) for remote winhelp control\n", wh->command);
            break;
        }
    }
    return 0L;
}

/******************************************************************
 *		WINHELP_ReuseWindow
 *
 *
 */
static BOOL     WINHELP_ReuseWindow(WINHELP_WINDOW* win, WINHELP_WINDOW* oldwin, 
                                    HLPFILE_PAGE* page, int nCmdShow)
{
    unsigned int i;

    win->hMainWnd      = oldwin->hMainWnd;
    win->hButtonBoxWnd = oldwin->hButtonBoxWnd;
    win->hTextWnd      = oldwin->hTextWnd;
    win->hHistoryWnd   = oldwin->hHistoryWnd;
    oldwin->hMainWnd = oldwin->hButtonBoxWnd = oldwin->hTextWnd = oldwin->hHistoryWnd = 0;

    SetWindowLong(win->hMainWnd,      0, (LONG)win);
    SetWindowLong(win->hButtonBoxWnd, 0, (LONG)win);
    SetWindowLong(win->hTextWnd,      0, (LONG)win);
    SetWindowLong(win->hHistoryWnd,   0, (LONG)win);

    WINHELP_InitFonts(win->hMainWnd);

    if (page)
        SetWindowText(win->hMainWnd, page->file->lpszTitle);

    WINHELP_SetupText(win->hTextWnd);
    InvalidateRect(win->hTextWnd, NULL, TRUE);
    SendMessage(win->hMainWnd, WM_USER, 0, 0);
    ShowWindow(win->hMainWnd, nCmdShow);
    UpdateWindow(win->hTextWnd);

    if (!(win->info->win_style & WS_POPUP))
    {
        unsigned        num;

        memcpy(win->history, oldwin->history, sizeof(win->history));
        win->histIndex = oldwin->histIndex;

        /* FIXME: when using back, we shouldn't update the history... */

        if (page)
        {
            for (i = 0; i < win->histIndex; i++)
                if (win->history[i] == page) break;

            /* if the new page is already in the history, do nothing */
            if (i == win->histIndex)
            {
                num = sizeof(win->history) / sizeof(win->history[0]);
                if (win->histIndex == num)
                {
                    /* we're full, remove latest entry */
                    HLPFILE_FreeHlpFile(win->history[0]->file);
                    memmove(&win->history[0], &win->history[1], 
                            (num - 1) * sizeof(win->history[0]));
                    win->histIndex--;
                }
                win->history[win->histIndex++] = page;
                page->file->wRefCount++;
                if (win->hHistoryWnd) InvalidateRect(win->hHistoryWnd, NULL, TRUE);
            }
        }

        memcpy(win->back, oldwin->back, sizeof(win->back));
        win->backIndex = oldwin->backIndex;

        if (page)
        {
            num = sizeof(win->back) / sizeof(win->back[0]);
            if (win->backIndex == num)
            {
                /* we're full, remove latest entry */
                HLPFILE_FreeHlpFile(win->back[0]->file);
                memmove(&win->back[0], &win->back[1], 
                        (num - 1) * sizeof(win->back[0]));
                win->backIndex--;
            }
            win->back[win->backIndex++] = page;
            page->file->wRefCount++;
        }
    }
    else
        win->backIndex = win->histIndex = 0;

    oldwin->histIndex = oldwin->backIndex = 0;
    WINHELP_DeleteWindow(oldwin);
    return TRUE;
}

/***********************************************************************
 *
 *           WINHELP_CreateHelpWindow
 */
BOOL WINHELP_CreateHelpWindow(HLPFILE_PAGE* page, HLPFILE_WINDOWINFO* wi,
                              int nCmdShow)
{
    WINHELP_WINDOW *win, *oldwin;
    HWND hWnd;
    BOOL bPrimary;
    BOOL bPopup;

    bPrimary = !lstrcmpi(wi->name, "main");
    bPopup = wi->win_style & WS_POPUP;

    /* Initialize WINHELP_WINDOW struct */
    win = HeapAlloc(GetProcessHeap(), 0,
                    sizeof(WINHELP_WINDOW) + strlen(wi->name) + 1);
    if (!win) return FALSE;

    win->next = Globals.win_list;
    Globals.win_list = win;

    win->lpszName = (char*)win + sizeof(WINHELP_WINDOW);
    lstrcpy((char*)win->lpszName, wi->name);

    win->page = page;
    win->first_button = 0;
    win->first_line = 0;
    win->hMainWnd = 0;
    win->hButtonBoxWnd = 0;
    win->hTextWnd = 0;
    win->hShadowWnd = 0;
    win->hHistoryWnd = 0;

    win->hArrowCur = LoadCursorA(0, (LPSTR)IDC_ARROW);
    win->hHandCur = LoadCursorA(0, (LPSTR)IDC_HAND);

    win->info = wi;

    Globals.active_win = win;

    /* Initialize default pushbuttons */
    if (bPrimary && page)
    {
        CHAR    buffer[MAX_STRING_LEN];

        LoadString(Globals.hInstance, STID_CONTENTS, buffer, sizeof(buffer));
        MACRO_CreateButton("BTN_CONTENTS", buffer, "Contents()");
        LoadString(Globals.hInstance, STID_SEARCH,buffer, sizeof(buffer));
        MACRO_CreateButton("BTN_SEARCH", buffer, "Search()");
        LoadString(Globals.hInstance, STID_BACK, buffer, sizeof(buffer));
        MACRO_CreateButton("BTN_BACK", buffer, "Back()");
        LoadString(Globals.hInstance, STID_HISTORY, buffer, sizeof(buffer));
        MACRO_CreateButton("BTN_HISTORY", buffer, "History()");
        LoadString(Globals.hInstance, STID_TOPICS, buffer, sizeof(buffer));
        MACRO_CreateButton("BTN_TOPICS", buffer, "Finder()");
    }

    /* Initialize file specific pushbuttons */
    if (!(wi->win_style & WS_POPUP) && page)
    {
        HLPFILE_MACRO  *macro;
        for (macro = page->file->first_macro; macro; macro = macro->next)
            MACRO_ExecuteMacro(macro->lpszMacro);

        for (macro = page->first_macro; macro; macro = macro->next)
            MACRO_ExecuteMacro(macro->lpszMacro);
    }

    win->histIndex = win->backIndex = 0;
    /* Reuse existing window */
    if (!bPopup)
    {
        for (oldwin = win->next; oldwin; oldwin = oldwin->next)
        {
            if (!lstrcmpi(oldwin->lpszName, wi->name))
            {
                return WINHELP_ReuseWindow(win, oldwin, page, nCmdShow);
            }
        }
        if (page)
        {
            win->histIndex = win->backIndex = 1;
            win->history[0] = win->back[0] = page;
            page->file->wRefCount += 2;
            strcpy(wi->caption, page->file->lpszTitle);
        }
    }

    hWnd = CreateWindow(bPopup ? TEXT_WIN_CLASS_NAME : MAIN_WIN_CLASS_NAME,
                        wi->caption, 
                        bPrimary ? WS_OVERLAPPEDWINDOW : wi->win_style,
                        wi->origin.x, wi->origin.y, wi->size.cx, wi->size.cy,
                        NULL, bPrimary ? LoadMenu(Globals.hInstance, MAKEINTRESOURCE(MAIN_MENU)) : 0,
                        Globals.hInstance, win);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

/***********************************************************************
 *
 *           WINHELP_CreateHelpWindowByHash
 */
BOOL WINHELP_CreateHelpWindowByHash(HLPFILE* hlpfile, LONG lHash, 
                                    HLPFILE_WINDOWINFO* wi, int nCmdShow)
{
    HLPFILE_PAGE*       page = NULL;

    if (hlpfile)
        page = lHash ? HLPFILE_PageByHash(hlpfile, lHash) : 
            HLPFILE_Contents(hlpfile);
    if (page) page->file->wRefCount++;
    return WINHELP_CreateHelpWindow(page, wi, nCmdShow);
}

/***********************************************************************
 *
 *           WINHELP_MainWndProc
 */
static LRESULT CALLBACK WINHELP_MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    WINHELP_WINDOW *win;
    WINHELP_BUTTON *button;
    RECT rect, button_box_rect;
    INT  text_top, curPos, min, max, dy, keyDelta;

    WINHELP_CheckPopup(msg);

    switch (msg)
    {
    case WM_NCCREATE:
        win = (WINHELP_WINDOW*) ((LPCREATESTRUCT) lParam)->lpCreateParams;
        SetWindowLong(hWnd, 0, (LONG) win);
        win->hMainWnd = hWnd;
        break;

    case WM_CREATE:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        /* Create button box and text Window */
        CreateWindow(BUTTON_BOX_WIN_CLASS_NAME, "", WS_CHILD | WS_VISIBLE,
                     0, 0, 0, 0, hWnd, 0, Globals.hInstance, win);

        CreateWindow(TEXT_WIN_CLASS_NAME, "", WS_CHILD | WS_VISIBLE,
                     0, 0, 0, 0, hWnd, 0, Globals.hInstance, win);

        /* Fall through */
    case WM_USER:
    case WM_WINDOWPOSCHANGED:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        GetClientRect(hWnd, &rect);

        /* Update button box and text Window */
        SetWindowPos(win->hButtonBoxWnd, HWND_TOP,
                     rect.left, rect.top,
                     rect.right - rect.left,
                     rect.bottom - rect.top, 0);

        GetWindowRect(win->hButtonBoxWnd, &button_box_rect);
        text_top = rect.top + button_box_rect.bottom - button_box_rect.top;

        SetWindowPos(win->hTextWnd, HWND_TOP,
                     rect.left, text_top,
                     rect.right - rect.left,
                     rect.bottom - text_top, 0);

        break;

    case WM_COMMAND:
        Globals.active_win = win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        switch (wParam)
	{
            /* Menu FILE */
	case MNID_FILE_OPEN:    MACRO_FileOpen();       break;
	case MNID_FILE_PRINT:	MACRO_Print();          break;
	case MNID_FILE_SETUP:	MACRO_PrinterSetup();   break;
	case MNID_FILE_EXIT:	MACRO_Exit();           break;

            /* Menu EDIT */
	case MNID_EDIT_COPYDLG: MACRO_CopyDialog();     break;
	case MNID_EDIT_ANNOTATE:MACRO_Annotate();       break;

            /* Menu Bookmark */
	case MNID_BKMK_DEFINE:  MACRO_BookmarkDefine(); break;

            /* Menu Help */
	case MNID_HELP_HELPON:	MACRO_HelpOn();         break;
	case MNID_HELP_HELPTOP: MACRO_HelpOnTop();      break;
	case MNID_HELP_ABOUT:	MACRO_About();          break;
	case MNID_HELP_WINE:    ShellAbout(hWnd, "WINE", "Help", 0); break;

	default:
            /* Buttons */
            for (button = win->first_button; button; button = button->next)
                if (wParam == button->wParam) break;
            if (button)
                MACRO_ExecuteMacro(button->lpszMacro);
            else
                WINHELP_MessageBoxIDS(STID_NOT_IMPLEMENTED, 0x121, MB_OK);
            break;
	}
        break;
    case WM_DESTROY:
        if (Globals.hPopupWnd) DestroyWindow(Globals.hPopupWnd);
        break;
    case WM_COPYDATA:
        return WINHELP_HandleCommand((HWND)wParam, lParam);

    case WM_KEYDOWN:
        keyDelta = 0;

        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
            keyDelta = GetSystemMetrics(SM_CXVSCROLL);
            if (wParam == VK_UP)
                keyDelta = -keyDelta;

        case VK_PRIOR:
        case VK_NEXT:
            win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
            curPos = GetScrollPos(win->hTextWnd, SB_VERT);
            GetScrollRange(win->hTextWnd, SB_VERT, &min, &max);

            if (keyDelta == 0)
            {            
                GetClientRect(win->hTextWnd, &rect);
                keyDelta = (rect.bottom - rect.top) / 2;
                if (wParam == VK_PRIOR)
                    keyDelta = -keyDelta;
            }

            curPos += keyDelta;
            if (curPos > max)
                 curPos = max;
            else if (curPos < min)
                 curPos = min;

            dy = GetScrollPos(win->hTextWnd, SB_VERT) - curPos;
            SetScrollPos(win->hTextWnd, SB_VERT, curPos, TRUE);
            ScrollWindow(win->hTextWnd, 0, dy, NULL, NULL);
            UpdateWindow(win->hTextWnd);
            return 0;

        case VK_ESCAPE:
            MACRO_Exit();
            return 0;
        }
        break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/***********************************************************************
 *
 *           WINHELP_ButtonBoxWndProc
 */
static LRESULT CALLBACK WINHELP_ButtonBoxWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    WINDOWPOS      *winpos;
    WINHELP_WINDOW *win;
    WINHELP_BUTTON *button;
    SIZE button_size;
    INT  x, y;

    WINHELP_CheckPopup(msg);

    switch (msg)
    {
    case WM_NCCREATE:
        win = (WINHELP_WINDOW*) ((LPCREATESTRUCT) lParam)->lpCreateParams;
        SetWindowLong(hWnd, 0, (LONG) win);
        win->hButtonBoxWnd = hWnd;
        break;

    case WM_WINDOWPOSCHANGING:
        winpos = (WINDOWPOS*) lParam;
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        /* Update buttons */
        button_size.cx = 0;
        button_size.cy = 0;
        for (button = win->first_button; button; button = button->next)
	{
            HDC  hDc;
            SIZE textsize;
            if (!button->hWnd)
            {
                button->hWnd = CreateWindow(STRING_BUTTON, (LPSTR) button->lpszName,
                                            WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                                            0, 0, 0, 0,
                                            hWnd, (HMENU) button->wParam,
                                            Globals.hInstance, 0);
                if (button->hWnd) {
                    if (Globals.button_proc == NULL)
                        Globals.button_proc = (WNDPROC) GetWindowLong(button->hWnd, GWL_WNDPROC);
                    SetWindowLong(button->hWnd, GWL_WNDPROC, (LONG) WINHELP_ButtonWndProc);
                }
            }
            hDc = GetDC(button->hWnd);
            GetTextExtentPoint(hDc, button->lpszName,
                               lstrlen(button->lpszName), &textsize);
            ReleaseDC(button->hWnd, hDc);

            button_size.cx = max(button_size.cx, textsize.cx + BUTTON_CX);
            button_size.cy = max(button_size.cy, textsize.cy + BUTTON_CY);
	}

        x = 0;
        y = 0;
        for (button = win->first_button; button; button = button->next)
	{
            SetWindowPos(button->hWnd, HWND_TOP, x, y, button_size.cx, button_size.cy, 0);

            if (x + 2 * button_size.cx <= winpos->cx)
                x += button_size.cx;
            else
                x = 0, y += button_size.cy;
	}
        winpos->cy = y + (x ? button_size.cy : 0);
        break;

    case WM_COMMAND:
        SendMessage(GetParent(hWnd), msg, wParam, lParam);
        break;

    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
        case VK_PRIOR:
        case VK_NEXT:
        case VK_ESCAPE:
            return SendMessage(GetParent(hWnd), msg, wParam, lParam);
        }
        break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/***********************************************************************
 *
 *           WINHELP_ButtonWndProc
 */
static LRESULT CALLBACK WINHELP_ButtonWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_KEYDOWN)
    {
        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
        case VK_PRIOR:
        case VK_NEXT:
        case VK_ESCAPE:
            return SendMessage(GetParent(hWnd), msg, wParam, lParam);
        }
    }

    return CallWindowProc(Globals.button_proc, hWnd, msg, wParam, lParam);
}

/***********************************************************************
 *
 *           WINHELP_TextWndProc
 */
static LRESULT CALLBACK WINHELP_TextWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    WINHELP_WINDOW    *win;
    WINHELP_LINE      *line;
    WINHELP_LINE_PART *part;
    WINDOWPOS         *winpos;
    PAINTSTRUCT        ps;
    HDC   hDc;
    POINT mouse;
    INT   scroll_pos;
    HWND  hPopupWnd;
    BOOL  bExit;

    if (msg != WM_LBUTTONDOWN)
        WINHELP_CheckPopup(msg);

    switch (msg)
    {
    case WM_NCCREATE:
        win = (WINHELP_WINDOW*) ((LPCREATESTRUCT) lParam)->lpCreateParams;
        SetWindowLong(hWnd, 0, (LONG) win);
        win->hTextWnd = hWnd;
        if (win->info->win_style & WS_POPUP) Globals.hPopupWnd = win->hMainWnd = hWnd;
        WINHELP_InitFonts(hWnd);
        break;

    case WM_CREATE:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        /* Calculate vertical size and position of a popup window */
        if (win->info->win_style & WS_POPUP)
	{
            POINT origin;
            RECT old_window_rect;
            RECT old_client_rect;
            SIZE old_window_size;
            SIZE old_client_size;
            SIZE new_client_size;
            SIZE new_window_size;

            GetWindowRect(hWnd, &old_window_rect);
            origin.x = old_window_rect.left;
            origin.y = old_window_rect.top;
            old_window_size.cx = old_window_rect.right  - old_window_rect.left;
            old_window_size.cy = old_window_rect.bottom - old_window_rect.top;

            GetClientRect(hWnd, &old_client_rect);
            old_client_size.cx = old_client_rect.right  - old_client_rect.left;
            old_client_size.cy = old_client_rect.bottom - old_client_rect.top;

            new_client_size = old_client_size;
            WINHELP_SplitLines(hWnd, &new_client_size);

            if (origin.y + POPUP_YDISTANCE + new_client_size.cy <= GetSystemMetrics(SM_CYSCREEN))
                origin.y += POPUP_YDISTANCE;
            else
                origin.y -= POPUP_YDISTANCE + new_client_size.cy;

            new_window_size.cx = old_window_size.cx - old_client_size.cx + new_client_size.cx;
            new_window_size.cy = old_window_size.cy - old_client_size.cy + new_client_size.cy;

            win->hShadowWnd =
                CreateWindow(SHADOW_WIN_CLASS_NAME, "", WS_POPUP,
                             origin.x + SHADOW_DX, origin.y + SHADOW_DY,
                             new_window_size.cx, new_window_size.cy,
                             0, 0, Globals.hInstance, 0);

            SetWindowPos(hWnd, HWND_TOP, origin.x, origin.y,
                         new_window_size.cx, new_window_size.cy,
                         0);
            SetWindowPos(win->hShadowWnd, hWnd, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
            ShowWindow(win->hShadowWnd, SW_NORMAL);
            SetActiveWindow(hWnd);
	}
        break;

    case WM_WINDOWPOSCHANGED:
        winpos = (WINDOWPOS*) lParam;

        if (!(winpos->flags & SWP_NOSIZE)) WINHELP_SetupText(hWnd);
        break;

    case WM_MOUSEWHEEL:
    {
       int wheelDelta = 0;
       UINT scrollLines = 3;
       int curPos = GetScrollPos(hWnd, SB_VERT);
       int min, max;

       GetScrollRange(hWnd, SB_VERT, &min, &max);

       SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0, &scrollLines, 0);
       if (wParam & (MK_SHIFT | MK_CONTROL))
           return DefWindowProc(hWnd, msg, wParam, lParam);
       wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam);
       if (abs(wheelDelta) >= WHEEL_DELTA && scrollLines) {
           int dy;

           curPos += wheelDelta;
           if (curPos > max)
                curPos = max;
           else if (curPos < min)
                curPos = min;

           dy = GetScrollPos(hWnd, SB_VERT) - curPos;
           SetScrollPos(hWnd, SB_VERT, curPos, TRUE);
           ScrollWindow(hWnd, 0, dy, NULL, NULL);
           UpdateWindow(hWnd);
       }
    }
    break;

    case WM_VSCROLL:
    {
	BOOL  update = TRUE;
	RECT  rect;
	INT   Min, Max;
	INT   CurPos = GetScrollPos(hWnd, SB_VERT);
	INT   dy;
	
	GetScrollRange(hWnd, SB_VERT, &Min, &Max);
	GetClientRect(hWnd, &rect);

	switch (wParam & 0xffff)
        {
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION: CurPos  = wParam >> 16;                   break;
        case SB_TOP:           CurPos  = Min;                            break;
        case SB_BOTTOM:        CurPos  = Max;                            break;
        case SB_PAGEUP:        CurPos -= (rect.bottom - rect.top) / 2;   break;
        case SB_PAGEDOWN:      CurPos += (rect.bottom - rect.top) / 2;   break;
        case SB_LINEUP:        CurPos -= GetSystemMetrics(SM_CXVSCROLL); break;
        case SB_LINEDOWN:      CurPos += GetSystemMetrics(SM_CXVSCROLL); break;
        default: update = FALSE;
        }
	if (update)
        {
	    if (CurPos > Max)
                CurPos = Max;
	    else if (CurPos < Min)
                CurPos = Min;
	    dy = GetScrollPos(hWnd, SB_VERT) - CurPos;
	    SetScrollPos(hWnd, SB_VERT, CurPos, TRUE);
	    ScrollWindow(hWnd, 0, dy, NULL, NULL);
	    UpdateWindow(hWnd);
        }
    }
    break;

    case WM_PAINT:
        hDc = BeginPaint(hWnd, &ps);
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        scroll_pos = GetScrollPos(hWnd, SB_VERT);

        for (line = win->first_line; line; line = line->next)
        {
            for (part = &line->first_part; part; part = part->next)
            {
                switch (part->cookie)
                {
                case hlp_line_part_text:
                    SelectObject(hDc, part->u.text.hFont);
                    SetTextColor(hDc, part->u.text.color);
                    TextOut(hDc, part->rect.left, part->rect.top - scroll_pos,
                            part->u.text.lpsText, part->u.text.wTextLen);
                    if (part->u.text.wUnderline)
                    {
                        HPEN    hPen;

                        switch (part->u.text.wUnderline)
                        {
                        case 1: /* simple */
                        case 2: /* double */
                            hPen = CreatePen(PS_SOLID, 1, part->u.text.color);
                            break;
                        case 3: /* dotted */
                            hPen = CreatePen(PS_DOT, 1, part->u.text.color);
                            break;
                        default:
                            WINE_FIXME("Unknow underline type\n");
                            continue;
                        }

                        SelectObject(hDc, hPen);
                        MoveToEx(hDc, part->rect.left, part->rect.bottom - scroll_pos - 1, NULL);
                        LineTo(hDc, part->rect.right, part->rect.bottom - scroll_pos - 1);
                        if (part->u.text.wUnderline == 2)
                        {
                            MoveToEx(hDc, part->rect.left, part->rect.bottom - scroll_pos + 1, NULL);
                            LineTo(hDc, part->rect.right, part->rect.bottom - scroll_pos + 1);
                        }
                        DeleteObject(hPen);
                    }
                    break;
                case hlp_line_part_bitmap:
                    {
                        HDC hMemDC;

                        hMemDC = CreateCompatibleDC(hDc);
                        SelectObject(hMemDC, part->u.bitmap.hBitmap);
                        BitBlt(hDc, part->rect.left, part->rect.top - scroll_pos,
                               part->rect.right - part->rect.left, part->rect.bottom - part->rect.top,
                               hMemDC, 0, 0, SRCCOPY);
                        DeleteDC(hMemDC);
                    }
                    break;
                case hlp_line_part_metafile:
                    {
                        POINT   pt;

                        SetViewportOrgEx(hDc, part->rect.left, part->rect.top - scroll_pos, &pt);
                        PlayMetaFile(hDc, part->u.metafile.hMetaFile);
                        SetViewportOrgEx(hDc, pt.x, pt.y, NULL);
                    }
                    break;
                }
            }
        }

        EndPaint(hWnd, &ps);
        break;

    case WM_MOUSEMOVE:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        if (WINHELP_IsOverLink(win, wParam, lParam))
            SetCursor(win->hHandCur); /* set to hand pointer cursor to indicate a link */
        else
            SetCursor(win->hArrowCur); /* set to hand pointer cursor to indicate a link */

        break;

    case WM_LBUTTONDOWN:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        hPopupWnd = Globals.hPopupWnd;
        Globals.hPopupWnd = 0;

        part = WINHELP_IsOverLink(win, wParam, lParam);
        if (part)
        {
            HLPFILE*            hlpfile;
            HLPFILE_WINDOWINFO* wi;

            mouse.x = LOWORD(lParam);
            mouse.y = HIWORD(lParam);

            if (part->link) switch (part->link->cookie)
            {
            case hlp_link_link:
                hlpfile = WINHELP_LookupHelpFile(part->link->lpszString);
                if (part->link->window == -1)
                    wi = win->info;
                else if (part->link->window < hlpfile->numWindows)
                    wi = &hlpfile->windows[part->link->window];
                else
                {
                    WINE_WARN("link to window %d/%d\n", part->link->window, hlpfile->numWindows);
                    break;
                }
                WINHELP_CreateHelpWindowByHash(hlpfile, part->link->lHash, wi,
                                               SW_NORMAL);
                break;
            case hlp_link_popup:
                hlpfile = WINHELP_LookupHelpFile(part->link->lpszString);
                WINHELP_CreateHelpWindowByHash(hlpfile, part->link->lHash, 
                                               WINHELP_GetPopupWindowInfo(hlpfile, hWnd, &mouse),
                                               SW_NORMAL);
                break;
            case hlp_link_macro:
                MACRO_ExecuteMacro(part->link->lpszString);
                break;
            default:
                WINE_FIXME("Unknown link cookie %d\n", part->link->cookie);
            }
        }

        if (hPopupWnd)
            DestroyWindow(hPopupWnd);
        break;

    case WM_NCDESTROY:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);

        if (hWnd == Globals.hPopupWnd) Globals.hPopupWnd = 0;

        bExit = (Globals.wVersion >= 4 && !lstrcmpi(win->lpszName, "main"));

        WINHELP_DeleteWindow(win);

        if (bExit) MACRO_Exit();

        if (!Globals.win_list)
            PostQuitMessage(0);
        break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/******************************************************************
 *		WINHELP_HistoryWndProc
 *
 *
 */
static LRESULT CALLBACK WINHELP_HistoryWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    WINHELP_WINDOW*     win;
    PAINTSTRUCT         ps;
    HDC                 hDc;
    TEXTMETRIC          tm;
    unsigned int        i;
    RECT                r;

    switch (msg)
    {
    case WM_NCCREATE:
        win = (WINHELP_WINDOW*)((LPCREATESTRUCT)lParam)->lpCreateParams;
        SetWindowLong(hWnd, 0, (LONG)win);
        win->hHistoryWnd = hWnd;
        break;
    case WM_CREATE:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        hDc = GetDC(hWnd);
        GetTextMetrics(hDc, &tm);
        GetWindowRect(hWnd, &r);

        r.right = r.left + 30 * tm.tmAveCharWidth;
        r.bottom = r.top + (sizeof(win->history) / sizeof(win->history[0])) * tm.tmHeight;
        AdjustWindowRect(&r, GetWindowLong(hWnd, GWL_STYLE), FALSE);
        if (r.left < 0) {r.right -= r.left; r.left = 0;}
        if (r.top < 0) {r.bottom -= r.top; r.top = 0;}

        MoveWindow(hWnd, r.left, r.top, r.right, r.bottom, TRUE);
        ReleaseDC(hWnd, hDc);
        break;
    case WM_LBUTTONDOWN:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        hDc = GetDC(hWnd);
        GetTextMetrics(hDc, &tm);
        i = HIWORD(lParam) / tm.tmHeight;
        if (i < win->histIndex)
            WINHELP_CreateHelpWindow(win->history[i], win->info, SW_SHOW);
        ReleaseDC(hWnd, hDc);
        break;
    case WM_PAINT:
        hDc = BeginPaint(hWnd, &ps);
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        GetTextMetrics(hDc, &tm);

        for (i = 0; i < win->histIndex; i++)
        {
            TextOut(hDc, 0, i * tm.tmHeight, win->history[i]->lpszTitle, 
                    strlen(win->history[i]->lpszTitle));
        }
        EndPaint(hWnd, &ps);
        break;
    case WM_DESTROY:
        win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
        if (hWnd == win->hHistoryWnd)
            win->hHistoryWnd = 0;
        break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/***********************************************************************
 *
 *           WINHELP_ShadowWndProc
 */
static LRESULT CALLBACK WINHELP_ShadowWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    WINHELP_CheckPopup(msg);
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

/***********************************************************************
 *
 *           SetupText
 */
static void WINHELP_SetupText(HWND hWnd)
{
    HDC  hDc = GetDC(hWnd);
    RECT rect;
    SIZE newsize;

    ShowScrollBar(hWnd, SB_VERT, FALSE);
    if (!WINHELP_SplitLines(hWnd, NULL))
    {
        ShowScrollBar(hWnd, SB_VERT, TRUE);
        GetClientRect(hWnd, &rect);

        WINHELP_SplitLines(hWnd, &newsize);
        SetScrollRange(hWnd, SB_VERT, 0, rect.top + newsize.cy - rect.bottom, TRUE);
    }
    else
    {
        SetScrollPos(hWnd, SB_VERT, 0, FALSE);
        SetScrollRange(hWnd, SB_VERT, 0, 0, FALSE);
    }

    ReleaseDC(hWnd, hDc);
}

/***********************************************************************
 *
 *           WINHELP_AppendText
 */
static BOOL WINHELP_AppendText(WINHELP_LINE ***linep, WINHELP_LINE_PART ***partp,
			       LPSIZE space, LPSIZE textsize,
			       INT *line_ascent, INT ascent,
			       LPCSTR text, UINT textlen,
			       HFONT font, COLORREF color, HLPFILE_LINK *link,
                               unsigned underline)
{
    WINHELP_LINE      *line;
    WINHELP_LINE_PART *part;
    LPSTR ptr;

    if (!*partp) /* New line */
    {
        *line_ascent  = ascent;

        line = HeapAlloc(GetProcessHeap(), 0,
                         sizeof(WINHELP_LINE) + textlen);
        if (!line) return FALSE;

        line->next    = 0;
        part          = &line->first_part;
        ptr           = (char*)line + sizeof(WINHELP_LINE);

        line->rect.top    = (**linep ? (**linep)->rect.bottom : 0) + space->cy;
        line->rect.bottom = line->rect.top;
        line->rect.left   = space->cx;
        line->rect.right  = space->cx;

        if (**linep) *linep = &(**linep)->next;
        **linep = line;
        space->cy = 0;
    }
    else /* Same line */
    {
        line = **linep;

        if (*line_ascent < ascent)
	{
            WINHELP_LINE_PART *p;
            for (p = &line->first_part; p; p = p->next)
	    {
                p->rect.top    += ascent - *line_ascent;
                p->rect.bottom += ascent - *line_ascent;
	    }
            line->rect.bottom += ascent - *line_ascent;
            *line_ascent = ascent;
	}

        part = HeapAlloc(GetProcessHeap(), 0,
                         sizeof(WINHELP_LINE_PART) + textlen);
        if (!part) return FALSE;
        **partp = part;
        ptr     = (char*)part + sizeof(WINHELP_LINE_PART);
    }

    memcpy(ptr, text, textlen);
    part->cookie            = hlp_line_part_text;
    part->rect.left         = line->rect.right + (*partp ? space->cx : 0);
    part->rect.right        = part->rect.left + textsize->cx;
    line->rect.right        = part->rect.right;
    part->rect.top          =
        ((*partp) ? line->rect.top : line->rect.bottom) + *line_ascent - ascent;
    part->rect.bottom       = part->rect.top + textsize->cy;
    line->rect.bottom       = max(line->rect.bottom, part->rect.bottom);
    part->u.text.lpsText    = ptr;
    part->u.text.wTextLen   = textlen;
    part->u.text.hFont      = font;
    part->u.text.color      = color;
    part->u.text.wUnderline = underline;

    WINE_TRACE("Appended text '%*.*s'[%d] @ (%ld,%ld-%ld,%ld)\n",
               part->u.text.wTextLen,
               part->u.text.wTextLen,
               part->u.text.lpsText,
               part->u.text.wTextLen,
               part->rect.left, part->rect.top, part->rect.right, part->rect.bottom);

    part->link = link;
    if (link) link->wRefCount++;

    part->next          = 0;
    *partp              = &part->next;

    space->cx = 0;

    return TRUE;
}

/***********************************************************************
 *
 *           WINHELP_AppendGfxObject
 */
static WINHELP_LINE_PART* WINHELP_AppendGfxObject(WINHELP_LINE ***linep, WINHELP_LINE_PART ***partp,
                                                  LPSIZE space, LPSIZE gfxSize,
                                                  HLPFILE_LINK *link, unsigned pos)
{
    WINHELP_LINE      *line;
    WINHELP_LINE_PART *part;
    LPSTR              ptr;

    if (!*partp || pos == 1) /* New line */
    {
        line = HeapAlloc(GetProcessHeap(), 0, sizeof(WINHELP_LINE));
        if (!line) return NULL;

        line->next    = NULL;
        part          = &line->first_part;

        line->rect.top    = (**linep ? (**linep)->rect.bottom : 0) + space->cy;
        line->rect.bottom = line->rect.top;
        line->rect.left   = space->cx;
        line->rect.right  = space->cx;

        if (**linep) *linep = &(**linep)->next;
        **linep = line;
        space->cy = 0;
        ptr = (char*)line + sizeof(WINHELP_LINE);
    }
    else /* Same line */
    {
        if (pos == 2) WINE_FIXME("Left alignment not handled\n");
        line = **linep;

        part = HeapAlloc(GetProcessHeap(), 0, sizeof(WINHELP_LINE_PART));
        if (!part) return NULL;
        **partp = part;
        ptr = (char*)part + sizeof(WINHELP_LINE_PART);
    }

    /* part->cookie should be set by caller (image or metafile) */
    part->rect.left       = line->rect.right + (*partp ? space->cx : 0);
    part->rect.right      = part->rect.left + gfxSize->cx;
    line->rect.right      = part->rect.right;
    part->rect.top        = (*partp) ? line->rect.top : line->rect.bottom;
    part->rect.bottom     = part->rect.top + gfxSize->cy;
    line->rect.bottom     = max(line->rect.bottom, part->rect.bottom);

    WINE_TRACE("Appended gfx @ (%ld,%ld-%ld,%ld)\n",
               part->rect.left, part->rect.top, part->rect.right, part->rect.bottom);

    part->link = link;
    if (link) link->wRefCount++;

    part->next            = NULL;
    *partp                = &part->next;

    space->cx = 0;

    return part;
}


/***********************************************************************
 *
 *           WINHELP_SplitLines
 */
static BOOL WINHELP_SplitLines(HWND hWnd, LPSIZE newsize)
{
    WINHELP_WINDOW     *win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
    HLPFILE_PARAGRAPH  *p;
    WINHELP_LINE      **line = &win->first_line;
    WINHELP_LINE_PART **part = 0;
    INT                 line_ascent = 0;
    SIZE                space;
    RECT                rect;
    HDC                 hDc;

    if (newsize) newsize->cx = newsize->cy = 0;

    if (!win->page) return TRUE;

    WINHELP_DeleteLines(win);

    GetClientRect(hWnd, &rect);

    rect.top    += INTERNAL_BORDER_WIDTH;
    rect.left   += INTERNAL_BORDER_WIDTH;
    rect.right  -= INTERNAL_BORDER_WIDTH;
    rect.bottom -= INTERNAL_BORDER_WIDTH;

    space.cy = rect.top;
    space.cx = rect.left;

    hDc = GetDC(hWnd);

    for (p = win->page->first_paragraph; p; p = p->next)
    {
        switch (p->cookie)
        {
        case para_normal_text:
        case para_debug_text:
            {
                TEXTMETRIC tm;
                SIZE textsize    = {0, 0};
                LPCSTR text      = p->u.text.lpszText;
                UINT indent      = 0;
                UINT len         = strlen(text);
                unsigned underline = 0;

                HFONT hFont = 0;
                COLORREF color = RGB(0, 0, 0);

                if (p->u.text.wFont < win->page->file->numFonts)
                {
                    HLPFILE*    hlpfile = win->page->file;

                    if (!hlpfile->fonts[p->u.text.wFont].hFont)
                        hlpfile->fonts[p->u.text.wFont].hFont = CreateFontIndirect(&hlpfile->fonts[p->u.text.wFont].LogFont);
                    hFont = hlpfile->fonts[p->u.text.wFont].hFont;
                    color = hlpfile->fonts[p->u.text.wFont].color;
                }
                else
                {
                    UINT  wFont = (p->u.text.wFont < win->fonts_len) ? p->u.text.wFont : 0;

                    hFont = win->fonts[wFont];
                }

                if (p->link && p->link->bClrChange)
                {
                    underline = (p->link->cookie == hlp_link_popup) ? 3 : 1;
                    color = RGB(0, 0x80, 0);
                }
                if (p->cookie == para_debug_text) color = RGB(0xff, 0, 0);

                SelectObject(hDc, hFont);

                GetTextMetrics(hDc, &tm);

                if (p->u.text.wIndent)
                {
                    indent = p->u.text.wIndent * 5 * tm.tmAveCharWidth;
                    if (!part)
                        space.cx = rect.left + indent - 2 * tm.tmAveCharWidth;
                }

                if (p->u.text.wVSpace)
                {
                    part = 0;
                    space.cx = rect.left + indent;
                    space.cy += (p->u.text.wVSpace - 1) * tm.tmHeight;
                }

                if (p->u.text.wHSpace)
                {
                    space.cx += p->u.text.wHSpace * 2 * tm.tmAveCharWidth;
                }

                WINE_TRACE("splitting text %s\n", text);

                while (len)
                {
                    INT free_width = rect.right - (part ? (*line)->rect.right : rect.left) - space.cx;
                    UINT low = 0, curr = len, high = len, textlen = 0;

                    if (free_width > 0)
                    {
                        while (1)
                        {
                            GetTextExtentPoint(hDc, text, curr, &textsize);

                            if (textsize.cx <= free_width) low = curr;
                            else high = curr;

                            if (high <= low + 1) break;

                            if (textsize.cx) curr = (curr * free_width) / textsize.cx;
                            if (curr <= low) curr = low + 1;
                            else if (curr >= high) curr = high - 1;
                        }
                        textlen = low;
                        while (textlen && text[textlen] && text[textlen] != ' ') textlen--;
                    }
                    if (!part && !textlen) textlen = max(low, 1);

                    if (free_width <= 0 || !textlen)
                    {
                        part = 0;
                        space.cx = rect.left + indent;
                        space.cx = min(space.cx, rect.right - rect.left - 1);
                        continue;
                    }

                    WINE_TRACE("\t => %d %*s\n", textlen, textlen, text);

                    if (!WINHELP_AppendText(&line, &part, &space, &textsize,
                                            &line_ascent, tm.tmAscent,
                                            text, textlen, hFont, color, p->link, underline) ||
                        (!newsize && (*line)->rect.bottom > rect.bottom))
                    {
                        ReleaseDC(hWnd, hDc);
                        return FALSE;
                    }

                    if (newsize)
                        newsize->cx = max(newsize->cx, (*line)->rect.right + INTERNAL_BORDER_WIDTH);

                    len -= textlen;
                    text += textlen;
                    if (text[0] == ' ') text++, len--;
                }
            }
            break;
        case para_bitmap:
        case para_metafile:
            {
                SIZE                    gfxSize;
                INT                     free_width;
                WINHELP_LINE_PART*      ref_part;

                if (p->u.gfx.pos & 0x8000)
                {
                    space.cx = rect.left;
                    if (*line)
                        space.cy += (*line)->rect.bottom - (*line)->rect.top;
                    part = 0;
                }

                if (p->cookie == para_bitmap)
                {
                    DIBSECTION              dibs;
                    
                    GetObject(p->u.gfx.u.bmp.hBitmap, sizeof(dibs), &dibs);
                    gfxSize.cx = dibs.dsBm.bmWidth;
                    gfxSize.cy = dibs.dsBm.bmHeight;
                }
                else gfxSize = p->u.gfx.u.mf.mfSize;
                    
                free_width = rect.right - ((part && *line) ? (*line)->rect.right : rect.left) - space.cx;
                if (free_width <= 0)
                {
                    part = NULL;
                    space.cx = rect.left;
                    space.cx = min(space.cx, rect.right - rect.left - 1);
                }
                ref_part = WINHELP_AppendGfxObject(&line, &part, &space, &gfxSize,
                                                   p->link, p->u.gfx.pos);
                if (!ref_part || (!newsize && (*line)->rect.bottom > rect.bottom))
                {
                    return FALSE;
                }
                if (p->cookie == para_bitmap)
                {
                    ref_part->cookie = hlp_line_part_bitmap;
                    ref_part->u.bitmap.hBitmap = p->u.gfx.u.bmp.hBitmap;
                }
                else
                {
                    ref_part->cookie = hlp_line_part_metafile;
                    ref_part->u.metafile.hMetaFile = p->u.gfx.u.mf.hMetaFile;
                }
            }
            break;
        }
    }

    if (newsize)
        newsize->cy = (*line)->rect.bottom + INTERNAL_BORDER_WIDTH;

    ReleaseDC(hWnd, hDc);
    return TRUE;
}

/***********************************************************************
 *
 *           WINHELP_CheckPopup
 */
static void WINHELP_CheckPopup(UINT msg)
{
    if (!Globals.hPopupWnd) return;

    switch (msg)
    {
    case WM_COMMAND:
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_NCLBUTTONDOWN:
    case WM_NCMBUTTONDOWN:
    case WM_NCRBUTTONDOWN:
        DestroyWindow(Globals.hPopupWnd);
        Globals.hPopupWnd = 0;
    }
}

/***********************************************************************
 *
 *           WINHELP_DeleteLines
 */
static void WINHELP_DeleteLines(WINHELP_WINDOW *win)
{
    WINHELP_LINE      *line, *next_line;
    WINHELP_LINE_PART *part, *next_part;
    for (line = win->first_line; line; line = next_line)
    {
        next_line = line->next;
        for (part = &line->first_part; part; part = next_part)
	{
            next_part = part->next;
            HLPFILE_FreeLink(part->link);
            HeapFree(GetProcessHeap(), 0, part);
	}
    }
    win->first_line = 0;
}

/***********************************************************************
 *
 *           WINHELP_DeleteWindow
 */
static void WINHELP_DeleteWindow(WINHELP_WINDOW* win)
{
    WINHELP_WINDOW**    w;
    unsigned int        i;
    WINHELP_BUTTON*     b;
    WINHELP_BUTTON*     bp;

    for (w = &Globals.win_list; *w; w = &(*w)->next)
    {
        if (*w == win)
        {
            *w = win->next;
            break;
        }
    }

    for (b = win->first_button; b; b = bp)
    {
        DestroyWindow(b->hWnd);
        bp = b->next;
        HeapFree(GetProcessHeap(), 0, b);
    }

    if (win->hShadowWnd) DestroyWindow(win->hShadowWnd);
    if (win->hHistoryWnd) DestroyWindow(win->hHistoryWnd);

    for (i = 0; i < win->histIndex; i++)
    {
        HLPFILE_FreeHlpFile(win->history[i]->file);
    }

    for (i = 0; i < win->backIndex; i++)
        HLPFILE_FreeHlpFile(win->back[i]->file);

    if (win->page) HLPFILE_FreeHlpFile(win->page->file);
    WINHELP_DeleteLines(win);
    HeapFree(GetProcessHeap(), 0, win);
}

/***********************************************************************
 *
 *           WINHELP_InitFonts
 */
static void WINHELP_InitFonts(HWND hWnd)
{
    WINHELP_WINDOW *win = (WINHELP_WINDOW*) GetWindowLong(hWnd, 0);
    LOGFONT logfontlist[] = {
        {-10, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        {-12, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        {-12, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        {-12, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        {-12, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        {-10, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"},
        { -8, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 32, "Helv"}};
#define FONTS_LEN (sizeof(logfontlist)/sizeof(*logfontlist))

    static HFONT fonts[FONTS_LEN];
    static BOOL init = 0;

    win->fonts_len = FONTS_LEN;
    win->fonts = fonts;

    if (!init)
    {
        UINT i;

        for (i = 0; i < FONTS_LEN; i++)
	{
            fonts[i] = CreateFontIndirect(&logfontlist[i]);
	}

        init = 1;
    }
}

/***********************************************************************
 *
 *           WINHELP_MessageBoxIDS
 */
INT WINHELP_MessageBoxIDS(UINT ids_text, UINT ids_title, WORD type)
{
    CHAR text[MAX_STRING_LEN];
    CHAR title[MAX_STRING_LEN];

    LoadString(Globals.hInstance, ids_text, text, sizeof(text));
    LoadString(Globals.hInstance, ids_title, title, sizeof(title));

    return MessageBox(0, text, title, type);
}

/***********************************************************************
 *
 *           MAIN_MessageBoxIDS_s
 */
INT WINHELP_MessageBoxIDS_s(UINT ids_text, LPCSTR str, UINT ids_title, WORD type)
{
    CHAR text[MAX_STRING_LEN];
    CHAR title[MAX_STRING_LEN];
    CHAR newtext[MAX_STRING_LEN + MAX_PATHNAME_LEN];

    LoadString(Globals.hInstance, ids_text, text, sizeof(text));
    LoadString(Globals.hInstance, ids_title, title, sizeof(title));
    wsprintf(newtext, text, str);

    return MessageBox(0, newtext, title, type);
}

/******************************************************************
 *		WINHELP_IsOverLink
 *
 *
 */
WINHELP_LINE_PART* WINHELP_IsOverLink(WINHELP_WINDOW* win, WPARAM wParam, LPARAM lParam)
{
    POINT mouse;
    WINHELP_LINE      *line;
    WINHELP_LINE_PART *part;
    int scroll_pos = GetScrollPos(win->hTextWnd, SB_VERT);

    mouse.x = LOWORD(lParam);
    mouse.y = HIWORD(lParam);
    for (line = win->first_line; line; line = line->next)
    {
        for (part = &line->first_part; part; part = part->next)
        {
            if (part->link && 
                part->link->lpszString &&
                part->rect.left   <= mouse.x &&
                part->rect.right  >= mouse.x &&
                part->rect.top    <= mouse.y + scroll_pos &&
                part->rect.bottom >= mouse.y + scroll_pos)
            {
                return part;
            }
        }
    }

    return NULL;
}
