1 /*
2  * Help Viewer
3  *
4  * Copyright    1996 Ulrich Schmid <uschmid@mail.hh.provi.de>
5  *              2002 Sylvain Petreolle <spetreolle@yahoo.fr>
6  *              2002, 2008 Eric Pouech <eric.pouech@wanadoo.fr>
7  *              2004 Ken Belleau <jamez@ivic.qc.ca>
8  *              2008 Kirill K. Smirnov <lich@math.spbu.ru>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include "winhelp.h"
26 
27 #include <richedit.h>
28 #include <commctrl.h>
29 
30 WINHELP_GLOBALS Globals = {3, NULL, TRUE, NULL, NULL, NULL, NULL, NULL, {{{NULL,NULL}},0}, NULL};
31 
32 #define CTL_ID_BUTTON   0x700
33 #define CTL_ID_TEXT     0x701
34 
35 
36 /***********************************************************************
37  *
38  *           WINHELP_InitFonts
39  */
40 static void WINHELP_InitFonts(HWND hWnd)
41 {
42     WINHELP_WINDOW *win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
43     LOGFONTW logfontlist[] = {
44         {-10, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
45         {-12, 0, 0, 0, 700, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
46         {-12, 0, 0, 0, 700, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
47         {-12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
48         {-12, 0, 0, 0, 700, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
49         {-10, 0, 0, 0, 700, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}},
50         { -8, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 32, {'H','e','l','v',0}}};
51 #define FONTS_LEN (sizeof(logfontlist)/sizeof(*logfontlist))
52 
53     static HFONT fonts[FONTS_LEN];
54     static BOOL init = FALSE;
55 
56     win->fonts_len = FONTS_LEN;
57     win->fonts = fonts;
58 
59     if (!init)
60     {
61         UINT i;
62 
63         for (i = 0; i < FONTS_LEN; i++)
64 	{
65             fonts[i] = CreateFontIndirectW(&logfontlist[i]);
66 	}
67 
68         init = TRUE;
69     }
70 }
71 
72 static DWORD CALLBACK WINHELP_RtfStreamIn(DWORD_PTR cookie, BYTE* buff,
73                                           LONG cb, LONG* pcb)
74 {
75     struct RtfData*     rd = (struct RtfData*)cookie;
76 
77     if (rd->where >= rd->ptr) return 1;
78     if (rd->where + cb > rd->ptr)
79         cb = rd->ptr - rd->where;
80     memcpy(buff, rd->where, cb);
81     rd->where += cb;
82     *pcb = cb;
83     return 0;
84 }
85 
86 static void WINHELP_SetupText(HWND hTextWnd, WINHELP_WINDOW* win, ULONG relative)
87 {
88     static const WCHAR emptyW[1];
89     /* At first clear area - needed by EM_POSFROMCHAR/EM_SETSCROLLPOS */
90     SendMessageW(hTextWnd, WM_SETTEXT, 0, (LPARAM)emptyW);
91     SendMessageW(hTextWnd, WM_SETREDRAW, FALSE, 0);
92     SendMessageW(hTextWnd, EM_SETBKGNDCOLOR, 0, (LPARAM)win->info->sr_color);
93     /* set word-wrap to window size (undocumented) */
94     SendMessageW(hTextWnd, EM_SETTARGETDEVICE, 0, 0);
95     if (win->page)
96     {
97         struct RtfData  rd;
98         EDITSTREAM      es;
99         unsigned        cp = 0;
100         POINTL          ptl;
101         POINT           pt;
102 
103 
104         if (HLPFILE_BrowsePage(win->page, &rd, win->font_scale, relative))
105         {
106             rd.where = rd.data;
107             es.dwCookie = (DWORD_PTR)&rd;
108             es.dwError = 0;
109             es.pfnCallback = WINHELP_RtfStreamIn;
110 
111             SendMessageW(hTextWnd, EM_STREAMIN, SF_RTF, (LPARAM)&es);
112             cp = rd.char_pos_rel;
113         }
114         /* FIXME: else leaking potentially the rd.first_link chain */
115         HeapFree(GetProcessHeap(), 0, rd.data);
116         SendMessageW(hTextWnd, EM_POSFROMCHAR, (WPARAM)&ptl, cp ? cp - 1 : 0);
117         pt.x = 0; pt.y = ptl.y;
118         SendMessageW(hTextWnd, EM_SETSCROLLPOS, 0, (LPARAM)&pt);
119     }
120     SendMessageW(hTextWnd, WM_SETREDRAW, TRUE, 0);
121     RedrawWindow(hTextWnd, NULL, NULL, RDW_FRAME|RDW_INVALIDATE);
122 }
123 
124 /***********************************************************************
125  *
126  *           WINHELP_GetOpenFileName
127  */
128 BOOL WINHELP_GetOpenFileName(LPSTR lpszFile, int len)
129 {
130     OPENFILENAMEA openfilename;
131     CHAR szDir[MAX_PATH];
132     CHAR szzFilter[2 * MAX_STRING_LEN + 100];
133     LPSTR p = szzFilter;
134 
135     WINE_TRACE("()\n");
136 
137     LoadStringA(Globals.hInstance, STID_HELP_FILES_HLP, p, MAX_STRING_LEN);
138     p += strlen(p) + 1;
139     strcpy(p, "*.hlp");
140     p += strlen(p) + 1;
141     LoadStringA(Globals.hInstance, STID_ALL_FILES, p, MAX_STRING_LEN);
142     p += strlen(p) + 1;
143     strcpy(p, "*.*");
144     p += strlen(p) + 1;
145     *p = '\0';
146 
147     GetCurrentDirectoryA(sizeof(szDir), szDir);
148 
149     lpszFile[0]='\0';
150 
151     openfilename.lStructSize       = sizeof(openfilename);
152     openfilename.hwndOwner         = (Globals.active_win ? Globals.active_win->hMainWnd : 0);
153     openfilename.hInstance         = Globals.hInstance;
154     openfilename.lpstrFilter       = szzFilter;
155     openfilename.lpstrCustomFilter = 0;
156     openfilename.nMaxCustFilter    = 0;
157     openfilename.nFilterIndex      = 1;
158     openfilename.lpstrFile         = lpszFile;
159     openfilename.nMaxFile          = len;
160     openfilename.lpstrFileTitle    = 0;
161     openfilename.nMaxFileTitle     = 0;
162     openfilename.lpstrInitialDir   = szDir;
163     openfilename.lpstrTitle        = 0;
164     openfilename.Flags             = OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_READONLY;
165     openfilename.nFileOffset       = 0;
166     openfilename.nFileExtension    = 0;
167     openfilename.lpstrDefExt       = 0;
168     openfilename.lCustData         = 0;
169     openfilename.lpfnHook          = 0;
170     openfilename.lpTemplateName    = 0;
171 
172     return GetOpenFileNameA(&openfilename);
173 }
174 
175 /***********************************************************************
176  *
177  *           WINHELP_MessageBoxIDS_s
178  */
179 static INT WINHELP_MessageBoxIDS_s(UINT ids_text, LPCSTR str, UINT ids_title, WORD type)
180 {
181     CHAR text[MAX_STRING_LEN];
182     CHAR newtext[MAX_STRING_LEN + MAX_PATH];
183 
184     LoadStringA(Globals.hInstance, ids_text, text, sizeof(text));
185     wsprintfA(newtext, text, str);
186 
187     return MessageBoxA(0, newtext, MAKEINTRESOURCEA(ids_title), type);
188 }
189 
190 /***********************************************************************
191  *
192  *           WINHELP_LookupHelpFile
193  */
194 HLPFILE* WINHELP_LookupHelpFile(LPCSTR lpszFile)
195 {
196     HLPFILE*        hlpfile;
197     char szFullName[MAX_PATH];
198     char szAddPath[MAX_PATH];
199     char *p;
200 
201     /*
202      * NOTE: This is needed by popup windows only.
203      * In other cases it's not needed but does not hurt though.
204      */
205     if (Globals.active_win && Globals.active_win->page && Globals.active_win->page->file)
206     {
207         strcpy(szAddPath, Globals.active_win->page->file->lpszPath);
208         p = strrchr(szAddPath, '\\');
209         if (p) *p = 0;
210     }
211 
212     /*
213      * FIXME: Should we swap conditions?
214      */
215     if (!SearchPathA(NULL, lpszFile, ".hlp", MAX_PATH, szFullName, NULL) &&
216         !SearchPathA(szAddPath, lpszFile, ".hlp", MAX_PATH, szFullName, NULL))
217     {
218         if (WINHELP_MessageBoxIDS_s(STID_FILE_NOT_FOUND_s, lpszFile, STID_WHERROR,
219                                     MB_YESNO|MB_ICONQUESTION) != IDYES)
220             return NULL;
221         if (!WINHELP_GetOpenFileName(szFullName, MAX_PATH))
222             return NULL;
223     }
224     hlpfile = HLPFILE_ReadHlpFile(szFullName);
225     if (!hlpfile)
226         WINHELP_MessageBoxIDS_s(STID_HLPFILE_ERROR_s, lpszFile,
227                                 STID_WHERROR, MB_OK|MB_ICONSTOP);
228     return hlpfile;
229 }
230 
231 /******************************************************************
232  *		WINHELP_GetWindowInfo
233  *
234  *
235  */
236 HLPFILE_WINDOWINFO*     WINHELP_GetWindowInfo(HLPFILE* hlpfile, LPCSTR name)
237 {
238     static      HLPFILE_WINDOWINFO      mwi;
239     unsigned int     i;
240 
241     if (!name || !name[0])
242         name = Globals.active_win->info->name;
243 
244     if (hlpfile)
245         for (i = 0; i < hlpfile->numWindows; i++)
246             if (!lstrcmpiA(hlpfile->windows[i].name, name))
247                 return &hlpfile->windows[i];
248 
249     if (strcmp(name, "main") != 0)
250     {
251         WINE_FIXME("Couldn't find window info for %s\n", debugstr_a(name));
252         assert(0);
253         return NULL;
254     }
255     if (!mwi.name[0])
256     {
257         strcpy(mwi.type, "primary");
258         strcpy(mwi.name, "main");
259         if (hlpfile && hlpfile->lpszTitle[0])
260         {
261             char        tmp[128];
262             LoadStringA(Globals.hInstance, STID_WINE_HELP, tmp, sizeof(tmp));
263             snprintf(mwi.caption, sizeof(mwi.caption), "%s %s - %s",
264                      hlpfile->lpszTitle, tmp, hlpfile->lpszPath);
265         }
266         else
267             LoadStringA(Globals.hInstance, STID_WINE_HELP, mwi.caption, sizeof(mwi.caption));
268         mwi.origin.x = mwi.origin.y = mwi.size.cx = mwi.size.cy = CW_USEDEFAULT;
269         mwi.style = SW_SHOW;
270         mwi.win_style = WS_OVERLAPPEDWINDOW;
271         mwi.sr_color = mwi.nsr_color = 0xFFFFFF;
272     }
273     return &mwi;
274 }
275 
276 /******************************************************************
277  *		HLPFILE_GetPopupWindowInfo
278  *
279  *
280  */
281 static HLPFILE_WINDOWINFO*     WINHELP_GetPopupWindowInfo(HLPFILE* hlpfile,
282                                                           WINHELP_WINDOW* parent, LPARAM mouse)
283 {
284     static      HLPFILE_WINDOWINFO      wi;
285 
286     RECT parent_rect;
287 
288     wi.type[0] = wi.name[0] = wi.caption[0] = '\0';
289 
290     /* Calculate horizontal size and position of a popup window */
291     GetWindowRect(parent->hMainWnd, &parent_rect);
292     wi.size.cx = (parent_rect.right  - parent_rect.left) / 2;
293     wi.size.cy = 10; /* need a non null value, so that borders are taken into account while computing */
294 
295     wi.origin.x = (short)LOWORD(mouse);
296     wi.origin.y = (short)HIWORD(mouse);
297     ClientToScreen(parent->hMainWnd, &wi.origin);
298     wi.origin.x -= wi.size.cx / 2;
299     wi.origin.x  = min(wi.origin.x, GetSystemMetrics(SM_CXSCREEN) - wi.size.cx);
300     wi.origin.x  = max(wi.origin.x, 0);
301 
302     wi.style = SW_SHOW;
303     wi.win_style = WS_POPUP | WS_BORDER;
304     if (parent->page->file->has_popup_color)
305         wi.sr_color = parent->page->file->popup_color;
306     else
307         wi.sr_color = parent->info->sr_color;
308     wi.nsr_color = 0xFFFFFF;
309 
310     return &wi;
311 }
312 
313 typedef struct
314 {
315     WORD size;
316     WORD command;
317     LONG data;
318     LONG reserved;
319     WORD ofsFilename;
320     WORD ofsData;
321 } WINHELP,*LPWINHELP;
322 
323 static BOOL WINHELP_HasWorkingWindow(void)
324 {
325     if (!Globals.active_win) return FALSE;
326     if (Globals.active_win->next || Globals.win_list != Globals.active_win) return TRUE;
327     return Globals.active_win->page != NULL && Globals.active_win->page->file != NULL;
328 }
329 
330 /******************************************************************
331  *		WINHELP_HandleCommand
332  *
333  *
334  */
335 static LRESULT  WINHELP_HandleCommand(HWND hSrcWnd, LPARAM lParam)
336 {
337     COPYDATASTRUCT*     cds = (COPYDATASTRUCT*)lParam;
338     WINHELP*            wh;
339 
340     if (cds->dwData != 0xA1DE505)
341     {
342         WINE_FIXME("Wrong magic number (%08lx)\n", cds->dwData);
343         return 0;
344     }
345 
346     wh = cds->lpData;
347 
348     if (wh)
349     {
350         char*   ptr = (wh->ofsFilename) ? (LPSTR)wh + wh->ofsFilename : NULL;
351 
352         WINE_TRACE("Got[%u]: cmd=%u data=%08x fn=%s\n",
353                    wh->size, wh->command, wh->data, debugstr_a(ptr));
354         switch (wh->command)
355         {
356         case HELP_CONTEXT:
357             if (ptr)
358             {
359                 MACRO_JumpContext(ptr, "main", wh->data);
360             }
361             if (!WINHELP_HasWorkingWindow()) MACRO_Exit();
362             break;
363         case HELP_QUIT:
364             MACRO_Exit();
365             break;
366         case HELP_CONTENTS:
367             if (ptr)
368             {
369                 MACRO_JumpContents(ptr, "main");
370             }
371             if (!WINHELP_HasWorkingWindow()) MACRO_Exit();
372             break;
373         case HELP_HELPONHELP:
374             MACRO_HelpOn();
375             if (!WINHELP_HasWorkingWindow()) MACRO_Exit();
376             break;
377         /* case HELP_SETINDEX: */
378         case HELP_SETCONTENTS:
379             if (ptr)
380             {
381                 MACRO_SetContents(ptr, wh->data);
382             }
383             break;
384         case HELP_CONTEXTPOPUP:
385             if (ptr)
386             {
387                 MACRO_PopupContext(ptr, wh->data);
388             }
389             break;
390         /* case HELP_FORCEFILE:*/
391         /* case HELP_CONTEXTMENU: */
392         case HELP_FINDER:
393             /* in fact, should be the topic dialog box */
394             WINE_FIXME("HELP_FINDER: stub\n");
395             if (ptr)
396             {
397                 MACRO_JumpHash(ptr, "main", 0);
398             }
399             break;
400         /* case HELP_WM_HELP: */
401         /* case HELP_SETPOPUP_POS: */
402         /* case HELP_KEY: */
403         /* case HELP_COMMAND: */
404         /* case HELP_PARTIALKEY: */
405         /* case HELP_MULTIKEY: */
406         /* case HELP_SETWINPOS: */
407         default:
408             WINE_FIXME("Unhandled command (%x) for remote winhelp control\n", wh->command);
409             break;
410         }
411     }
412     /* Always return success for now */
413     return 1;
414 }
415 
416 void            WINHELP_LayoutMainWindow(WINHELP_WINDOW* win)
417 {
418     RECT        rect, button_box_rect;
419     INT         text_top = 0;
420     HWND        hButtonBoxWnd = GetDlgItem(win->hMainWnd, CTL_ID_BUTTON);
421     HWND        hTextWnd = GetDlgItem(win->hMainWnd, CTL_ID_TEXT);
422 
423     GetClientRect(win->hMainWnd, &rect);
424 
425     /* Update button box and text Window */
426     SetWindowPos(hButtonBoxWnd, HWND_TOP,
427                  rect.left, rect.top,
428                  rect.right - rect.left,
429                  rect.bottom - rect.top, 0);
430 
431     if (GetWindowRect(hButtonBoxWnd, &button_box_rect))
432         text_top = rect.top + button_box_rect.bottom - button_box_rect.top;
433 
434     SetWindowPos(hTextWnd, HWND_TOP,
435                  rect.left, text_top,
436                  rect.right - rect.left,
437                  rect.bottom - text_top, 0);
438 
439 }
440 
441 /******************************************************************
442  *		WINHELP_DeleteButtons
443  *
444  */
445 static void WINHELP_DeleteButtons(WINHELP_WINDOW* win)
446 {
447     WINHELP_BUTTON*     b;
448     WINHELP_BUTTON*     bp;
449 
450     for (b = win->first_button; b; b = bp)
451     {
452         DestroyWindow(b->hWnd);
453         bp = b->next;
454         HeapFree(GetProcessHeap(), 0, b);
455     }
456     win->first_button = NULL;
457 }
458 
459 /******************************************************************
460  *		WINHELP_DeleteBackSet
461  *
462  */
463 void WINHELP_DeleteBackSet(WINHELP_WINDOW* win)
464 {
465     unsigned int i;
466 
467     for (i = 0; i < win->back.index; i++)
468     {
469         HLPFILE_FreeHlpFile(win->back.set[i].page->file);
470         win->back.set[i].page = NULL;
471     }
472     win->back.index = 0;
473 }
474 
475 /******************************************************************
476  *             WINHELP_DeletePageLinks
477  *
478  */
479 static void WINHELP_DeletePageLinks(HLPFILE_PAGE* page)
480 {
481     HLPFILE_LINK*       curr;
482     HLPFILE_LINK*       next;
483 
484     for (curr = page->first_link; curr; curr = next)
485     {
486         next = curr->next;
487         HeapFree(GetProcessHeap(), 0, curr);
488     }
489 }
490 
491 /***********************************************************************
492  *
493  *           WINHELP_GrabWindow
494  */
495 WINHELP_WINDOW* WINHELP_GrabWindow(WINHELP_WINDOW* win)
496 {
497     WINE_TRACE("Grab %p#%d++\n", win, win->ref_count);
498     win->ref_count++;
499     return win;
500 }
501 
502 /***********************************************************************
503  *
504  *           WINHELP_ReleaseWindow
505  */
506 BOOL WINHELP_ReleaseWindow(WINHELP_WINDOW* win)
507 {
508     WINE_TRACE("Release %p#%d--\n", win, win->ref_count);
509 
510     if (!--win->ref_count)
511     {
512         DestroyWindow(win->hMainWnd);
513         return FALSE;
514     }
515     return TRUE;
516 }
517 
518 /***********************************************************************
519  *
520  *           WINHELP_DeleteWindow
521  */
522 static void WINHELP_DeleteWindow(WINHELP_WINDOW* win)
523 {
524     WINHELP_WINDOW**    w;
525     BOOL bExit;
526     HWND hTextWnd;
527 
528     for (w = &Globals.win_list; *w; w = &(*w)->next)
529     {
530         if (*w == win)
531         {
532             *w = win->next;
533             break;
534         }
535     }
536     bExit = (Globals.wVersion >= 4 && !lstrcmpiA(win->info->name, "main"));
537 
538     if (Globals.active_win == win)
539     {
540         Globals.active_win = Globals.win_list;
541         if (Globals.win_list)
542             SetActiveWindow(Globals.win_list->hMainWnd);
543     }
544 
545     if (win == Globals.active_popup)
546         Globals.active_popup = NULL;
547 
548     hTextWnd = GetDlgItem(win->hMainWnd, CTL_ID_TEXT);
549     SetWindowLongPtrA(hTextWnd, GWLP_WNDPROC, (LONG_PTR)win->origRicheditWndProc);
550 
551     WINHELP_DeleteButtons(win);
552 
553     if (win->page) WINHELP_DeletePageLinks(win->page);
554     if (win->hHistoryWnd) DestroyWindow(win->hHistoryWnd);
555 
556     DeleteObject(win->hBrush);
557 
558     WINHELP_DeleteBackSet(win);
559 
560     if (win->page) HLPFILE_FreeHlpFile(win->page->file);
561     HeapFree(GetProcessHeap(), 0, win);
562 
563     if (bExit) MACRO_Exit();
564     if (!Globals.win_list)
565         PostQuitMessage(0);
566 }
567 
568 static char* WINHELP_GetCaption(WINHELP_WNDPAGE* wpage)
569 {
570     if (wpage->wininfo->caption[0]) return wpage->wininfo->caption;
571     return wpage->page->file->lpszTitle;
572 }
573 
574 static void WINHELP_RememberPage(WINHELP_WINDOW* win, WINHELP_WNDPAGE* wpage)
575 {
576     unsigned        num;
577 
578     if (!Globals.history.index || Globals.history.set[0].page != wpage->page)
579     {
580         num = sizeof(Globals.history.set) / sizeof(Globals.history.set[0]);
581         /* we're full, remove latest entry */
582         if (Globals.history.index == num)
583         {
584             HLPFILE_FreeHlpFile(Globals.history.set[num - 1].page->file);
585             Globals.history.index--;
586         }
587         memmove(&Globals.history.set[1], &Globals.history.set[0],
588                 Globals.history.index * sizeof(Globals.history.set[0]));
589         Globals.history.set[0] = *wpage;
590         Globals.history.index++;
591         wpage->page->file->wRefCount++;
592     }
593     if (win->hHistoryWnd) InvalidateRect(win->hHistoryWnd, NULL, TRUE);
594 
595     num = sizeof(win->back.set) / sizeof(win->back.set[0]);
596     if (win->back.index == num)
597     {
598         /* we're full, remove latest entry */
599         HLPFILE_FreeHlpFile(win->back.set[0].page->file);
600         memmove(&win->back.set[0], &win->back.set[1],
601                 (num - 1) * sizeof(win->back.set[0]));
602         win->back.index--;
603     }
604     win->back.set[win->back.index++] = *wpage;
605     wpage->page->file->wRefCount++;
606 }
607 
608 /***********************************************************************
609  *
610  *           WINHELP_FindLink
611  */
612 static HLPFILE_LINK* WINHELP_FindLink(WINHELP_WINDOW* win, LPARAM pos)
613 {
614     HLPFILE_LINK*           link;
615     POINTL                  mouse_ptl, char_ptl, char_next_ptl;
616     DWORD                   cp;
617 
618     if (!win->page) return NULL;
619 
620     mouse_ptl.x = (short)LOWORD(pos);
621     mouse_ptl.y = (short)HIWORD(pos);
622     cp = SendMessageW(GetDlgItem(win->hMainWnd, CTL_ID_TEXT), EM_CHARFROMPOS,
623                       0, (LPARAM)&mouse_ptl);
624 
625     for (link = win->page->first_link; link; link = link->next)
626     {
627         if (link->cpMin <= cp && cp <= link->cpMax)
628         {
629             /* check whether we're at end of line */
630             SendMessageW(GetDlgItem(win->hMainWnd, CTL_ID_TEXT), EM_POSFROMCHAR,
631                          (LPARAM)&char_ptl, cp);
632             SendMessageW(GetDlgItem(win->hMainWnd, CTL_ID_TEXT), EM_POSFROMCHAR,
633                          (LPARAM)&char_next_ptl, cp + 1);
634             if (link->bHotSpot)
635             {
636                 HLPFILE_HOTSPOTLINK*    hslink = (HLPFILE_HOTSPOTLINK*)link;
637                 if ((mouse_ptl.x < char_ptl.x + hslink->x) ||
638                     (mouse_ptl.x >= char_ptl.x + hslink->x + hslink->width) ||
639                     (mouse_ptl.y < char_ptl.y + hslink->y) ||
640                     (mouse_ptl.y >= char_ptl.y + hslink->y + hslink->height))
641                     continue;
642                 break;
643             }
644             if (char_next_ptl.y != char_ptl.y || mouse_ptl.x >= char_next_ptl.x)
645                 link = NULL;
646             break;
647         }
648     }
649     return link;
650 }
651 
652 static LRESULT CALLBACK WINHELP_RicheditWndProc(HWND hWnd, UINT msg,
653                                                 WPARAM wParam, LPARAM lParam)
654 {
655     WINHELP_WINDOW *win = (WINHELP_WINDOW*) GetWindowLongPtrW(GetParent(hWnd), 0);
656     DWORD messagePos;
657     POINT pt;
658     switch(msg)
659     {
660         case WM_SETCURSOR:
661             messagePos = GetMessagePos();
662             pt.x = (short)LOWORD(messagePos);
663             pt.y = (short)HIWORD(messagePos);
664             ScreenToClient(hWnd, &pt);
665             if (win->page && WINHELP_FindLink(win, MAKELPARAM(pt.x, pt.y)))
666             {
667                 SetCursor(win->hHandCur);
668                 return 0;
669             }
670             /* fall through */
671         default:
672             return CallWindowProcA(win->origRicheditWndProc, hWnd, msg, wParam, lParam);
673     }
674 }
675 
676 /***********************************************************************
677  *
678  *           WINHELP_CreateHelpWindow
679  */
680 BOOL WINHELP_CreateHelpWindow(WINHELP_WNDPAGE* wpage, int nCmdShow, BOOL remember)
681 {
682     WINHELP_WINDOW*     win = NULL;
683     BOOL                bPrimary, bPopup, bReUsed = FALSE;
684     HICON               hIcon;
685     HWND                hTextWnd = NULL;
686 
687     bPrimary = !lstrcmpiA(wpage->wininfo->name, "main");
688     bPopup = !bPrimary && (wpage->wininfo->win_style & WS_POPUP);
689 
690     if (!bPopup)
691     {
692         for (win = Globals.win_list; win; win = win->next)
693         {
694             if (!lstrcmpiA(win->info->name, wpage->wininfo->name))
695             {
696                 if (win->page == wpage->page && win->info == wpage->wininfo)
697                 {
698                     /* see #22979, some hlp files have a macro (run at page opening), which
699                      * jumps to the very same page
700                      * Exit gracefully in that case
701                      */
702                     return TRUE;
703                 }
704                 WINHELP_DeleteButtons(win);
705                 bReUsed = TRUE;
706                 SetWindowTextA(win->hMainWnd, WINHELP_GetCaption(wpage));
707                 if (win->info != wpage->wininfo)
708                 {
709                     POINT   pt = {0, 0};
710                     SIZE    sz = {0, 0};
711                     DWORD   flags = SWP_NOSIZE | SWP_NOMOVE;
712 
713                     if (wpage->wininfo->origin.x != CW_USEDEFAULT &&
714                         wpage->wininfo->origin.y != CW_USEDEFAULT)
715                     {
716                         pt = wpage->wininfo->origin;
717                         flags &= ~SWP_NOSIZE;
718                     }
719                     if (wpage->wininfo->size.cx != CW_USEDEFAULT &&
720                         wpage->wininfo->size.cy != CW_USEDEFAULT)
721                     {
722                         sz = wpage->wininfo->size;
723                         flags &= ~SWP_NOMOVE;
724                     }
725                     SetWindowPos(win->hMainWnd, HWND_TOP, pt.x, pt.y, sz.cx, sz.cy, flags);
726                 }
727 
728                 if (wpage->page && win->page && wpage->page->file != win->page->file)
729                     WINHELP_DeleteBackSet(win);
730                 WINHELP_InitFonts(win->hMainWnd);
731 
732                 win->page = wpage->page;
733                 win->info = wpage->wininfo;
734                 hTextWnd = GetDlgItem(win->hMainWnd, CTL_ID_TEXT);
735                 WINHELP_SetupText(hTextWnd, win, wpage->relative);
736 
737                 InvalidateRect(win->hMainWnd, NULL, TRUE);
738                 if (win->hHistoryWnd) InvalidateRect(win->hHistoryWnd, NULL, TRUE);
739 
740                 break;
741             }
742         }
743     }
744 
745     if (!win)
746     {
747         /* Initialize WINHELP_WINDOW struct */
748         win = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINHELP_WINDOW));
749         if (!win) return FALSE;
750         win->next = Globals.win_list;
751         Globals.win_list = win;
752 
753         win->hHandCur = LoadCursorW(0, (LPWSTR)IDC_HAND);
754         win->back.index = 0;
755         win->font_scale = 1;
756         WINHELP_GrabWindow(win);
757     }
758     win->page = wpage->page;
759     win->info = wpage->wininfo;
760     WINHELP_GrabWindow(win);
761 
762     if (!bPopup && wpage->page && remember)
763     {
764         WINHELP_RememberPage(win, wpage);
765     }
766 
767     if (bPopup)
768         Globals.active_popup = win;
769     else
770         Globals.active_win = win;
771 
772     /* Initialize default pushbuttons */
773     if (bPrimary && wpage->page)
774     {
775         CHAR    buffer[MAX_STRING_LEN];
776 
777         LoadStringA(Globals.hInstance, STID_CONTENTS, buffer, sizeof(buffer));
778         MACRO_CreateButton("BTN_CONTENTS", buffer, "Contents()");
779         LoadStringA(Globals.hInstance, STID_INDEX, buffer, sizeof(buffer));
780         MACRO_CreateButton("BTN_INDEX", buffer, "Finder()");
781         LoadStringA(Globals.hInstance, STID_BACK, buffer, sizeof(buffer));
782         MACRO_CreateButton("BTN_BACK", buffer, "Back()");
783         if (win->back.index <= 1) MACRO_DisableButton("BTN_BACK");
784     }
785 
786     if (!bReUsed)
787     {
788         win->hMainWnd = CreateWindowExA((bPopup) ? WS_EX_TOOLWINDOW : 0, MAIN_WIN_CLASS_NAME,
789                                        WINHELP_GetCaption(wpage),
790                                        bPrimary ? WS_OVERLAPPEDWINDOW : wpage->wininfo->win_style,
791                                        wpage->wininfo->origin.x, wpage->wininfo->origin.y,
792                                        wpage->wininfo->size.cx, wpage->wininfo->size.cy,
793                                        bPopup ? Globals.active_win->hMainWnd : NULL,
794                                        bPrimary ? LoadMenuW(Globals.hInstance, MAKEINTRESOURCEW(MAIN_MENU)) : 0,
795                                        Globals.hInstance, win);
796         if (!bPopup)
797             /* Create button box and text Window */
798             CreateWindowA(BUTTON_BOX_WIN_CLASS_NAME, "", WS_CHILD | WS_VISIBLE,
799                          0, 0, 0, 0, win->hMainWnd, (HMENU)CTL_ID_BUTTON, Globals.hInstance, NULL);
800 
801         hTextWnd = CreateWindowA(RICHEDIT_CLASS20A, NULL,
802                                 ES_MULTILINE | ES_READONLY | WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE,
803                                 0, 0, 0, 0, win->hMainWnd, (HMENU)CTL_ID_TEXT, Globals.hInstance, NULL);
804         SendMessageW(hTextWnd, EM_SETEVENTMASK, 0,
805                     SendMessageW(hTextWnd, EM_GETEVENTMASK, 0, 0) | ENM_MOUSEEVENTS);
806         win->origRicheditWndProc = (WNDPROC)SetWindowLongPtrA(hTextWnd, GWLP_WNDPROC,
807                                                              (LONG_PTR)WINHELP_RicheditWndProc);
808     }
809 
810     hIcon = (wpage->page) ? wpage->page->file->hIcon : NULL;
811     if (!hIcon) hIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_WINHELP), IMAGE_ICON,
812                                   GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED);
813     SendMessageW(win->hMainWnd, WM_SETICON, ICON_SMALL, (DWORD_PTR)hIcon);
814 
815     /* Initialize file specific pushbuttons */
816     if (!(wpage->wininfo->win_style & WS_POPUP) && wpage->page)
817     {
818         HLPFILE_MACRO  *macro;
819         for (macro = wpage->page->file->first_macro; macro; macro = macro->next)
820             MACRO_ExecuteMacro(win, macro->lpszMacro);
821 
822         for (macro = wpage->page->first_macro; macro; macro = macro->next)
823             MACRO_ExecuteMacro(win, macro->lpszMacro);
824     }
825     /* See #17681, in some cases, the newly created window is closed by the macros it contains
826      * (braindead), so deal with this case
827      */
828     for (win = Globals.win_list; win; win = win->next)
829     {
830         if (!lstrcmpiA(win->info->name, wpage->wininfo->name)) break;
831     }
832     if (!win || !WINHELP_ReleaseWindow(win)) return TRUE;
833 
834     if (bPopup)
835     {
836         DWORD   mask = SendMessageW(hTextWnd, EM_GETEVENTMASK, 0, 0);
837 
838         win->font_scale = Globals.active_win->font_scale;
839         WINHELP_SetupText(hTextWnd, win, wpage->relative);
840 
841         /* we need the window to be shown for richedit to compute the size */
842         ShowWindow(win->hMainWnd, nCmdShow);
843         SendMessageW(hTextWnd, EM_SETEVENTMASK, 0, mask | ENM_REQUESTRESIZE);
844         SendMessageW(hTextWnd, EM_REQUESTRESIZE, 0, 0);
845         SendMessageW(hTextWnd, EM_SETEVENTMASK, 0, mask);
846     }
847     else
848     {
849         WINHELP_SetupText(hTextWnd, win, wpage->relative);
850         WINHELP_LayoutMainWindow(win);
851         ShowWindow(win->hMainWnd, nCmdShow);
852     }
853 
854     return TRUE;
855 }
856 
857 /******************************************************************
858  *             WINHELP_OpenHelpWindow
859  * Main function to search for a page and display it in a window
860  */
861 BOOL WINHELP_OpenHelpWindow(HLPFILE_PAGE* (*lookup)(HLPFILE*, LONG, ULONG*),
862                             HLPFILE* hlpfile, LONG val, HLPFILE_WINDOWINFO* wi,
863                             int nCmdShow)
864 {
865     WINHELP_WNDPAGE     wpage;
866 
867     wpage.page = lookup(hlpfile, val, &wpage.relative);
868     if (wpage.page) wpage.page->file->wRefCount++;
869     wpage.wininfo = wi;
870     return WINHELP_CreateHelpWindow(&wpage, nCmdShow, TRUE);
871 }
872 
873 /******************************************************************
874  *             WINHELP_HandleTextMouse
875  *
876  */
877 static BOOL WINHELP_HandleTextMouse(WINHELP_WINDOW* win, UINT msg, LPARAM lParam)
878 {
879     HLPFILE*                hlpfile;
880     HLPFILE_LINK*           link;
881     BOOL                    ret = FALSE;
882 
883     switch (msg)
884     {
885     case WM_LBUTTONDOWN:
886         if ((link = WINHELP_FindLink(win, lParam)))
887         {
888             HLPFILE_WINDOWINFO*     wi;
889 
890             switch (link->cookie)
891             {
892             case hlp_link_link:
893                 if ((hlpfile = WINHELP_LookupHelpFile(link->string)))
894                 {
895                     if (link->window == -1)
896                     {
897                         wi = win->info;
898                         if (wi->win_style & WS_POPUP) wi = Globals.active_win->info;
899                     }
900                     else if (link->window < hlpfile->numWindows)
901                         wi = &hlpfile->windows[link->window];
902                     else
903                     {
904                         WINE_WARN("link to window %d/%d\n", link->window, hlpfile->numWindows);
905                         break;
906                     }
907                     WINHELP_OpenHelpWindow(HLPFILE_PageByHash, hlpfile, link->hash, wi, SW_NORMAL);
908                 }
909                 break;
910             case hlp_link_popup:
911                 if ((hlpfile = WINHELP_LookupHelpFile(link->string)))
912                     WINHELP_OpenHelpWindow(HLPFILE_PageByHash, hlpfile, link->hash,
913                                            WINHELP_GetPopupWindowInfo(hlpfile, win, lParam),
914                                            SW_NORMAL);
915                 break;
916             case hlp_link_macro:
917                 MACRO_ExecuteMacro(win, link->string);
918                 break;
919             default:
920                 WINE_FIXME("Unknown link cookie %d\n", link->cookie);
921             }
922             ret = TRUE;
923         }
924         break;
925     }
926     return ret;
927 }
928 
929 /***********************************************************************
930  *
931  *           WINHELP_CheckPopup
932  */
933 static BOOL WINHELP_CheckPopup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT* lret)
934 {
935     WINHELP_WINDOW*     popup;
936 
937     if (!Globals.active_popup) return FALSE;
938 
939     switch (msg)
940     {
941     case WM_NOTIFY:
942         {
943             MSGFILTER*  msgf = (MSGFILTER*)lParam;
944             if (msgf->nmhdr.code == EN_MSGFILTER)
945             {
946                 if (!WINHELP_CheckPopup(hWnd, msgf->msg, msgf->wParam, msgf->lParam, NULL))
947                     return FALSE;
948                 if (lret) *lret = 1;
949                 return TRUE;
950             }
951         }
952         break;
953     case WM_ACTIVATE:
954         if (LOWORD(wParam) != WA_INACTIVE || (HWND)lParam == Globals.active_win->hMainWnd ||
955             (HWND)lParam == Globals.active_popup->hMainWnd ||
956             GetWindow((HWND)lParam, GW_OWNER) == Globals.active_win->hMainWnd)
957             break;
958         /* fall through */
959     case WM_LBUTTONDOWN:
960         if (msg == WM_LBUTTONDOWN)
961             WINHELP_HandleTextMouse(Globals.active_popup, msg, lParam);
962         /* fall through */
963     case WM_MBUTTONDOWN:
964     case WM_RBUTTONDOWN:
965     case WM_NCLBUTTONDOWN:
966     case WM_NCMBUTTONDOWN:
967     case WM_NCRBUTTONDOWN:
968         popup = Globals.active_popup;
969         Globals.active_popup = NULL;
970         WINHELP_ReleaseWindow(popup);
971         if (lret) *lret = 1;
972         return TRUE;
973     }
974     return FALSE;
975 }
976 
977 /***********************************************************************
978  *
979  *           WINHELP_ButtonWndProc
980  */
981 static LRESULT CALLBACK WINHELP_ButtonWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
982 {
983     if (WINHELP_CheckPopup(hWnd, msg, wParam, lParam, NULL)) return 0;
984 
985     if (msg == WM_KEYDOWN)
986     {
987         switch (wParam)
988         {
989         case VK_UP:
990         case VK_DOWN:
991         case VK_PRIOR:
992         case VK_NEXT:
993         case VK_ESCAPE:
994             return SendMessageA(GetParent(hWnd), msg, wParam, lParam);
995         }
996     }
997 
998     return CallWindowProcA(Globals.button_proc, hWnd, msg, wParam, lParam);
999 }
1000 
1001 /***********************************************************************
1002  *
1003  *           WINHELP_ButtonBoxWndProc
1004  */
1005 static LRESULT CALLBACK WINHELP_ButtonBoxWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1006 {
1007     WINDOWPOS      *winpos;
1008     WINHELP_WINDOW *win;
1009     WINHELP_BUTTON *button;
1010     SIZE button_size;
1011     INT  x, y;
1012 
1013     if (WINHELP_CheckPopup(hWnd, msg, wParam, lParam, NULL)) return 0L;
1014 
1015     switch (msg)
1016     {
1017     case WM_WINDOWPOSCHANGING:
1018         winpos = (WINDOWPOS*) lParam;
1019         win = (WINHELP_WINDOW*) GetWindowLongPtrW(GetParent(hWnd), 0);
1020 
1021         /* Update buttons */
1022         button_size.cx = 0;
1023         button_size.cy = 0;
1024         for (button = win->first_button; button; button = button->next)
1025 	{
1026             HDC  hDc;
1027             SIZE textsize;
1028             if (!button->hWnd)
1029             {
1030                 button->hWnd = CreateWindowA(STRING_BUTTON, button->lpszName,
1031                                             WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
1032                                             0, 0, 0, 0,
1033                                             hWnd, (HMENU) button->wParam,
1034                                             Globals.hInstance, 0);
1035                 if (button->hWnd)
1036                 {
1037                     if (Globals.button_proc == NULL)
1038                     {
1039                         NONCLIENTMETRICSW ncm;
1040                         Globals.button_proc = (WNDPROC) GetWindowLongPtrA(button->hWnd, GWLP_WNDPROC);
1041 
1042                         ncm.cbSize = sizeof(NONCLIENTMETRICSW);
1043                         SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
1044                                               sizeof(NONCLIENTMETRICSW), &ncm, 0);
1045                         Globals.hButtonFont = CreateFontIndirectW(&ncm.lfMenuFont);
1046                     }
1047                     SetWindowLongPtrA(button->hWnd, GWLP_WNDPROC, (LONG_PTR) WINHELP_ButtonWndProc);
1048                     if (Globals.hButtonFont)
1049                         SendMessageW(button->hWnd, WM_SETFONT, (WPARAM)Globals.hButtonFont, TRUE);
1050                 }
1051             }
1052             hDc = GetDC(button->hWnd);
1053             GetTextExtentPointA(hDc, button->lpszName, strlen(button->lpszName), &textsize);
1054             ReleaseDC(button->hWnd, hDc);
1055 
1056             button_size.cx = max(button_size.cx, textsize.cx + BUTTON_CX);
1057             button_size.cy = max(button_size.cy, textsize.cy + BUTTON_CY);
1058 	}
1059 
1060         x = 0;
1061         y = 0;
1062         for (button = win->first_button; button; button = button->next)
1063 	{
1064             SetWindowPos(button->hWnd, HWND_TOP, x, y, button_size.cx, button_size.cy, 0);
1065 
1066             if (x + 2 * button_size.cx <= winpos->cx)
1067                 x += button_size.cx;
1068             else
1069                 x = 0, y += button_size.cy;
1070 	}
1071         winpos->cy = y + (x ? button_size.cy : 0);
1072         break;
1073 
1074     case WM_COMMAND:
1075         SendMessageW(GetParent(hWnd), msg, wParam, lParam);
1076         break;
1077 
1078     case WM_KEYDOWN:
1079         switch (wParam)
1080         {
1081         case VK_UP:
1082         case VK_DOWN:
1083         case VK_PRIOR:
1084         case VK_NEXT:
1085         case VK_ESCAPE:
1086             return SendMessageA(GetParent(hWnd), msg, wParam, lParam);
1087         }
1088         break;
1089     }
1090 
1091     return DefWindowProcA(hWnd, msg, wParam, lParam);
1092 }
1093 
1094 /******************************************************************
1095  *		WINHELP_HistoryWndProc
1096  *
1097  *
1098  */
1099 static LRESULT CALLBACK WINHELP_HistoryWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1100 {
1101     WINHELP_WINDOW*     win;
1102     PAINTSTRUCT         ps;
1103     HDC                 hDc;
1104     TEXTMETRICW         tm;
1105     unsigned int        i;
1106     RECT                r;
1107 
1108     switch (msg)
1109     {
1110     case WM_NCCREATE:
1111         win = (WINHELP_WINDOW*)((LPCREATESTRUCTA)lParam)->lpCreateParams;
1112         SetWindowLongPtrW(hWnd, 0, (ULONG_PTR)win);
1113         win->hHistoryWnd = hWnd;
1114         break;
1115     case WM_CREATE:
1116         hDc = GetDC(hWnd);
1117         GetTextMetricsW(hDc, &tm);
1118         GetWindowRect(hWnd, &r);
1119 
1120         r.right = r.left + 30 * tm.tmAveCharWidth;
1121         r.bottom = r.top + (sizeof(Globals.history.set) / sizeof(Globals.history.set[0])) * tm.tmHeight;
1122         AdjustWindowRect(&r, GetWindowLongW(hWnd, GWL_STYLE), FALSE);
1123         if (r.left < 0) {r.right -= r.left; r.left = 0;}
1124         if (r.top < 0) {r.bottom -= r.top; r.top = 0;}
1125 
1126         MoveWindow(hWnd, r.left, r.top, r.right, r.bottom, TRUE);
1127         ReleaseDC(hWnd, hDc);
1128         break;
1129     case WM_LBUTTONDOWN:
1130         hDc = GetDC(hWnd);
1131         GetTextMetricsW(hDc, &tm);
1132         i = HIWORD(lParam) / tm.tmHeight;
1133         if (i < Globals.history.index)
1134             WINHELP_CreateHelpWindow(&Globals.history.set[i], SW_SHOW, TRUE);
1135         ReleaseDC(hWnd, hDc);
1136         break;
1137     case WM_PAINT:
1138         hDc = BeginPaint(hWnd, &ps);
1139         GetTextMetricsW(hDc, &tm);
1140 
1141         for (i = 0; i < Globals.history.index; i++)
1142         {
1143             if (Globals.history.set[i].page->file == Globals.active_win->page->file)
1144             {
1145                 TextOutA(hDc, 0, i * tm.tmHeight,
1146                         Globals.history.set[i].page->lpszTitle,
1147                         strlen(Globals.history.set[i].page->lpszTitle));
1148             }
1149             else
1150             {
1151                 char        buffer[1024];
1152                 const char* ptr1;
1153                 const char* ptr2;
1154                 unsigned    len;
1155 
1156                 ptr1 = strrchr(Globals.history.set[i].page->file->lpszPath, '\\');
1157                 if (!ptr1) ptr1 = Globals.history.set[i].page->file->lpszPath;
1158                 else ptr1++;
1159                 ptr2 = strrchr(ptr1, '.');
1160                 len = ptr2 ? ptr2 - ptr1 : strlen(ptr1);
1161                 if (len > sizeof(buffer)) len = sizeof(buffer);
1162                 memcpy(buffer, ptr1, len);
1163                 if (len < sizeof(buffer)) buffer[len++] = ':';
1164                 lstrcpynA(&buffer[len], Globals.history.set[i].page->lpszTitle, sizeof(buffer) - len);
1165                 TextOutA(hDc, 0, i * tm.tmHeight, buffer, strlen(buffer));
1166             }
1167         }
1168         EndPaint(hWnd, &ps);
1169         break;
1170     case WM_DESTROY:
1171         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1172         if (hWnd == win->hHistoryWnd)
1173             win->hHistoryWnd = 0;
1174         break;
1175     }
1176     return DefWindowProcA(hWnd, msg, wParam, lParam);
1177 }
1178 
1179 /**************************************************************************
1180  * cb_KWBTree
1181  *
1182  * HLPFILE_BPTreeCallback enumeration function for '|KWBTREE' internal file.
1183  *
1184  */
1185 static void cb_KWBTree(void *p, void **next, void *cookie)
1186 {
1187     HWND hListWnd = cookie;
1188     int count;
1189 
1190     WINE_TRACE("Adding %s to search list\n", debugstr_a((char *)p));
1191     SendMessageA(hListWnd, LB_INSERTSTRING, -1, (LPARAM)p);
1192     count = SendMessageW(hListWnd, LB_GETCOUNT, 0, 0);
1193     SendMessageW(hListWnd, LB_SETITEMDATA, count-1, (LPARAM)p);
1194     *next = (char*)p + strlen((char*)p) + 7;
1195 }
1196 
1197 struct index_data
1198 {
1199     HLPFILE*    hlpfile;
1200     BOOL        jump;
1201     ULONG       offset;
1202 };
1203 
1204 /**************************************************************************
1205  * WINHELP_IndexDlgProc
1206  *
1207  */
1208 static INT_PTR CALLBACK WINHELP_IndexDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1209 {
1210     static struct index_data* id;
1211     int sel;
1212 
1213     switch (msg)
1214     {
1215     case WM_INITDIALOG:
1216         id = (struct index_data*)((PROPSHEETPAGEA*)lParam)->lParam;
1217         HLPFILE_BPTreeEnum(id->hlpfile->kwbtree, cb_KWBTree,
1218                            GetDlgItem(hWnd, IDC_INDEXLIST));
1219         id->jump = FALSE;
1220         id->offset = 1;
1221         return TRUE;
1222     case WM_COMMAND:
1223         switch (HIWORD(wParam))
1224         {
1225         case LBN_DBLCLK:
1226             if (LOWORD(wParam) == IDC_INDEXLIST)
1227                 SendMessageW(GetParent(hWnd), PSM_PRESSBUTTON, PSBTN_OK, 0);
1228             break;
1229         }
1230         break;
1231     case WM_NOTIFY:
1232 	switch (((NMHDR*)lParam)->code)
1233 	{
1234 	case PSN_APPLY:
1235             sel = SendDlgItemMessageW(hWnd, IDC_INDEXLIST, LB_GETCURSEL, 0, 0);
1236             if (sel != LB_ERR)
1237             {
1238                 BYTE *p;
1239                 int count;
1240 
1241                 p = (BYTE*)SendDlgItemMessageW(hWnd, IDC_INDEXLIST, LB_GETITEMDATA, sel, 0);
1242                 count = *(short*)((char *)p + strlen((char *)p) + 1);
1243                 if (count > 1)
1244                 {
1245                     MessageBoxA(hWnd, "count > 1 not supported yet", "Error", MB_OK | MB_ICONSTOP);
1246                     SetWindowLongPtrW(hWnd, DWLP_MSGRESULT, PSNRET_INVALID);
1247                     return TRUE;
1248                 }
1249                 id->offset = *(ULONG*)((char *)p + strlen((char *)p) + 3);
1250                 id->offset = *(long*)(id->hlpfile->kwdata + id->offset + 9);
1251                 if (id->offset == 0xFFFFFFFF)
1252                 {
1253                     MessageBoxA(hWnd, "macro keywords not supported yet", "Error", MB_OK | MB_ICONSTOP);
1254                     SetWindowLongPtrW(hWnd, DWLP_MSGRESULT, PSNRET_INVALID);
1255                     return TRUE;
1256                 }
1257                 id->jump = TRUE;
1258                 SetWindowLongPtrW(hWnd, DWLP_MSGRESULT, PSNRET_NOERROR);
1259             }
1260             return TRUE;
1261         default:
1262             return FALSE;
1263         }
1264         break;
1265     default:
1266         break;
1267     }
1268     return FALSE;
1269 }
1270 
1271 /**************************************************************************
1272  * WINHELP_SearchDlgProc
1273  *
1274  */
1275 static INT_PTR CALLBACK WINHELP_SearchDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1276 {
1277     switch (msg)
1278     {
1279     case WM_INITDIALOG:
1280         return TRUE;
1281     case WM_NOTIFY:
1282 	switch (((NMHDR*)lParam)->code)
1283 	{
1284 	case PSN_APPLY:
1285             SetWindowLongPtrW(hWnd, DWLP_MSGRESULT, PSNRET_NOERROR);
1286             return TRUE;
1287         default:
1288             return FALSE;
1289         }
1290         break;
1291     default:
1292         break;
1293     }
1294     return FALSE;
1295 }
1296 
1297 /***********************************************************************
1298  *
1299  *           WINHELP_MainWndProc
1300  */
1301 static LRESULT CALLBACK WINHELP_MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1302 {
1303     WINHELP_WINDOW *win;
1304     WINHELP_BUTTON *button;
1305     HWND hTextWnd;
1306     LRESULT ret;
1307 
1308     if (WINHELP_CheckPopup(hWnd, msg, wParam, lParam, &ret)) return ret;
1309 
1310     switch (msg)
1311     {
1312     case WM_NCCREATE:
1313         win = (WINHELP_WINDOW*) ((LPCREATESTRUCTA) lParam)->lpCreateParams;
1314         SetWindowLongPtrW(hWnd, 0, (ULONG_PTR) win);
1315         if (!win->page && Globals.isBook)
1316             PostMessageW(hWnd, WM_COMMAND, MNID_FILE_OPEN, 0);
1317         win->hMainWnd = hWnd;
1318         break;
1319 
1320     case WM_WINDOWPOSCHANGED:
1321         WINHELP_LayoutMainWindow((WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0));
1322         break;
1323 
1324     case WM_COMMAND:
1325         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1326         switch (LOWORD(wParam))
1327 	{
1328             /* Menu FILE */
1329 	case MNID_FILE_OPEN:    MACRO_FileOpen();       break;
1330 	case MNID_FILE_PRINT:	MACRO_Print();          break;
1331 	case MNID_FILE_SETUP:	MACRO_PrinterSetup();   break;
1332 	case MNID_FILE_EXIT:	MACRO_Exit();           break;
1333 
1334             /* Menu EDIT */
1335 	case MNID_EDIT_COPYDLG:
1336             SendDlgItemMessageW(hWnd, CTL_ID_TEXT, WM_COPY, 0, 0);
1337             break;
1338 	case MNID_EDIT_ANNOTATE:MACRO_Annotate();       break;
1339 
1340             /* Menu Bookmark */
1341 	case MNID_BKMK_DEFINE:  MACRO_BookmarkDefine(); break;
1342 
1343             /* Menu Help */
1344 	case MNID_HELP_HELPON:	MACRO_HelpOn();         break;
1345 	case MNID_HELP_HELPTOP: MACRO_HelpOnTop();      break;
1346 	case MNID_HELP_ABOUT:	MACRO_About();          break;
1347 
1348             /* Context help */
1349         case MNID_CTXT_ANNOTATE:MACRO_Annotate();       break;
1350         case MNID_CTXT_COPY:    MACRO_CopyDialog();     break;
1351         case MNID_CTXT_PRINT:   MACRO_Print();          break;
1352         case MNID_OPTS_HISTORY: MACRO_History();        break;
1353         case MNID_OPTS_FONTS_SMALL:
1354         case MNID_CTXT_FONTS_SMALL:
1355             win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1356             if (win->font_scale != 0)
1357             {
1358                 win->font_scale = 0;
1359                 WINHELP_SetupText(GetDlgItem(hWnd, CTL_ID_TEXT), win, 0 /* FIXME */);
1360             }
1361             break;
1362         case MNID_OPTS_FONTS_NORMAL:
1363         case MNID_CTXT_FONTS_NORMAL:
1364             win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1365             if (win->font_scale != 1)
1366             {
1367                 win->font_scale = 1;
1368                 WINHELP_SetupText(GetDlgItem(hWnd, CTL_ID_TEXT), win, 0 /* FIXME */);
1369             }
1370             break;
1371         case MNID_OPTS_FONTS_LARGE:
1372         case MNID_CTXT_FONTS_LARGE:
1373             win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1374             if (win->font_scale != 2)
1375             {
1376                 win->font_scale = 2;
1377                 WINHELP_SetupText(GetDlgItem(hWnd, CTL_ID_TEXT), win, 0 /* FIXME */);
1378             }
1379             break;
1380 
1381 	default:
1382             /* Buttons */
1383             for (button = win->first_button; button; button = button->next)
1384                 if (wParam == button->wParam) break;
1385             if (button)
1386                 MACRO_ExecuteMacro(win, button->lpszMacro);
1387             else if (!HIWORD(wParam))
1388                 MessageBoxW(0, MAKEINTRESOURCEW(STID_NOT_IMPLEMENTED),
1389                             MAKEINTRESOURCEW(STID_WHERROR), MB_OK);
1390             break;
1391 	}
1392         break;
1393 /* EPP     case WM_DESTROY: */
1394 /* EPP         if (Globals.hPopupWnd) DestroyWindow(Globals.hPopupWnd); */
1395 /* EPP         break; */
1396     case WM_COPYDATA:
1397         return WINHELP_HandleCommand((HWND)wParam, lParam);
1398 
1399     case WM_CHAR:
1400         if (wParam == 3)
1401         {
1402             SendDlgItemMessageW(hWnd, CTL_ID_TEXT, WM_COPY, 0, 0);
1403             return 0;
1404         }
1405         break;
1406 
1407     case WM_KEYDOWN:
1408         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1409         hTextWnd = GetDlgItem(win->hMainWnd, CTL_ID_TEXT);
1410 
1411         switch (wParam)
1412         {
1413         case VK_UP:
1414             SendMessageW(hTextWnd, EM_SCROLL, SB_LINEUP, 0);
1415             return 0;
1416         case VK_DOWN:
1417             SendMessageW(hTextWnd, EM_SCROLL, SB_LINEDOWN, 0);
1418             return 0;
1419         case VK_PRIOR:
1420             SendMessageW(hTextWnd, EM_SCROLL, SB_PAGEUP, 0);
1421             return 0;
1422         case VK_NEXT:
1423             SendMessageW(hTextWnd, EM_SCROLL, SB_PAGEDOWN, 0);
1424             return 0;
1425         case VK_ESCAPE:
1426             MACRO_Exit();
1427             return 0;
1428         }
1429         break;
1430 
1431     case WM_NOTIFY:
1432         if (wParam == CTL_ID_TEXT)
1433         {
1434             RECT        rc;
1435 
1436             switch (((NMHDR*)lParam)->code)
1437             {
1438             case EN_MSGFILTER:
1439                 {
1440                     const MSGFILTER*    msgf = (const MSGFILTER*)lParam;
1441                     switch (msgf->msg)
1442                     {
1443                     case WM_KEYUP:
1444                         if (msgf->wParam == VK_ESCAPE)
1445                             WINHELP_ReleaseWindow((WINHELP_WINDOW*)GetWindowLongPtrW(hWnd, 0));
1446                         break;
1447                     case WM_RBUTTONDOWN:
1448                     {
1449                         HMENU       hMenu;
1450                         POINT       pt;
1451 
1452                         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1453                         hMenu = LoadMenuW(Globals.hInstance, MAKEINTRESOURCEW(CONTEXT_MENU));
1454                         switch (win->font_scale)
1455                         {
1456                         case 0:
1457                             CheckMenuItem(hMenu, MNID_CTXT_FONTS_SMALL,
1458                                           MF_BYCOMMAND|MF_CHECKED);
1459                             break;
1460                         default:
1461                             WINE_FIXME("Unsupported %d\n", win->font_scale);
1462                             /* fall through */
1463                         case 1:
1464                             CheckMenuItem(hMenu, MNID_CTXT_FONTS_NORMAL,
1465                                           MF_BYCOMMAND|MF_CHECKED);
1466                             break;
1467                         case 2:
1468                             CheckMenuItem(hMenu, MNID_CTXT_FONTS_LARGE,
1469                                           MF_BYCOMMAND|MF_CHECKED);
1470                             break;
1471                         }
1472                         pt.x = (int)(short)LOWORD(msgf->lParam);
1473                         pt.y = (int)(short)HIWORD(msgf->lParam);
1474                         ClientToScreen(msgf->nmhdr.hwndFrom, &pt);
1475                         TrackPopupMenu(GetSubMenu(hMenu, 0), TPM_LEFTALIGN|TPM_TOPALIGN,
1476                                        pt.x, pt.y, 0, hWnd, NULL);
1477                         DestroyMenu(hMenu);
1478                     }
1479                     break;
1480                     default:
1481                         return WINHELP_HandleTextMouse((WINHELP_WINDOW*)GetWindowLongPtrW(hWnd, 0),
1482                                                        msgf->msg, msgf->lParam);
1483                     }
1484                 }
1485                 break;
1486 
1487             case EN_REQUESTRESIZE:
1488                 rc = ((REQRESIZE*)lParam)->rc;
1489                 win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1490                 AdjustWindowRect(&rc, GetWindowLongW(win->hMainWnd, GWL_STYLE),
1491                                  FALSE);
1492                 SetWindowPos(win->hMainWnd, HWND_TOP, 0, 0,
1493                              rc.right - rc.left, rc.bottom - rc.top,
1494                              SWP_NOMOVE | SWP_NOZORDER);
1495                 WINHELP_LayoutMainWindow(win);
1496                 break;
1497             }
1498         }
1499         break;
1500 
1501     case WM_INITMENUPOPUP:
1502         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1503         CheckMenuItem((HMENU)wParam, MNID_OPTS_FONTS_SMALL,
1504                       (win->font_scale == 0) ? MF_CHECKED : MF_UNCHECKED);
1505         CheckMenuItem((HMENU)wParam, MNID_OPTS_FONTS_NORMAL,
1506                       (win->font_scale == 1) ? MF_CHECKED : MF_UNCHECKED);
1507         CheckMenuItem((HMENU)wParam, MNID_OPTS_FONTS_LARGE,
1508                       (win->font_scale == 2) ? MF_CHECKED : MF_UNCHECKED);
1509         break;
1510     case WM_DESTROY:
1511         win = (WINHELP_WINDOW*) GetWindowLongPtrW(hWnd, 0);
1512         WINHELP_DeleteWindow(win);
1513         break;
1514     }
1515     return DefWindowProcA(hWnd, msg, wParam, lParam);
1516 }
1517 
1518 /**************************************************************************
1519  * WINHELP_CreateIndexWindow
1520  *
1521  * Displays a dialog with keywords of current help file.
1522  *
1523  */
1524 BOOL WINHELP_CreateIndexWindow(BOOL is_search)
1525 {
1526     HPROPSHEETPAGE      psPage[3];
1527     PROPSHEETPAGEA      psp;
1528     PROPSHEETHEADERA    psHead;
1529     struct index_data   id;
1530     char                buf[256];
1531 
1532     if (Globals.active_win && Globals.active_win->page && Globals.active_win->page->file)
1533         id.hlpfile = Globals.active_win->page->file;
1534     else
1535         return FALSE;
1536 
1537     if (id.hlpfile->kwbtree == NULL)
1538     {
1539         WINE_TRACE("No index provided\n");
1540         return FALSE;
1541     }
1542 
1543     InitCommonControls();
1544 
1545     id.jump = FALSE;
1546     memset(&psp, 0, sizeof(psp));
1547     psp.dwSize = sizeof(psp);
1548     psp.dwFlags = 0;
1549     psp.hInstance = Globals.hInstance;
1550 
1551     psp.u.pszTemplate = MAKEINTRESOURCEA(IDD_INDEX);
1552     psp.lParam = (LPARAM)&id;
1553     psp.pfnDlgProc = WINHELP_IndexDlgProc;
1554     psPage[0] = CreatePropertySheetPageA(&psp);
1555 
1556     psp.u.pszTemplate = MAKEINTRESOURCEA(IDD_SEARCH);
1557     psp.lParam = (LPARAM)&id;
1558     psp.pfnDlgProc = WINHELP_SearchDlgProc;
1559     psPage[1] = CreatePropertySheetPageA(&psp);
1560 
1561     memset(&psHead, 0, sizeof(psHead));
1562     psHead.dwSize = sizeof(psHead);
1563 
1564     LoadStringA(Globals.hInstance, STID_PSH_INDEX, buf, sizeof(buf));
1565     strcat(buf, Globals.active_win->info->caption);
1566 
1567     psHead.pszCaption = buf;
1568     psHead.nPages = 2;
1569     psHead.u2.nStartPage = is_search ? 1 : 0;
1570     psHead.hwndParent = Globals.active_win->hMainWnd;
1571     psHead.u3.phpage = psPage;
1572     psHead.dwFlags = PSH_NOAPPLYNOW;
1573 
1574     PropertySheetA(&psHead);
1575     if (id.jump)
1576     {
1577         WINE_TRACE("got %d as an offset\n", id.offset);
1578         WINHELP_OpenHelpWindow(HLPFILE_PageByOffset, id.hlpfile, id.offset,
1579                                Globals.active_win->info, SW_NORMAL);
1580     }
1581     return TRUE;
1582 }
1583 
1584 /***********************************************************************
1585  *
1586  *           RegisterWinClasses
1587  */
1588 static BOOL WINHELP_RegisterWinClasses(void)
1589 {
1590     WNDCLASSEXA class_main, class_button_box, class_history;
1591 
1592     class_main.cbSize              = sizeof(class_main);
1593     class_main.style               = CS_HREDRAW | CS_VREDRAW;
1594     class_main.lpfnWndProc         = WINHELP_MainWndProc;
1595     class_main.cbClsExtra          = 0;
1596     class_main.cbWndExtra          = sizeof(WINHELP_WINDOW *);
1597     class_main.hInstance           = Globals.hInstance;
1598     class_main.hIcon               = LoadIconW(Globals.hInstance, MAKEINTRESOURCEW(IDI_WINHELP));
1599     class_main.hCursor             = LoadCursorW(0, (LPWSTR)IDC_ARROW);
1600     class_main.hbrBackground       = (HBRUSH)(COLOR_WINDOW+1);
1601     class_main.lpszMenuName        = 0;
1602     class_main.lpszClassName       = MAIN_WIN_CLASS_NAME;
1603     class_main.hIconSm             = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_WINHELP), IMAGE_ICON,
1604                                                GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
1605                                                LR_SHARED);
1606 
1607     class_button_box               = class_main;
1608     class_button_box.lpfnWndProc   = WINHELP_ButtonBoxWndProc;
1609     class_button_box.cbWndExtra    = 0;
1610     class_button_box.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
1611     class_button_box.lpszClassName = BUTTON_BOX_WIN_CLASS_NAME;
1612 
1613     class_history                  = class_main;
1614     class_history.lpfnWndProc      = WINHELP_HistoryWndProc;
1615     class_history.lpszClassName    = HISTORY_WIN_CLASS_NAME;
1616 
1617     return (RegisterClassExA(&class_main) &&
1618             RegisterClassExA(&class_button_box) &&
1619             RegisterClassExA(&class_history));
1620 }
1621 
1622 /***********************************************************************
1623  *
1624  *           WinMain
1625  */
1626 int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR cmdline, int show)
1627 {
1628     MSG                 msg;
1629     LONG                lHash = 0;
1630     HLPFILE*            hlpfile;
1631     static CHAR         default_wndname[] = "main";
1632     LPSTR               wndname = default_wndname;
1633     WINHELP_DLL*        dll;
1634     HACCEL              hAccel;
1635 
1636     Globals.hInstance = hInstance;
1637 
1638     if (LoadLibraryA("riched20.dll") == NULL)
1639         return MessageBoxW(0, MAKEINTRESOURCEW(STID_NO_RICHEDIT),
1640                            MAKEINTRESOURCEW(STID_WHERROR), MB_OK);
1641 
1642     /* Get options */
1643     while (*cmdline && (*cmdline == ' ' || *cmdline == '-'))
1644     {
1645         CHAR   option;
1646         LPCSTR topic_id;
1647         if (*cmdline++ == ' ') continue;
1648 
1649         option = *cmdline;
1650         if (option) cmdline++;
1651         while (*cmdline == ' ') cmdline++;
1652         switch (option)
1653 	{
1654 	case 'i':
1655 	case 'I':
1656             topic_id = cmdline;
1657             while (*cmdline && *cmdline != ' ') cmdline++;
1658             if (*cmdline) *cmdline++ = '\0';
1659             lHash = HLPFILE_Hash(topic_id);
1660             break;
1661 
1662 	case '3':
1663 	case '4':
1664             Globals.wVersion = option - '0';
1665             break;
1666 
1667         case 'x':
1668             show = SW_HIDE;
1669             Globals.isBook = FALSE;
1670             break;
1671 
1672         default:
1673             WINE_FIXME("Unsupported cmd line: %s\n", debugstr_a(cmdline));
1674             break;
1675 	}
1676     }
1677 
1678     /* Create primary window */
1679     if (!WINHELP_RegisterWinClasses())
1680     {
1681         WINE_FIXME("Couldn't register classes\n");
1682         return 0;
1683     }
1684 
1685     if (*cmdline)
1686     {
1687         char*   ptr;
1688         if ((*cmdline == '"') && (ptr = strchr(cmdline+1, '"')))
1689         {
1690             cmdline++;
1691             *ptr = '\0';
1692         }
1693         if ((ptr = strchr(cmdline, '>')))
1694         {
1695             *ptr = '\0';
1696             wndname = ptr + 1;
1697         }
1698         hlpfile = WINHELP_LookupHelpFile(cmdline);
1699         if (!hlpfile) return 0;
1700     }
1701     else hlpfile = NULL;
1702     WINHELP_OpenHelpWindow(HLPFILE_PageByHash, hlpfile, lHash,
1703                            WINHELP_GetWindowInfo(hlpfile, wndname), show);
1704 
1705     /* Message loop */
1706     hAccel = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(MAIN_ACCEL));
1707     while ((Globals.win_list || Globals.active_popup) && GetMessageW(&msg, 0, 0, 0))
1708     {
1709         HWND hWnd = Globals.active_win ? Globals.active_win->hMainWnd : NULL;
1710         if (!TranslateAcceleratorW(hWnd, hAccel, &msg))
1711 	{
1712             TranslateMessage(&msg);
1713             DispatchMessageW(&msg);
1714         }
1715     }
1716     for (dll = Globals.dlls; dll; dll = dll->next)
1717     {
1718         if (dll->class & DC_INITTERM) dll->handler(DW_TERM, 0, 0);
1719     }
1720     return 0;
1721 }
1722