/* * This file is part of NetSurf, http://netsurf.sourceforge.net/ * Licensed under the GNU General Public License, * http://www.opensource.org/licenses/gpl-license * Copyright 2004 Richard Wilson */ /** \file * Hotlist (implementation). */ #include #include #include #include #include #include "libxml/HTMLparser.h" #include "libxml/HTMLtree.h" #include "oslib/colourtrans.h" #include "oslib/dragasprite.h" #include "oslib/osfile.h" #include "oslib/wimp.h" #include "oslib/wimpspriteop.h" #include "netsurf/content/content.h" #include "netsurf/riscos/gui.h" #include "netsurf/riscos/theme.h" #include "netsurf/riscos/tinct.h" #include "netsurf/riscos/wimp.h" #include "netsurf/utils/log.h" #include "netsurf/utils/messages.h" #include "netsurf/utils/utils.h" #include "netsurf/utils/url.h" #define HOTLIST_EXPAND 0 #define HOTLIST_COLLAPSE 1 #define HOTLIST_ENTRY 2 #define HOTLIST_LINE 3 #define HOTLIST_TLINE 4 #define HOTLIST_BLINE 5 #define HOTLIST_TEXT_BUFFER 256 #define HOTLIST_LEAF_INSET 32 #define HOTLIST_ICON_WIDTH 36 #define HOTLIST_LINE_HEIGHT 44 #define HOTLIST_TEXT_PADDING 16 struct hotlist_entry { /** The next hotlist entry at this level, or NULL for no more */ struct hotlist_entry *next_entry; /** The child hotlist entry (NULL for no children). The children value must be set for this value to take effect. */ struct hotlist_entry *child_entry; /** The hotlist entry that has this entry as its next entry */ struct hotlist_entry *previous_entry; /** The hotlist entry that this is a child of */ struct hotlist_entry *parent_entry; /** The number of children (-1 for non-folders, >=0 for folders) */ int children; /** The title of the hotlist entry/folder, UTF-8. */ char *title; /** The URL of the hotlist entry (NULL for folders) */ char *url; /** Whether this entry is expanded */ bool expanded; /** Whether this entry is selected */ bool selected; /** The content filetype (not for folders) */ int filetype; /** The number of visits */ int visits; /** Add/last visit dates */ time_t add_date; time_t last_date; /** Position on last reformat (relative to window origin) */ int x0; int y0; int width; int height; /** Cached values */ int collapsed_width; int expanded_width; /** The width of the various lines sub-text */ int widths[4]; /** Whether the item is awaiting processing */ bool process; }; /* A basic window for the toolbar and status */ static wimp_window hotlist_window_definition = { {0, 0, 600, 800}, 0, 0, wimp_TOP, wimp_WINDOW_NEW_FORMAT | wimp_WINDOW_MOVEABLE | wimp_WINDOW_BACK_ICON | wimp_WINDOW_CLOSE_ICON | wimp_WINDOW_TITLE_ICON | wimp_WINDOW_TOGGLE_ICON | wimp_WINDOW_SIZE_ICON | wimp_WINDOW_VSCROLL, wimp_COLOUR_BLACK, wimp_COLOUR_LIGHT_GREY, wimp_COLOUR_LIGHT_GREY, wimp_COLOUR_WHITE, wimp_COLOUR_DARK_GREY, wimp_COLOUR_MID_LIGHT_GREY, wimp_COLOUR_CREAM, 0, {0, -800, 16384, 0}, wimp_ICON_TEXT | wimp_ICON_INDIRECTED | wimp_ICON_HCENTRED | wimp_ICON_VCENTRED, wimp_BUTTON_DOUBLE_CLICK_DRAG << wimp_ICON_BUTTON_TYPE_SHIFT, wimpspriteop_AREA, 1, 256, {""}, 0 }; /* An icon to plot text with */ static wimp_icon text_icon; static wimp_icon sprite_icon; /* Temporary workspace for plotting */ static char drag_name[12]; static char icon_name[12]; static char extended_text[HOTLIST_TEXT_BUFFER]; /* Whether a reformat is pending */ static bool reformat_pending = false; static int max_width = 0; static int max_height = 0; /* The hotlist window, toolbar and plot origins */ wimp_w hotlist_window; struct toolbar *hotlist_toolbar = NULL; static int origin_x, origin_y; /* The current redraw rectangle */ static int clip_x0, clip_y0, clip_x1, clip_y1; /* The root entry */ static struct hotlist_entry root; /* The sprite header addresses for Tinct */ static char *sprite[6]; /* The drag buttons */ bool dragging; wimp_mouse_state drag_buttons; /* Whether the current selection was from a menu click */ bool menu_selection = false; bool menu_open = false; /* Whether the editing facilities are for add so that we know how to reset the dialog boxes on a adjust-cancel and the action to perform on ok. */ bool dialog_folder_add = false; bool dialog_entry_add = false; bool hotlist_insert = false; static bool ro_gui_hotlist_initialise_sprite(const char *name, int number); static bool ro_gui_hotlist_load(void); static void ro_gui_hotlist_load_file(const char *filename); static void ro_gui_hotlist_load_directory(xmlNode *ul, struct hotlist_entry *directory); static void ro_gui_hotlist_load_entry(xmlNode *li, struct hotlist_entry *directory); xmlNode *ro_gui_hotlist_find_element(xmlNode *node, const char *name); bool ro_gui_hotlist_save_directory(struct hotlist_entry *directory, xmlNode *node); bool ro_gui_hotlist_save_entry(struct hotlist_entry *entry, xmlNode *node); bool ro_gui_hotlist_save_entry_comment(xmlNode *node, const char *name, int value); static void ro_gui_hotlist_link_entry(struct hotlist_entry *link, struct hotlist_entry *entry, bool before); static void ro_gui_hotlist_delink_entry(struct hotlist_entry *entry); static void ro_gui_hotlist_delete_entry(struct hotlist_entry *entry, bool siblings); static void ro_gui_hotlist_visited_update(struct content *content, struct hotlist_entry *entry); static int ro_gui_hotlist_redraw_tree(struct hotlist_entry *entry, int level, int x0, int y0); static int ro_gui_hotlist_redraw_item(struct hotlist_entry *entry, int level, int x0, int y0); static struct hotlist_entry *ro_gui_hotlist_create_entry(const char *title, const char *url, int filetype, struct hotlist_entry *folder); static void ro_gui_hotlist_update_entry_size(struct hotlist_entry *entry); static struct hotlist_entry *ro_gui_hotlist_find_entry(int x, int y, struct hotlist_entry *entry); static int ro_gui_hotlist_selection_state(struct hotlist_entry *entry, bool selected, bool redraw); static void ro_gui_hotlist_selection_drag(struct hotlist_entry *entry, int x0, int y0, int x1, int y1, bool toggle, bool redraw); static int ro_gui_hotlist_selection_count(struct hotlist_entry *entry, bool folders); static void ro_gui_hotlist_update_expansion(struct hotlist_entry *entry, bool only_selected, bool folders, bool links, bool expand, bool contract); static void ro_gui_hotlist_launch_selection(struct hotlist_entry *entry); static void ro_gui_hotlist_invalidate_statistics(struct hotlist_entry *entry); static struct hotlist_entry *ro_gui_hotlist_first_selection(struct hotlist_entry *entry); static void ro_gui_hotlist_selection_to_process(struct hotlist_entry *entry); static bool ro_gui_hotlist_move_processing(struct hotlist_entry *entry, struct hotlist_entry *destination, bool before); #define hotlist_ensure_sprite(buffer, fallback) if (xwimpspriteop_read_sprite_info(buffer, 0, 0, 0, 0)) sprintf(buffer, fallback) #define hotlist_redraw_entry(entry, full) xwimp_force_redraw(hotlist_window, full ? 0 : entry->x0, \ full ? -16384 : entry->y0, full ? 16384 : entry->x0 + entry->expanded_width, entry->y0 + entry->height); #define hotlist_redraw_entry_title(entry) xwimp_force_redraw(hotlist_window, entry->x0, \ entry->y0 + entry->height - HOTLIST_LINE_HEIGHT, entry->x0 + entry->width, entry->y0 + entry->height); void ro_gui_hotlist_init(void) { const char *title; os_box extent = {0, 0, 0, 0}; os_error *error; /* Set the initial root options */ root.next_entry = NULL; root.child_entry = NULL; root.children = 0; root.expanded = true; /* Load the hotlist */ if (!ro_gui_hotlist_load()) return; /* Get our sprite ids for faster plotting. */ if (ro_gui_hotlist_initialise_sprite("expand", HOTLIST_EXPAND) || ro_gui_hotlist_initialise_sprite("collapse", HOTLIST_COLLAPSE) || ro_gui_hotlist_initialise_sprite("entry", HOTLIST_ENTRY) || ro_gui_hotlist_initialise_sprite("line", HOTLIST_LINE) || ro_gui_hotlist_initialise_sprite("halflinet", HOTLIST_TLINE) || ro_gui_hotlist_initialise_sprite("halflineb", HOTLIST_BLINE)) { return; } /* Update our text icon */ text_icon.data.indirected_text.validation = (char *) -1; text_icon.data.indirected_text.size = 256; sprite_icon.flags = wimp_ICON_SPRITE | wimp_ICON_INDIRECTED | wimp_ICON_HCENTRED | wimp_ICON_VCENTRED | (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | (wimp_COLOUR_VERY_LIGHT_GREY << wimp_ICON_BG_COLOUR_SHIFT); sprite_icon.data.indirected_sprite.area = wimpspriteop_AREA; sprite_icon.data.indirected_text.size = 12; /* Create our window */ title = messages_get("Hotlist"); hotlist_window_definition.title_data.indirected_text.text = title; hotlist_window_definition.title_data.indirected_text.validation = (char *) -1; hotlist_window_definition.title_data.indirected_text.size = strlen(title); error = xwimp_create_window(&hotlist_window_definition, &hotlist_window); if (error) { LOG(("xwimp_create_window: 0x%x: %s", error->errnum, error->errmess)); die(error->errmess); } /* Create our toolbar */ hotlist_toolbar = ro_gui_theme_create_toolbar(NULL, THEME_HOTLIST_TOOLBAR); ro_gui_theme_attach_toolbar(hotlist_toolbar, hotlist_window); /* Update the extent */ if (hotlist_toolbar) { extent.x1 = 16384; extent.y1 = hotlist_toolbar->height; extent.y0 = -16384; error = xwimp_set_extent(hotlist_window, &extent); if (error) { LOG(("xwimp_set_extent: 0x%x: %s", error->errnum, error->errmess)); die(error->errmess); } reformat_pending = true; } } /** * Initialise a hotlist sprite for use with Tinct * * \param name the name of the sprite * \param number the sprite cache number * \return whether an error occurred */ bool ro_gui_hotlist_initialise_sprite(const char *name, int number) { os_error *error; sprintf(icon_name, "tr_%s", name); error = xosspriteop_select_sprite(osspriteop_USER_AREA, gui_sprites, (osspriteop_id)icon_name, (osspriteop_header **)&sprite[number]); if (error) { warn_user("MiscError", error->errmess); LOG(("Failed to load hotlist sprite 'tr_%s'", name)); return true; } return false; } /** * Shows the hotlist window. */ void ro_gui_hotlist_show(void) { os_error *error; int screen_width, screen_height; wimp_window_state state; int dimension; int scroll_width; /* We may have failed to initialise */ if (!hotlist_window) return; /* Get the window state */ state.w = hotlist_window; error = xwimp_get_window_state(&state); if (error) { warn_user("WimpError", error->errmess); return; } /* If we're open we jump to the top of the stack, if not then we open in the centre of the screen. */ if (!(state.flags & wimp_WINDOW_OPEN)) { /* Clear the selection/expansion states */ ro_gui_hotlist_update_expansion(root.child_entry, false, true, true, false, true); ro_gui_hotlist_selection_state(root.child_entry, false, false); /* Get the current screen size */ ro_gui_screen_size(&screen_width, &screen_height); /* Move to the centre */ dimension = state.visible.x1 - state.visible.x0; scroll_width = ro_get_vscroll_width(hotlist_window); state.visible.x0 = (screen_width - (dimension + scroll_width)) / 2; state.visible.x1 = state.visible.x0 + dimension; dimension = state.visible.y1 - state.visible.y0; state.visible.y0 = (screen_height - dimension) / 2; state.visible.y1 = state.visible.y0 + dimension; state.xscroll = 0; state.yscroll = 0; if (hotlist_toolbar) state.yscroll = hotlist_toolbar->height; } /* Open the window at the top of the stack */ ro_gui_menu_prepare_hotlist(); state.next = wimp_TOP; error = xwimp_open_window((wimp_open*)&state); if (error) { warn_user("WimpError", error->errmess); return; } /* Set the caret position */ xwimp_set_caret_position(state.w, -1, -100, -100, 32, -1); } bool ro_gui_hotlist_load(void) { fileswitch_object_type obj_type = 0; struct hotlist_entry *netsurf; struct hotlist_entry *entry; /* Check if we have an initial hotlist. OS_File does funny things relating to errors, so we use the object type to determine success */ xosfile_read_stamped_no_path("Choices:WWW.NetSurf.Hotlist", &obj_type, (bits)0, (bits)0, (int *)0, (fileswitch_attr)0, (bits)0); if (obj_type == fileswitch_IS_FILE) { ro_gui_hotlist_load_file("Choices:WWW.NetSurf.Hotlist"); return true; } else { /* Create a folder */ netsurf = ro_gui_hotlist_create_entry("NetSurf", NULL, 0, &root); if (!netsurf) return false; /* Add some content */ entry = ro_gui_hotlist_create_entry("NetSurf homepage", "http://netsurf.sourceforge.net/", 0xfaf, netsurf); if (!entry) return false; entry->add_date = (time_t) -1; entry = ro_gui_hotlist_create_entry("NetSurf test builds", "http://netsurf.strcprstskrzkrk.co.uk/", 0xfaf, netsurf); if (!entry) return false; entry->add_date = (time_t) -1; /* We succeeded */ return true; } } /** * Load the hotlist from file. * * \param filename name of file to read */ void ro_gui_hotlist_load_file(const char *filename) { xmlDoc *doc; xmlNode *html, *body, *ul; doc = htmlParseFile(filename, "iso-8859-1"); if (!doc) { warn_user("HotlistLoadError", messages_get("ParsingFail")); return; } html = ro_gui_hotlist_find_element((xmlNode *) doc, "html"); body = ro_gui_hotlist_find_element(html, "body"); ul = ro_gui_hotlist_find_element(body, "ul"); if (!ul) { xmlFreeDoc(doc); warn_user("HotlistLoadError", "(......