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);