diff --git a/dlls/hhctrl.ocx/Makefile.in b/dlls/hhctrl.ocx/Makefile.in index b2c10d0b270..c4f9d497e91 100644 --- a/dlls/hhctrl.ocx/Makefile.in +++ b/dlls/hhctrl.ocx/Makefile.in @@ -10,6 +10,7 @@ C_SRCS = \ content.c \ help.c \ hhctrl.c \ + index.c \ regsvr.c \ stream.c \ webbrowser.c diff --git a/dlls/hhctrl.ocx/help.c b/dlls/hhctrl.ocx/help.c index 20305f141f9..64f7b245af3 100644 --- a/dlls/hhctrl.ocx/help.c +++ b/dlls/hhctrl.ocx/help.c @@ -397,26 +397,53 @@ static LRESULT OnTabChange(HWND hwnd) return 0; } -static LRESULT OnTopicChange(HWND hwnd, ContentItem *item) +static LRESULT OnTopicChange(HWND hwnd, void *user_data) { HHInfo *info = (HHInfo*)GetWindowLongPtrW(hwnd, GWLP_USERDATA); - LPCWSTR chmfile = NULL; - ContentItem *iter = item; + LPCWSTR chmfile = NULL, name = NULL, local = NULL; + ContentItem *citer; + IndexItem *iiter; - if(!item || !info) + if(!user_data || !info) return 0; - TRACE("name %s loal %s\n", debugstr_w(item->name), debugstr_w(item->local)); - - while(iter) { - if(iter->merge.chm_file) { - chmfile = iter->merge.chm_file; - break; + switch (info->current_tab) + { + case TAB_CONTENTS: + citer = (ContentItem *) user_data; + name = citer->name; + local = citer->local; + while(citer) { + if(citer->merge.chm_file) { + chmfile = citer->merge.chm_file; + break; + } + citer = citer->parent; } - iter = iter->parent; + chmfile = citer->merge.chm_file; + break; + case TAB_INDEX: + iiter = (IndexItem *) user_data; + if(iiter->nItems == 0) { + FIXME("No entries for this item!\n"); + return 0; + } + if(iiter->nItems > 1) { + FIXME("Support for sub-topics not implemented.\n"); + return 0; + } + name = iiter->items[0].name; + local = iiter->items[0].local; + chmfile = iiter->merge.chm_file; + break; + default: + FIXME("Unhandled operation for this tab!\n"); + return 0; } - NavigateToChm(info, chmfile, item->local); + TRACE("name %s loal %s\n", debugstr_w(name), debugstr_w(local)); + + NavigateToChm(info, chmfile, local); return 0; } @@ -434,7 +461,9 @@ static LRESULT CALLBACK Child_WndProc(HWND hWnd, UINT message, WPARAM wParam, LP case TCN_SELCHANGE: return OnTabChange(hWnd); case TVN_SELCHANGEDW: - return OnTopicChange(hWnd, (ContentItem*)((NMTREEVIEWW *)lParam)->itemNew.lParam); + return OnTopicChange(hWnd, (void*)((NMTREEVIEWW *)lParam)->itemNew.lParam); + case NM_DBLCLK: + return OnTopicChange(hWnd, (void*)((NMITEMACTIVATE *)lParam)->lParam); } break; } @@ -975,6 +1004,7 @@ static BOOL CreateViewer(HHInfo *pHHInfo) return FALSE; InitContent(pHHInfo); + InitIndex(pHHInfo); return TRUE; } @@ -1003,6 +1033,7 @@ void ReleaseHelpViewer(HHInfo *info) ReleaseWebBrowser(info); ReleaseContent(info); + ReleaseIndex(info); if(info->WinType.hwndHelp) DestroyWindow(info->WinType.hwndHelp); diff --git a/dlls/hhctrl.ocx/hhctrl.h b/dlls/hhctrl.ocx/hhctrl.h index 83d39f5de4c..8a3329e0304 100644 --- a/dlls/hhctrl.ocx/hhctrl.h +++ b/dlls/hhctrl.ocx/hhctrl.h @@ -65,6 +65,23 @@ typedef struct ContentItem { ChmPath merge; } ContentItem; +typedef struct IndexSubItem { + LPWSTR name; + LPWSTR local; +} IndexSubItem; + +typedef struct IndexItem { + struct IndexItem *next; + + HTREEITEM id; + LPWSTR keyword; + ChmPath merge; + + int nItems; + int itemFlags; + IndexSubItem *items; +} IndexItem; + typedef struct CHMInfo { IITStorage *pITStorage; @@ -111,6 +128,7 @@ typedef struct { CHMInfo *pCHMInfo; ContentItem *content; + IndexItem *index; HWND hwndTabCtrl; HWND hwndSizeBar; HFONT hFont; @@ -127,6 +145,9 @@ void DoPageAction(HHInfo*,DWORD); void InitContent(HHInfo*); void ReleaseContent(HHInfo*); +void InitIndex(HHInfo*); +void ReleaseIndex(HHInfo*); + CHMInfo *OpenCHM(LPCWSTR szFile); BOOL LoadWinTypeFromCHM(HHInfo *info); CHMInfo *CloseCHM(CHMInfo *pCHMInfo); diff --git a/dlls/hhctrl.ocx/index.c b/dlls/hhctrl.ocx/index.c new file mode 100644 index 00000000000..dd61fe3fa3c --- /dev/null +++ b/dlls/hhctrl.ocx/index.c @@ -0,0 +1,271 @@ +/* + * Copyright 2007 Jacek Caban for CodeWeavers + * Copyright 2010 Erich Hoover + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define NONAMELESSUNION +#define NONAMELESSSTRUCT + +#include "hhctrl.h" +#include "stream.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); + +/* Fill the TreeView object corresponding to the Index items */ +static void fill_index_tree(HWND hwnd, IndexItem *item) +{ + int index = 0; + LVITEMW lvi; + + while(item) { + TRACE("tree debug: %s\n", debugstr_w(item->keyword)); + + memset(&lvi, 0, sizeof(lvi)); + lvi.iItem = index++; + lvi.mask = LVIF_TEXT|LVIF_PARAM; + lvi.cchTextMax = strlenW(item->keyword)+1; + lvi.pszText = item->keyword; + lvi.lParam = (LPARAM)item; + item->id = (HTREEITEM)SendMessageW(hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvi); + item = item->next; + } +} + +/* Parse the attributes correspond to a list item, including sub-topics. + * + * Each list item has, at minimum, a param of type "keyword" and two + * parameters corresponding to a "sub-topic." For each sub-topic there + * must be a "name" param and a "local" param, if there is only one + * sub-topic then there isn't really a sub-topic, the index will jump + * directly to the requested item. + */ +static void parse_index_obj_node_param(IndexItem *item, const char *text) +{ + const char *ptr; + LPWSTR *param; + int len, wlen; + + ptr = get_attr(text, "name", &len); + if(!ptr) { + WARN("name attr not found\n"); + return; + } + + /* Allocate a new sub-item, either on the first run or whenever a + * sub-topic has filled out both the "name" and "local" params. + */ + if(item->itemFlags == 0x11 && (!strncasecmp("name", ptr, len) || !strncasecmp("local", ptr, len))) { + item->nItems++; + item->items = heap_realloc(item->items, sizeof(IndexSubItem)*item->nItems); + item->items[item->nItems-1].name = NULL; + item->items[item->nItems-1].local = NULL; + item->itemFlags = 0x00; + } + if(!strncasecmp("keyword", ptr, len)) { + param = &item->keyword; + }else if(!strncasecmp("name", ptr, len)) { + item->itemFlags |= 0x01; + param = &item->items[item->nItems-1].name; + }else if(!strncasecmp("local", ptr, len)) { + item->itemFlags |= 0x10; + param = &item->items[item->nItems-1].local; + }else { + WARN("unhandled param %s\n", debugstr_an(ptr, len)); + return; + } + + ptr = get_attr(text, "value", &len); + if(!ptr) { + WARN("value attr not found\n"); + return; + } + + wlen = MultiByteToWideChar(CP_ACP, 0, ptr, len, NULL, 0); + *param = heap_alloc((wlen+1)*sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, 0, ptr, len, *param, wlen); + (*param)[wlen] = 0; +} + +/* Parse the object tag corresponding to a list item. + * + * At this step we look for all of the "param" child tags, using this information + * to build up the information about the list item. When we reach the + * tag we know that we've finished parsing this list item. + */ +static IndexItem *parse_index_sitemap_object(HHInfo *info, stream_t *stream) +{ + strbuf_t node, node_name; + IndexItem *item; + + strbuf_init(&node); + strbuf_init(&node_name); + + item = heap_alloc_zero(sizeof(IndexItem)); + item->nItems = 0; + item->items = heap_alloc_zero(0); + item->itemFlags = 0x11; + + while(next_node(stream, &node)) { + get_node_name(&node, &node_name); + + TRACE("%s\n", node.buf); + + if(!strcasecmp(node_name.buf, "param")) { + parse_index_obj_node_param(item, node.buf); + }else if(!strcasecmp(node_name.buf, "/object")) { + break; + }else { + WARN("Unhandled tag! %s\n", node_name.buf); + } + + strbuf_zero(&node); + } + + strbuf_free(&node); + strbuf_free(&node_name); + + return item; +} + +/* Parse the HTML list item node corresponding to a specific help entry. + * + * At this stage we look for the only child tag we expect to find under + * the list item: the tag. We also only expect to find object + * tags with the "type" attribute set to "text/sitemap". + */ +static IndexItem *parse_li(HHInfo *info, stream_t *stream) +{ + strbuf_t node, node_name; + IndexItem *ret = NULL; + + strbuf_init(&node); + strbuf_init(&node_name); + + while(next_node(stream, &node)) { + get_node_name(&node, &node_name); + + TRACE("%s\n", node.buf); + + if(!strcasecmp(node_name.buf, "object")) { + const char *ptr; + int len; + + static const char sz_text_sitemap[] = "text/sitemap"; + + ptr = get_attr(node.buf, "type", &len); + + if(ptr && len == sizeof(sz_text_sitemap)-1 + && !memcmp(ptr, sz_text_sitemap, len)) { + ret = parse_index_sitemap_object(info, stream); + break; + } + }else { + WARN("Unhandled tag! %s\n", node_name.buf); + } + + strbuf_zero(&node); + } + + strbuf_free(&node); + strbuf_free(&node_name); + + return ret; +} + +/* Parse the HTML Help page corresponding to all of the Index items. + * + * At this high-level stage we locate out each HTML list item tag. + * Since there is no end-tag for the
  • item, we must hope that + * the
  • entry is parsed correctly or tags might get lost. + */ +static void parse_hhindex(HHInfo *info, IStream *str, IndexItem *item) +{ + stream_t stream; + strbuf_t node, node_name; + + strbuf_init(&node); + strbuf_init(&node_name); + + stream_init(&stream, str); + + while(next_node(&stream, &node)) { + get_node_name(&node, &node_name); + + TRACE("%s\n", node.buf); + + if(!strcasecmp(node_name.buf, "li")) { + item->next = parse_li(info, &stream); + item->next->merge = item->merge; + item = item->next; + }else { + WARN("Unhandled tag! %s\n", node_name.buf); + } + + strbuf_zero(&node); + } + + strbuf_free(&node); + strbuf_free(&node_name); +} + +/* Initialize the HTML Help Index tab */ +void InitIndex(HHInfo *info) +{ + IStream *stream; + + info->index = heap_alloc_zero(sizeof(IndexItem)); + info->index->nItems = 0; + SetChmPath(&info->index->merge, info->pCHMInfo->szFile, info->WinType.pszIndex); + + stream = GetChmStream(info->pCHMInfo, info->pCHMInfo->szFile, &info->index->merge); + if(!stream) { + TRACE("Could not get index stream\n"); + return; + } + + parse_hhindex(info, stream, info->index); + IStream_Release(stream); + + fill_index_tree(info->tabs[TAB_INDEX].hwnd, info->index->next); +} + +/* Free all of the Index items, including all of the "sub-items" that + * correspond to different sub-topics. + */ +void ReleaseIndex(HHInfo *info) +{ + IndexItem *item = info->index, *next; + int i; + + /* Note: item->merge is identical for all items, only free once */ + heap_free(item->merge.chm_file); + heap_free(item->merge.chm_index); + while(item) { + next = item->next; + + heap_free(item->keyword); + for(i=0;inItems;i++) { + heap_free(item->items[i].name); + heap_free(item->items[i].local); + } + heap_free(item->items); + + item = next; + } +}