hhctrl.ocx: Make the Index tab functional.
authorErich Hoover <ehoover@mines.edu>
Wed, 27 Jan 2010 08:12:25 +0000 (01:12 -0700)
committerAlexandre Julliard <julliard@winehq.org>
Thu, 28 Jan 2010 10:58:33 +0000 (11:58 +0100)
dlls/hhctrl.ocx/Makefile.in
dlls/hhctrl.ocx/help.c
dlls/hhctrl.ocx/hhctrl.h
dlls/hhctrl.ocx/index.c [new file with mode: 0644]

index b2c10d0b27013d7718b484f20a080679f004394a..c4f9d497e9104caff039e3d58dd1a161ee5aa78e 100644 (file)
@@ -10,6 +10,7 @@ C_SRCS = \
        content.c \
        help.c \
        hhctrl.c \
+       index.c \
        regsvr.c \
        stream.c \
        webbrowser.c
index 20305f141f9684c3fea14f8b7cdbe09aca5a35e9..64f7b245af3a8cbcdf70f80e7049e543b3ac5ff4 100644 (file)
@@ -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;
+        }
+        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;
         }
-        iter = iter->parent;
+        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);
index 83d39f5de4c096c7e53058903bc22398b863dd2d..8a3329e0304df196926b4c99685b824bc5d0d1af 100644 (file)
@@ -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 (file)
index 0000000..dd61fe3
--- /dev/null
@@ -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 </object>
+ * 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 <OBJECT> 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 <LI> item, we must hope that
+ * the <LI> 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;i<item->nItems;i++) {
+            heap_free(item->items[i].name);
+            heap_free(item->items[i].local);
+        }
+        heap_free(item->items);
+
+        item = next;
+    }
+}