hhctrl.ocx: Add Search capability.
diff --git a/dlls/hhctrl.ocx/Makefile.in b/dlls/hhctrl.ocx/Makefile.in
index 7e3bf52..19e6058 100644
--- a/dlls/hhctrl.ocx/Makefile.in
+++ b/dlls/hhctrl.ocx/Makefile.in
@@ -12,6 +12,7 @@
hhctrl.c \
index.c \
regsvr.c \
+ search.c \
stream.c \
webbrowser.c
diff --git a/dlls/hhctrl.ocx/chm.c b/dlls/hhctrl.ocx/chm.c
index 3e148db..cf5b8bf 100644
--- a/dlls/hhctrl.ocx/chm.c
+++ b/dlls/hhctrl.ocx/chm.c
@@ -389,7 +389,6 @@
WARN("Could not open storage: %08x\n", hres);
return CloseCHM(ret);
}
-
hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0,
&ret->strings_stream);
if(FAILED(hres)) {
diff --git a/dlls/hhctrl.ocx/help.c b/dlls/hhctrl.ocx/help.c
index 7eca538..680911a 100644
--- a/dlls/hhctrl.ocx/help.c
+++ b/dlls/hhctrl.ocx/help.c
@@ -421,6 +421,7 @@
{
LPCWSTR chmfile = NULL, name = NULL, local = NULL;
ContentItem *citer;
+ SearchItem *siter;
IndexItem *iiter;
if(!user_data || !info)
@@ -472,6 +473,12 @@
local = iiter->items[0].local;
chmfile = iiter->merge.chm_file;
break;
+ case TAB_SEARCH:
+ siter = (SearchItem *) user_data;
+ name = siter->filename;
+ local = siter->filename;
+ chmfile = info->pCHMInfo->szFile;
+ break;
default:
FIXME("Unhandled operation for this tab!\n");
return 0;
@@ -523,8 +530,16 @@
case TVN_SELCHANGEDW:
return OnTopicChange(info, (void*)((NMTREEVIEWW *)lParam)->itemNew.lParam);
case NM_DBLCLK:
- if(info && info->current_tab == TAB_INDEX)
+ if(!info)
+ return 0;
+ switch(info->current_tab)
+ {
+ case TAB_INDEX:
return OnTopicChange(info, (void*)((NMITEMACTIVATE *)lParam)->lParam);
+ case TAB_SEARCH:
+ return OnTopicChange(info, (void*)((NMITEMACTIVATE *)lParam)->lParam);
+ }
+ break;
case NM_RETURN:
if(!info)
return 0;
@@ -540,11 +555,34 @@
return 0;
}
case TAB_SEARCH: {
- WCHAR needle[100];
+ if(nmhdr->hwndFrom == info->search.hwndEdit) {
+ char needle[100];
+ DWORD i, len;
- GetWindowTextW(info->search.hwndEdit, needle, sizeof(needle)/sizeof(WCHAR));
- FIXME("Search for text: %s\n", debugstr_w(needle));
- return 0;
+ len = GetWindowTextA(info->search.hwndEdit, needle, sizeof(needle));
+ if(!len)
+ {
+ FIXME("Unable to get search text.\n");
+ return 0;
+ }
+ /* Convert the requested text for comparison later against the
+ * lower case version of HTML file contents.
+ */
+ for(i=0;i<len;i++)
+ needle[i] = tolower(needle[i]);
+ InitSearch(info, needle);
+ return 0;
+ }else if(nmhdr->hwndFrom == info->search.hwndList) {
+ HWND hwndList = info->search.hwndList;
+ LVITEMW lvItem;
+
+ lvItem.iItem = (int) SendMessageW(hwndList, LVM_GETSELECTIONMARK, 0, 0);
+ lvItem.mask = TVIF_PARAM;
+ ListView_GetItemW(hwndList, &lvItem);
+ OnTopicChange(info, (void*) lvItem.lParam);
+ return 0;
+ }
+ break;
}
}
break;
@@ -1376,6 +1414,7 @@
ReleaseWebBrowser(info);
ReleaseContent(info);
ReleaseIndex(info);
+ ReleaseSearch(info);
if(info->WinType.hwndHelp)
DestroyWindow(info->WinType.hwndHelp);
diff --git a/dlls/hhctrl.ocx/hhctrl.h b/dlls/hhctrl.ocx/hhctrl.h
index dfc80b7..91d3a77 100644
--- a/dlls/hhctrl.ocx/hhctrl.h
+++ b/dlls/hhctrl.ocx/hhctrl.h
@@ -83,6 +83,14 @@
IndexSubItem *items;
} IndexItem;
+typedef struct SearchItem {
+ struct SearchItem *next;
+
+ HTREEITEM id;
+ LPWSTR title;
+ LPWSTR filename;
+} SearchItem;
+
typedef struct CHMInfo
{
IITStorage *pITStorage;
@@ -115,6 +123,7 @@
} IndexPopup;
typedef struct {
+ SearchItem *root;
HWND hwndEdit;
HWND hwndList;
HWND hwndContainer;
@@ -175,6 +184,9 @@
BOOL NavigateToUrl(HHInfo*,LPCWSTR);
BOOL NavigateToChm(HHInfo*,LPCWSTR,LPCWSTR);
+void InitSearch(HHInfo *info, const char *needle);
+void ReleaseSearch(HHInfo *info);
+
/* memory allocation functions */
static inline void * __WINE_ALLOC_SIZE(1) heap_alloc(size_t len)
diff --git a/dlls/hhctrl.ocx/search.c b/dlls/hhctrl.ocx/search.c
new file mode 100644
index 0000000..c584b66
--- /dev/null
+++ b/dlls/hhctrl.ocx/search.c
@@ -0,0 +1,247 @@
+/*
+ * 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);
+
+static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage,
+ const WCHAR *folder, const char *needle);
+
+/* Allocate a ListView entry for a search result. */
+static SearchItem *alloc_search_item(WCHAR *title, const WCHAR *filename)
+{
+ int filename_len = filename ? (strlenW(filename)+1)*sizeof(WCHAR) : 0;
+ SearchItem *item;
+
+ item = heap_alloc_zero(sizeof(SearchItem));
+ if(filename)
+ {
+ item->filename = heap_alloc(filename_len);
+ memcpy(item->filename, filename, filename_len);
+ }
+ item->title = title; /* Already allocated */
+
+ return item;
+}
+
+/* Fill the ListView object corresponding to the found Search tab items */
+static void fill_search_tree(HWND hwndList, SearchItem *item)
+{
+ int index = 0;
+ LVITEMW lvi;
+
+ SendMessageW(hwndList, LVM_DELETEALLITEMS, 0, 0);
+ while(item) {
+ TRACE("list debug: %s\n", debugstr_w(item->filename));
+
+ memset(&lvi, 0, sizeof(lvi));
+ lvi.iItem = index++;
+ lvi.mask = LVIF_TEXT|LVIF_PARAM;
+ lvi.cchTextMax = strlenW(item->title)+1;
+ lvi.pszText = item->title;
+ lvi.lParam = (LPARAM)item;
+ item->id = (HTREEITEM)SendMessageW(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&lvi);
+ item = item->next;
+ }
+}
+
+/* Search the CHM storage stream (an HTML file) for the requested text.
+ *
+ * Before searching the HTML file all HTML tags are removed so that only
+ * the content of the document is scanned. If the search string is found
+ * then the title of the document is returned.
+ */
+static WCHAR *SearchCHM_File(IStorage *pStorage, const WCHAR *file, const char *needle)
+{
+ char *buffer = heap_alloc(BLOCK_SIZE);
+ strbuf_t content, node, node_name;
+ IStream *temp_stream = NULL;
+ DWORD i, buffer_size = 0;
+ WCHAR *title = NULL;
+ BOOL found = FALSE;
+ stream_t stream;
+ HRESULT hres;
+
+ hres = IStorage_OpenStream(pStorage, file, NULL, STGM_READ, 0, &temp_stream);
+ if(FAILED(hres)) {
+ FIXME("Could not open '%s' stream: %08x\n", debugstr_w(file), hres);
+ goto cleanup;
+ }
+
+ strbuf_init(&node);
+ strbuf_init(&content);
+ strbuf_init(&node_name);
+
+ stream_init(&stream, temp_stream);
+
+ /* Remove all HTML formatting and record the title */
+ buffer = heap_alloc(0);
+ while(next_node(&stream, &node)) {
+ get_node_name(&node, &node_name);
+
+ if(next_content(&stream, &content) && content.len > 1)
+ {
+ char *text = &content.buf[1];
+ int textlen = content.len-1;
+
+ if(!strcasecmp(node_name.buf, "title"))
+ {
+ int wlen = MultiByteToWideChar(CP_ACP, 0, text, textlen, NULL, 0);
+ title = heap_alloc((wlen+1)*sizeof(WCHAR));
+ MultiByteToWideChar(CP_ACP, 0, text, textlen, title, wlen);
+ title[wlen] = 0;
+ }
+
+ buffer = heap_realloc(buffer, buffer_size + textlen + 1);
+ memcpy(&buffer[buffer_size], text, textlen);
+ buffer[buffer_size + textlen] = '\0';
+ buffer_size += textlen;
+ }
+
+ strbuf_zero(&node);
+ strbuf_zero(&content);
+ }
+
+ /* Convert the buffer to lower case for comparison against the
+ * requested text (already in lower case).
+ */
+ for(i=0;i<buffer_size;i++)
+ buffer[i] = tolower(buffer[i]);
+
+ /* Search the decoded buffer for the requested text */
+ if(strstr(buffer, needle))
+ found = TRUE;
+
+ strbuf_free(&node);
+ strbuf_free(&content);
+ strbuf_free(&node_name);
+
+cleanup:
+ heap_free(buffer);
+ if(temp_stream)
+ IStream_Release(temp_stream);
+ if(!found)
+ {
+ heap_free(title);
+ return NULL;
+ }
+ return title;
+}
+
+/* Search all children of a CHM storage object for the requested text and
+ * return the last found search item.
+ */
+static SearchItem *SearchCHM_Storage(SearchItem *item, IStorage *pStorage,
+ const char *needle)
+{
+ const WCHAR szHTMext[] = {'.','h','t','m',0};
+ IEnumSTATSTG *elem = NULL;
+ WCHAR *filename = NULL;
+ STATSTG entries;
+ HRESULT hres;
+ ULONG retr;
+
+ hres = IStorage_EnumElements(pStorage, 0, NULL, 0, &elem);
+ if(hres != S_OK)
+ {
+ FIXME("Could not enumerate '/' storage elements: %08x\n", hres);
+ return NULL;
+ }
+ while (IEnumSTATSTG_Next(elem, 1, &entries, &retr) == NOERROR)
+ {
+ switch(entries.type) {
+ case STGTY_STORAGE:
+ item = SearchCHM_Folder(item, pStorage, entries.pwcsName, needle);
+ break;
+ case STGTY_STREAM:
+ filename = entries.pwcsName;
+ while(strchrW(filename, '/'))
+ filename = strchrW(filename, '/')+1;
+ if(strstrW(filename, szHTMext))
+ {
+ WCHAR *title = SearchCHM_File(pStorage, filename, needle);
+
+ if(title)
+ {
+ item->next = alloc_search_item(title, entries.pwcsName);
+ item = item->next;
+ }
+ }
+ break;
+ default:
+ FIXME("Unhandled IStorage stream element.\n");
+ }
+ }
+ return item;
+}
+
+/* Open a CHM storage object (folder) by name and find all items with
+ * the requested text. The last found item is returned.
+ */
+static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage,
+ const WCHAR *folder, const char *needle)
+{
+ IStorage *temp_storage = NULL;
+ HRESULT hres;
+
+ hres = IStorage_OpenStorage(pStorage, folder, NULL, STGM_READ, NULL, 0, &temp_storage);
+ if(FAILED(hres))
+ {
+ FIXME("Could not open '%s' storage object: %08x\n", debugstr_w(folder), hres);
+ return NULL;
+ }
+ item = SearchCHM_Storage(item, temp_storage, needle);
+
+ IStorage_Release(temp_storage);
+ return item;
+}
+
+/* Search the entire CHM file for the requested text and add all of
+ * the found items to a ListView for the user to choose the item
+ * they want.
+ */
+void InitSearch(HHInfo *info, const char *needle)
+{
+ CHMInfo *chm = info->pCHMInfo;
+ SearchItem *root_item = alloc_search_item(NULL, NULL);
+
+ SearchCHM_Storage(root_item, chm->pStorage, needle);
+ fill_search_tree(info->search.hwndList, root_item->next);
+ if(info->search.root)
+ ReleaseSearch(info);
+ info->search.root = root_item;
+}
+
+/* Free all of the found Search items. */
+void ReleaseSearch(HHInfo *info)
+{
+ SearchItem *item = info->search.root;
+
+ info->search.root = NULL;
+ while(item) {
+ heap_free(item->filename);
+ item = item->next;
+ }
+}
diff --git a/dlls/hhctrl.ocx/stream.c b/dlls/hhctrl.ocx/stream.c
index 2d4ecf5..317eeeb 100644
--- a/dlls/hhctrl.ocx/stream.c
+++ b/dlls/hhctrl.ocx/stream.c
@@ -98,6 +98,18 @@
strbuf_append(name, "", 1);
}
+/* Return the stream content up to the next HTML tag.
+ *
+ * Note: the first returned character is the end of the last tag (>).
+ */
+BOOL next_content(stream_t *stream, strbuf_t *buf)
+{
+ if(!stream_chr(stream, buf, '<'))
+ return FALSE;
+
+ return TRUE;
+}
+
BOOL next_node(stream_t *stream, strbuf_t *buf)
{
if(!stream_chr(stream, NULL, '<'))
diff --git a/dlls/hhctrl.ocx/stream.h b/dlls/hhctrl.ocx/stream.h
index 385f6f2..a03fd46 100644
--- a/dlls/hhctrl.ocx/stream.h
+++ b/dlls/hhctrl.ocx/stream.h
@@ -41,6 +41,7 @@
void stream_init(stream_t *stream, IStream *str);
BOOL stream_chr(stream_t *stream, strbuf_t *buf, char c);
void get_node_name(strbuf_t *node, strbuf_t *name);
+BOOL next_content(stream_t *stream, strbuf_t *buf);
BOOL next_node(stream_t *stream, strbuf_t *buf);
const char *get_attr(const char *node, const char *name, int *len);