From 6173bb0e6c3bf51cd463f7bc4f725429d9087b2b Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Tue, 5 Oct 2010 19:14:46 +0000 Subject: Merge treeview-redux to trunk svn path=/trunk/netsurf/; revision=10865 --- desktop/tree_url_node.c | 846 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 846 insertions(+) create mode 100644 desktop/tree_url_node.c (limited to 'desktop/tree_url_node.c') diff --git a/desktop/tree_url_node.c b/desktop/tree_url_node.c new file mode 100644 index 000000000..3bc9f90fc --- /dev/null +++ b/desktop/tree_url_node.c @@ -0,0 +1,846 @@ +/* + * Copyright 2005 Richard Wilson + * Copyright 2009 Paul Blokus + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** \file + * Creation of URL nodes with use of trees (implementation) + */ + + +#include +#include +#include +#include + +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/browser.h" +#include "desktop/options.h" +#include "desktop/tree_url_node.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/url.h" +#include "utils/utils.h" + +/** Flags for each type of url tree node. */ +enum tree_element_url { + TREE_ELEMENT_URL = 0x01, + TREE_ELEMENT_LAST_VISIT = 0x02, + TREE_ELEMENT_VISITS = 0x03, + TREE_ELEMENT_THUMBNAIL = 0x04, +}; + +#define MAX_ICON_NAME_LEN 256 + +static bool initialised = false; + +static hlcache_handle *folder_icon; + +struct icon_entry { + content_type type; + hlcache_handle *icon; +}; + +struct icon_entry icon_table[] = { + {CONTENT_HTML, NULL}, + {CONTENT_TEXTPLAIN, NULL}, + {CONTENT_CSS, NULL}, +#if defined(WITH_MNG) || defined(WITH_PNG) + {CONTENT_PNG, NULL}, +#endif +#ifdef WITH_MNG + {CONTENT_JNG, NULL}, + {CONTENT_MNG, NULL}, +#endif +#ifdef WITH_JPEG + {CONTENT_JPEG, NULL}, +#endif +#ifdef WITH_GIF + {CONTENT_GIF, NULL}, +#endif +#ifdef WITH_BMP + {CONTENT_BMP, NULL}, + {CONTENT_ICO, NULL}, +#endif +#ifdef WITH_SPRITE + {CONTENT_SPRITE, NULL}, +#endif +#ifdef WITH_DRAW + {CONTENT_DRAW, NULL}, +#endif +#ifdef WITH_ARTWORKS + {CONTENT_ARTWORKS, NULL}, +#endif +#ifdef WITH_NS_SVG + {CONTENT_SVG, NULL}, +#endif + {CONTENT_UNKNOWN, NULL}, + + /* this serves as a sentinel */ + {CONTENT_HTML, NULL} +}; + + +void tree_url_node_init(void) +{ + struct icon_entry *entry; + char icon_name[MAX_ICON_NAME_LEN]; + + if (initialised || option_tree_icons_dir == NULL) + return; + initialised = true; + + folder_icon = tree_load_icon(tree_directory_icon_name); + + entry = icon_table; + do { + + tree_icon_name_from_content_type(icon_name, entry->type); + entry->icon = tree_load_icon(icon_name); + + ++entry; + } while (entry->type != CONTENT_HTML); + +} + + +/** + * Creates a tree entry for a URL, and links it into the tree + * + * \param parent the node to link to + * \param url the URL (copied) + * \param data the URL data to use + * \param title the custom title to use + * \return the node created, or NULL for failure + */ +struct node *tree_create_URL_node(struct tree *tree, struct node *parent, + const char *url, const char *title, + tree_node_user_callback user_callback, void *callback_data) +{ + struct node *node; + struct node_element *element; + char *text_cp, *squashed; + + squashed = squash_whitespace(title ? title : url); + text_cp = strdup(squashed); + if (text_cp == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + free(squashed); + node = tree_create_leaf_node(tree, parent, text_cp, true, false, + false); + if (node == NULL) { + free(text_cp); + return NULL; + } + + if (user_callback != NULL) + tree_set_node_user_callback(node, user_callback, + callback_data); + + tree_create_node_element(node, NODE_ELEMENT_BITMAP, + TREE_ELEMENT_THUMBNAIL, false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS, + false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_LAST_VISIT, false); + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_URL, true); + if (element != NULL) { + text_cp = strdup(url); + if (text_cp == NULL) { + tree_delete_node(tree, node, false); + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + tree_update_node_element(tree, element, text_cp, NULL); + } + + return node; +} + + +/** + * Creates a tree entry for a URL, and links it into the tree. + * + * All information is used directly from the url_data, and as such cannot be + * edited and should never be freed. + * + * \param parent the node to link to + * \param url the URL + * \param data the URL data to use + * \return the node created, or NULL for failure + */ +struct node *tree_create_URL_node_shared(struct tree *tree, struct node *parent, + const char *url, const struct url_data *data, + tree_node_user_callback user_callback, void *callback_data) +{ + struct node *node; + struct node_element *element; + const char *title; + + assert(url && data); + + if (data->title != NULL) { + title = data->title; + } else { + title = url; + } + + node = tree_create_leaf_node(tree, parent, title, false, false, false); + if (node == NULL) + return NULL; + + if (user_callback != NULL) { + tree_set_node_user_callback(node, user_callback, + callback_data); + } + + tree_create_node_element(node, NODE_ELEMENT_BITMAP, + TREE_ELEMENT_THUMBNAIL, false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS, + false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_LAST_VISIT, false); + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_URL, false); + if (element != NULL) { + tree_update_node_element(tree, element, url, NULL); + } + + tree_update_URL_node(tree, node, url, data, true); + return node; +} + + +/** + * Updates the node details for a URL node. + * + * \param node the node to update + */ +void tree_update_URL_node(struct tree *tree, struct node *node, + const char *url, const struct url_data *data, bool shared) +{ + struct node_element *element; + struct bitmap *bitmap = NULL; + struct icon_entry *entry; + char *text_cp; + + assert(node != NULL); + + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + if (element == NULL) + return; + + if (data != NULL) { + if (data->title == NULL) + urldb_set_url_title(url, url); + + if (data->title == NULL) + return; + + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, + NULL); + if (shared) + tree_update_node_element(tree, element, data->title, + NULL); + else { + text_cp = strdup(data->title); + if (text_cp == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return; + } + tree_update_node_element(tree, element, text_cp, NULL); + } + } else { + data = urldb_get_url_data(url); + if (data == NULL) + return; + } + + entry = icon_table; + do { + if (entry->type == data->type) { + if (entry->icon != NULL) + tree_set_node_icon(tree, node, entry->icon); + break; + } + ++entry; + } while (entry->type != CONTENT_HTML); + + /* update last visit text */ + element = tree_node_find_element(node, TREE_ELEMENT_LAST_VISIT, element); + tree_update_element_text(tree, + element, + messages_get_buff("TreeLast", + (data->last_visit > 0) ? + ctime((time_t *)&data->last_visit) : + messages_get("TreeUnknown"))); + + + /* update number of visits text */ + element = tree_node_find_element(node, TREE_ELEMENT_VISITS, element); + tree_update_element_text(tree, + element, + messages_get_buff("TreeVisits", data->visits)); + + + /* update thumbnail */ + element = tree_node_find_element(node, TREE_ELEMENT_THUMBNAIL, element); + if (element != NULL) { + bitmap = urldb_get_thumbnail(url); + + if (bitmap != NULL) { + tree_update_node_element(tree, element, NULL, bitmap); + } + } +} + + +const char *tree_url_node_get_title(struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + if (element == NULL) + return NULL; + return tree_node_element_get_text(element); +} + + +const char *tree_url_node_get_url(struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + if (element == NULL) + return NULL; + return tree_node_element_get_text(element); +} + +void tree_url_node_edit_title(struct tree *tree, struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + tree_start_edit(tree, element); +} + +void tree_url_node_edit_url(struct tree *tree, struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + tree_start_edit(tree, element); +} + +node_callback_resp tree_url_node_callback(void *user_data, + struct node_msg_data *msg_data) +{ + struct tree *tree; + struct node_element *element; + url_func_result res; + const char *text; + char *norm_text, *escaped_text; + const struct url_data *data; + + /** @todo memory leaks on non-shared folder deletion. */ + switch (msg_data->msg) { + case NODE_DELETE_ELEMENT_TXT: + switch (msg_data->flag) { + /* only history is using non-editable url + * elements so only history deletion will run + * this code + */ + case TREE_ELEMENT_URL: + /* reset URL characteristics */ + urldb_reset_url_visit_data( + msg_data->data.text); + return NODE_CALLBACK_HANDLED; + case TREE_ELEMENT_TITLE: + return NODE_CALLBACK_HANDLED; + } + break; + case NODE_DELETE_ELEMENT_IMG: + if (msg_data->flag == TREE_ELEMENT_THUMBNAIL || + msg_data->flag == TREE_ELEMENT_TITLE) + return NODE_CALLBACK_HANDLED; + break; + case NODE_LAUNCH: + element = tree_node_find_element(msg_data->node, + TREE_ELEMENT_URL, NULL); + if (element != NULL) { + text = tree_node_element_get_text(element); + browser_window_create(text, NULL, 0, + true, false); + return NODE_CALLBACK_HANDLED; + } + break; + case NODE_ELEMENT_EDIT_FINISHING: + + text = msg_data->data.text; + + if (msg_data->flag == TREE_ELEMENT_URL) { + res = url_escape(text, 0, false, NULL, + &escaped_text); + if (res == URL_FUNC_OK) + res = url_normalize(escaped_text, + &norm_text); + if (res != URL_FUNC_OK) { + if (res == URL_FUNC_FAILED) { + warn_user("NoURLError", 0); + return NODE_CALLBACK_CONTINUE; + } + else { + warn_user("NoMemory", 0); + return NODE_CALLBACK_REJECT; + } + + } + msg_data->data.text = norm_text; + + data = urldb_get_url_data(norm_text); + if (data == NULL) { + urldb_add_url(norm_text); + urldb_set_url_persistence(norm_text, + true); + data = urldb_get_url_data(norm_text); + if (data == NULL) + return NODE_CALLBACK_REJECT; + } + tree = user_data; + tree_update_URL_node(tree, msg_data->node, + norm_text, NULL, false); + } + else if (msg_data->flag == TREE_ELEMENT_TITLE) { + while (isspace(*text)) + text++; + norm_text = strdup(text); + if (norm_text == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NODE_CALLBACK_REJECT; + } + /* don't allow zero length entry text, return + false */ + if (norm_text[0] == '\0') { + warn_user("NoNameError", 0); + msg_data->data.text = NULL; + return NODE_CALLBACK_CONTINUE; + } + msg_data->data.text = norm_text; + } + + return NODE_CALLBACK_HANDLED; + default: + break; + } + return NODE_CALLBACK_NOT_HANDLED; +} + +/** + * Search the children of an xmlNode for an element. + * + * \param node xmlNode to search children of, or 0 + * \param name name of element to find + * \return first child of node which is an element and matches name, or + * 0 if not found or parameter node is 0 + */ +static xmlNode *tree_url_find_xml_element(xmlNode *node, const char *name) +{ + xmlNode *xmlnode; + if (node == NULL) + return NULL; + + for (xmlnode = node->children; + xmlnode && !(xmlnode->type == XML_ELEMENT_NODE && + strcmp((const char *) xmlnode->name, name) == 0); + xmlnode = xmlnode->next) + ; + + return xmlnode; +} + +/** + * Parse an entry represented as a li. + * + * \param li xmlNode for parsed li + * \param directory directory to add this entry to + */ +static void tree_url_load_entry(xmlNode *li, struct tree *tree, + struct node *directory, tree_node_user_callback callback, + void *callback_data) +{ + char *url = NULL, *url1 = NULL; + char *title = NULL; + struct node *entry; + xmlNode *xmlnode; + const struct url_data *data; + url_func_result res; + + for (xmlnode = li->children; xmlnode; xmlnode = xmlnode->next) { + /* The li must contain an "a" element */ + if (xmlnode->type == XML_ELEMENT_NODE && + strcmp((const char *)xmlnode->name, "a") == 0) { + url1 = (char *)xmlGetProp(xmlnode, (const xmlChar *) "href"); + title = (char *)xmlNodeGetContent(xmlnode); + } + } + + if ((url1 == NULL) || (title == NULL)) { + warn_user("TreeLoadError", "(Missing in
  • or " + "memory exhausted.)"); + return; + } + + /* We're loading external input. + * This may be garbage, so attempt to normalise + */ + res = url_normalize(url1, &url); + if (res != URL_FUNC_OK) { + LOG(("Failed normalising '%s'", url1)); + + if (res == URL_FUNC_NOMEM) + warn_user("NoMemory", NULL); + + xmlFree(url1); + xmlFree(title); + + return; + } + + /* No longer need this */ + xmlFree(url1); + + data = urldb_get_url_data(url); + if (data == NULL) { + /* No entry in database, so add one */ + urldb_add_url(url); + /* now attempt to get url data */ + data = urldb_get_url_data(url); + } + if (data == NULL) { + xmlFree(title); + free(url); + + return; + } + + /* Make this URL persistent */ + urldb_set_url_persistence(url, true); + + if (data->title == NULL) + urldb_set_url_title(url, title); + + entry = tree_create_URL_node(tree, directory, url, title, + callback, callback_data); + + if (entry == NULL) { + /** \todo why isn't this fatal? */ + warn_user("NoMemory", 0); + } else { + tree_update_URL_node(tree, entry, url, data, false); + } + + + xmlFree(title); + free(url); +} + +/** + * Parse a directory represented as a ul. + * + * \param ul xmlNode for parsed ul + * \param directory directory to add this directory to + */ +static void tree_url_load_directory(xmlNode *ul, struct tree *tree, + struct node *directory, tree_node_user_callback callback, + void *callback_data) +{ + char *title; + struct node *dir; + xmlNode *xmlnode; + + assert(ul != NULL); + assert(directory != NULL); + + for (xmlnode = ul->children; xmlnode; xmlnode = xmlnode->next) { + /* The ul may contain entries as a li, or directories as + * an h4 followed by a ul. Non-element nodes may be present + * (eg. text, comments), and are ignored. */ + + if (xmlnode->type != XML_ELEMENT_NODE) + continue; + + if (strcmp((const char *)xmlnode->name, "li") == 0) { + /* entry */ + tree_url_load_entry(xmlnode, tree, directory, callback, + callback_data); + + } else if (strcmp((const char *)xmlnode->name, "h4") == 0) { + /* directory */ + title = (char *) xmlNodeGetContent(xmlnode ); + if (!title) { + warn_user("TreeLoadError", "(Empty

    " + "or memory exhausted.)"); + return; + } + + for (xmlnode = xmlnode->next; + xmlnode && xmlnode->type != XML_ELEMENT_NODE; + xmlnode = xmlnode->next) + ; + if ((xmlnode == NULL) || + strcmp((const char *)xmlnode->name, "ul") != 0) { + /* next element isn't expected ul */ + free(title); + warn_user("TreeLoadError", "(Expected " + "
      not present.)"); + return; + } + + dir = tree_create_folder_node(tree, directory, title, + true, false, false); + if (dir == NULL) { + free(title); + return; + } + + if (callback != NULL) + tree_set_node_user_callback(dir, callback, + callback_data); + + if (folder_icon != NULL) + tree_set_node_icon(tree, dir, folder_icon); + + tree_url_load_directory(xmlnode, tree, dir, callback, + callback_data); + } + } +} + +/** + * Loads an url tree from a specified file. + * + * \param filename name of file to read + * \param tree empty tree which data will be read into + * \return the file represented as a tree, or NULL on failure + */ +bool tree_urlfile_load(const char *filename, struct tree *tree, + tree_node_user_callback callback, void *callback_data) +{ + xmlDoc *doc; + xmlNode *html, *body, *ul; + struct node *root; + FILE *fp = NULL; + + if (filename == NULL) { + return false; + } + + fp = fopen(filename, "r"); + if (fp == NULL) { + return false; + } + fclose(fp); + + doc = htmlParseFile(filename, "iso-8859-1"); + if (doc == NULL) { + warn_user("TreeLoadError", messages_get("ParsingFail")); + return false; + } + + html = tree_url_find_xml_element((xmlNode *) doc, "html"); + body = tree_url_find_xml_element(html, "body"); + ul = tree_url_find_xml_element(body, "ul"); + if (ul == NULL) { + xmlFreeDoc(doc); + warn_user("TreeLoadError", + "(......
        not found.)"); + return false; + } + + root = tree_get_root(tree); + tree_url_load_directory(ul, tree, root, callback, callback_data); + tree_set_node_expanded(tree, root, true, false, false); + + xmlFreeDoc(doc); + return true; +} + +/** + * Add an entry to the HTML tree for saving. + * + * The node must contain a sequence of node_elements in the following order: + * + * \param entry hotlist entry to add + * \param node node to add li to + * \return true on success, false on memory exhaustion + */ +static bool tree_url_save_entry(struct node *entry, xmlNode *node) +{ + xmlNode *li, *a; + xmlAttr *href; + const char *text; + + li = xmlNewChild(node, NULL, (const xmlChar *) "li", NULL); + if (li == NULL) + return false; + + + text = tree_url_node_get_title(entry); + if (text == NULL) + return false; + a = xmlNewTextChild(li, NULL, (const xmlChar *) "a", + (const xmlChar *) text); + if (a == NULL) + return false; + + text = tree_url_node_get_url(entry); + if (text == NULL) + return false; + + href = xmlNewProp(a, (const xmlChar *) "href", (const xmlChar *) text); + if (href == NULL) + return false; + return true; +} + +/** + * Add a directory to the HTML tree for saving. + * + * \param directory hotlist directory to add + * \param node node to add ul to + * \return true on success, false on memory exhaustion + */ +static bool tree_url_save_directory(struct node *directory, xmlNode *node) +{ + struct node *child; + xmlNode *ul, *h4; + const char *text; + + ul = xmlNewChild(node, NULL, (const xmlChar *)"ul", NULL); + if (ul == NULL) + return false; + + for (child = tree_node_get_child(directory); child; + child = tree_node_get_next(child)) { + if (!tree_node_is_folder(child)) { + /* entry */ + if (!tree_url_save_entry(child, ul)) + return false; + } else { + /* directory */ + /* invalid HTML */ + + text = tree_url_node_get_title(child); + if (text == NULL) + return false; + + h4 = xmlNewTextChild(ul, NULL, + (const xmlChar *) "h4", + (const xmlChar *) text); + if (h4 == NULL) + return false; + + if (!tree_url_save_directory(child, ul)) + return false; + } } + + return true; +} + + + + + + + + +/** + * Perform a save to a specified file in the form of a html page + * + * \param filename the file to save to + * \param page_title title of the page + */ +bool tree_urlfile_save(struct tree *tree, const char *filename, + const char *page_title) +{ + int res; + xmlDoc *doc; + xmlNode *html, *head, *title, *body; + + /* Unfortunately the Browse Hotlist format is invalid HTML, + * so this is a lie. + */ + doc = htmlNewDoc( + (const xmlChar *) "http://www.w3.org/TR/html4/strict.dtd", + (const xmlChar *) "-//W3C//DTD HTML 4.01//EN"); + if (doc == NULL) { + warn_user("NoMemory", 0); + return false; + } + + html = xmlNewNode(NULL, (const xmlChar *) "html"); + if (html == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + xmlDocSetRootElement(doc, html); + + head = xmlNewChild(html, NULL, (const xmlChar *) "head", NULL); + if (head == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + title = xmlNewTextChild(head, NULL, (const xmlChar *) "title", + (const xmlChar *) page_title); + if (title == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + body = xmlNewChild(html, NULL, (const xmlChar *) "body", NULL); + if (body == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + if (!tree_url_save_directory(tree_get_root(tree), body)) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + doc->charset = XML_CHAR_ENCODING_UTF8; + res = htmlSaveFileEnc(filename, doc, "iso-8859-1"); + if (res == -1) { + warn_user("HotlistSaveError", 0); + xmlFreeDoc(doc); + return false; + } + + xmlFreeDoc(doc); + return true; +} -- cgit v1.2.3