netsurf/desktop/global_history.c
Michael Drake 9aecf47408 Treeview: Rationalise initialisation and finalisation.
Previously the expected behaviour for front ends using the correct
API for hotlist, global history, cookie manager, and ssl cert
viewer was that the front end would initialise the treeview module
on startup and finalise it on application exit.

However, this meant that the front ends had to include the core
treeview header, which they didn't otherwise need.

Since the tree module provided access to the new treeview utilities
through the old tree API, and was used by front ends with no changes
for the new treeview API, the tree layer refcounted initialisations
of treeview-based widgets, and only called the underlying treeview
init/fini functions when needed.

This change moves that refcounting into the treeview module.  Now
the hotlist, global history, cookie manager, and ssl cert viewer
widgets call call treeview init/fini as part of their own
initialisation and finalisation.  This means that front ends
using the correct APIs for treeview-based widgets don't need to
know anything about the underlying treeview, and the tree module
compatibility layer has had its treeview refcounting removed.

Finally, the treeview_init function took a font size parameter.
Now it does not and lit gets font size from config.  We probably
want to add a new `treeview_font_size` option to nsoptions, and
have differnent defaults on different platforms. 12pt on RISC OS,
and 11pt elsewhere, most likely.
2016-08-10 18:57:36 +01:00

1008 lines
22 KiB
C

/*
* Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "utils/messages.h"
#include "utils/utils.h"
#include "utils/utf8.h"
#include "utils/libdom.h"
#include "utils/log.h"
#include "utils/nsurl.h"
#include "content/urldb.h"
#include "desktop/global_history.h"
#include "desktop/treeview.h"
#include "netsurf/browser_window.h"
#define N_DAYS 28
#define N_SEC_PER_DAY (60 * 60 * 24)
enum global_history_folders {
GH_TODAY = 0,
GH_YESTERDAY,
GH_2_DAYS_AGO,
GH_3_DAYS_AGO,
GH_4_DAYS_AGO,
GH_5_DAYS_AGO,
GH_6_DAYS_AGO,
GH_LAST_WEEK,
GH_2_WEEKS_AGO,
GH_3_WEEKS_AGO,
GH_N_FOLDERS
};
enum global_history_fields {
GH_TITLE,
GH_URL,
GH_LAST_VISIT,
GH_VISITS,
GH_PERIOD,
N_FIELDS
};
struct global_history_folder {
treeview_node *folder;
struct treeview_field_data data;
};
struct global_history_ctx {
treeview *tree;
struct treeview_field_desc fields[N_FIELDS];
struct global_history_folder folders[GH_N_FOLDERS];
time_t today;
int weekday;
bool built;
};
struct global_history_ctx gh_ctx;
struct global_history_entry {
bool user_delete;
int slot;
nsurl *url;
time_t t;
treeview_node *entry;
struct global_history_entry *next;
struct global_history_entry *prev;
struct treeview_field_data data[N_FIELDS - 1];
};
struct global_history_entry *gh_list[N_DAYS];
/**
* Find an entry in the global history
*
* \param url The URL to find
* \return Pointer to history entry, or NULL if not found
*/
static struct global_history_entry *global_history_find(nsurl *url)
{
int i;
struct global_history_entry *e;
for (i = 0; i < N_DAYS; i++) {
e = gh_list[i];
while (e != NULL) {
if (nsurl_compare(e->url, url,
NSURL_COMPLETE) == true) {
/* Got a match */
return e;
}
e = e->next;
}
}
/* No match found */
return NULL;
}
/**
* Initialise the treeview directories
*
* \param f Ident for folder to create
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror global_history_create_dir(enum global_history_folders f)
{
nserror err;
treeview_node *relation = NULL;
enum treeview_relationship rel = TREE_REL_FIRST_CHILD;
const char *label;
int i;
switch (f) {
case GH_TODAY:
label = "DateToday";
break;
case GH_YESTERDAY:
label = "DateYesterday";
break;
case GH_2_DAYS_AGO:
label = "Date2Days";
break;
case GH_3_DAYS_AGO:
label = "Date3Days";
break;
case GH_4_DAYS_AGO:
label = "Date4Days";
break;
case GH_5_DAYS_AGO:
label = "Date5Days";
break;
case GH_6_DAYS_AGO:
label = "Date6Days";
break;
case GH_LAST_WEEK:
label = "Date1Week";
break;
case GH_2_WEEKS_AGO:
label = "Date2Week";
break;
case GH_3_WEEKS_AGO:
label = "Date3Week";
break;
default:
return NSERROR_BAD_PARAMETER;
}
label = messages_get(label);
for (i = f - 1; i >= 0; i--) {
if (gh_ctx.folders[i].folder != NULL) {
relation = gh_ctx.folders[i].folder;
rel = TREE_REL_NEXT_SIBLING;
break;
}
}
gh_ctx.folders[f].data.field = gh_ctx.fields[N_FIELDS - 1].field;
gh_ctx.folders[f].data.value = label;
gh_ctx.folders[f].data.value_len = strlen(label);
err = treeview_create_node_folder(gh_ctx.tree,
&gh_ctx.folders[f].folder,
relation, rel,
&gh_ctx.folders[f].data,
&gh_ctx.folders[f],
gh_ctx.built ? TREE_OPTION_NONE :
TREE_OPTION_SUPPRESS_RESIZE |
TREE_OPTION_SUPPRESS_REDRAW);
return err;
}
/**
* Get the treeview folder for history entires in a particular slot
*
* \param parent Updated to parent folder.
* \param slot Global history slot of entry we want folder node for
* \return NSERROR_OK on success, appropriate error otherwise
*/
static inline nserror global_history_get_parent_treeview_node(
treeview_node **parent, int slot)
{
int folder_index;
struct global_history_folder *f;
nserror err;
if (slot < 7) {
folder_index = slot;
} else if (slot < 14) {
folder_index = GH_LAST_WEEK;
} else if (slot < 21) {
folder_index = GH_2_WEEKS_AGO;
} else if (slot < N_DAYS) {
folder_index = GH_3_WEEKS_AGO;
} else {
/* Slot value is invalid */
return NSERROR_BAD_PARAMETER;
}
/* Get the folder */
f = &(gh_ctx.folders[folder_index]);
if (f->folder == NULL) {
err = global_history_create_dir(folder_index);
if (err != NSERROR_OK) {
*parent = NULL;
return err;
}
}
/* Return the parent treeview folder */
*parent = f->folder;
return NSERROR_OK;
}
/**
* Set a global history entry's data from the url_data.
*
* \param e Global history entry to set up
* \param data Data associated with entry's URL
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror global_history_create_treeview_field_data(
struct global_history_entry *e,
const struct url_data *data)
{
const char *title = (data->title != NULL) ?
data->title : messages_get("NoTitle");
char buffer[16];
const char *last_visited;
char *last_visited2;
int len;
e->data[GH_TITLE].field = gh_ctx.fields[GH_TITLE].field;
e->data[GH_TITLE].value = strdup(title);
e->data[GH_TITLE].value_len = (e->data[GH_TITLE].value != NULL) ?
strlen(title) : 0;
e->data[GH_URL].field = gh_ctx.fields[GH_URL].field;
e->data[GH_URL].value = nsurl_access(e->url);
e->data[GH_URL].value_len = nsurl_length(e->url);
last_visited = ctime(&data->last_visit);
last_visited2 = strdup(last_visited);
if (last_visited2 != NULL) {
assert(last_visited2[24] == '\n');
last_visited2[24] = '\0';
}
e->data[GH_LAST_VISIT].field = gh_ctx.fields[GH_LAST_VISIT].field;
e->data[GH_LAST_VISIT].value = last_visited2;
e->data[GH_LAST_VISIT].value_len = (last_visited2 != NULL) ? 24 : 0;
len = snprintf(buffer, 16, "%u", data->visits);
if (len == 16) {
len--;
buffer[len] = '\0';
}
e->data[GH_VISITS].field = gh_ctx.fields[GH_VISITS].field;
e->data[GH_VISITS].value = strdup(buffer);
e->data[GH_VISITS].value_len = len;
return NSERROR_OK;
}
/**
* Add a global history entry to the treeview
*
* \param e entry to add to treeview
* \param slot global history slot containing entry
* \return NSERROR_OK on success, or appropriate error otherwise
*
* It is assumed that the entry is unique (for its URL) in the global
* history table
*/
static nserror global_history_entry_insert(struct global_history_entry *e,
int slot)
{
nserror err;
treeview_node *parent;
err = global_history_get_parent_treeview_node(&parent, slot);
if (err != NSERROR_OK) {
return err;
}
err = treeview_create_node_entry(gh_ctx.tree, &(e->entry),
parent, TREE_REL_FIRST_CHILD, e->data, e,
gh_ctx.built ? TREE_OPTION_NONE :
TREE_OPTION_SUPPRESS_RESIZE |
TREE_OPTION_SUPPRESS_REDRAW);
if (err != NSERROR_OK) {
return err;
}
return NSERROR_OK;
}
/**
* Add an entry to the global history (creates the entry).
*
* If the treeview has already been created, the entry will be added to the
* treeview. Otherwise, the entry will have to be added to the treeview later.
*
* When we first create the global history we create it without the treeview, to
* simplfy sorting the entries.
*
* \param url URL for entry to add to history
* \param slot Global history slot to contain history entry
* \param data URL data for the entry
* \param got_treeview Whether the treeview has been created already
* \return NSERROR_OK on success, or appropriate error otherwise
*/
static nserror global_history_add_entry_internal(nsurl *url, int slot,
const struct url_data *data, bool got_treeview)
{
nserror err;
struct global_history_entry *e;
/* Create new local history entry */
e = malloc(sizeof(struct global_history_entry));
if (e == NULL) {
return NSERROR_NOMEM;
}
e->user_delete = false;
e->slot = slot;
e->url = nsurl_ref(url);
e->t = data->last_visit;
e->entry = NULL;
e->next = NULL;
e->prev = NULL;
err = global_history_create_treeview_field_data(e, data);
if (err != NSERROR_OK) {
return err;
}
if (gh_list[slot] == NULL) {
/* list empty */
gh_list[slot] = e;
} else if (gh_list[slot]->t < e->t) {
/* Insert at list head */
e->next = gh_list[slot];
gh_list[slot]->prev = e;
gh_list[slot] = e;
} else {
struct global_history_entry *prev = gh_list[slot];
struct global_history_entry *curr = prev->next;
while (curr != NULL) {
if (curr->t < e->t) {
break;
}
prev = curr;
curr = curr->next;
}
/* insert after prev */
e->next = curr;
e->prev = prev;
prev->next = e;
if (curr != NULL)
curr->prev = e;
}
if (got_treeview) {
err = global_history_entry_insert(e, slot);
if (err != NSERROR_OK) {
return err;
}
}
return NSERROR_OK;
}
/**
* Delete a global history entry
*
* This does not delete the treeview node, rather it should only be called from
* the treeview node delete event message.
*
* \param e Entry to delete
*/
static void global_history_delete_entry_internal(
struct global_history_entry *e)
{
assert(e != NULL);
assert(e->entry == NULL);
/* Unlink */
if (gh_list[e->slot] == e) {
/* e is first entry */
gh_list[e->slot] = e->next;
if (e->next != NULL)
e->next->prev = NULL;
} else if (e->next == NULL) {
/* e is last entry */
e->prev->next = NULL;
} else {
/* e has an entry before and after */
e->prev->next = e->next;
e->next->prev = e->prev;
}
if (e->user_delete) {
/* User requested delete, so delete from urldb too. */
urldb_reset_url_visit_data(e->url);
}
/* Destroy fields */
free((void *)e->data[GH_TITLE].value); /* Eww */
free((void *)e->data[GH_LAST_VISIT].value); /* Eww */
free((void *)e->data[GH_VISITS].value); /* Eww */
nsurl_unref(e->url);
/* Destroy entry */
free(e);
}
/**
* Internal routine to actually perform global history addition
*
* \param url The URL to add
* \param data URL data associated with URL
* \return true (for urldb_iterate_entries)
*/
static bool global_history_add_entry(nsurl *url,
const struct url_data *data)
{
int slot;
time_t visit_date;
time_t earliest_date = gh_ctx.today - (N_DAYS - 1) * N_SEC_PER_DAY;
bool got_treeview = gh_ctx.tree != NULL;
assert((url != NULL) && (data != NULL));
visit_date = data->last_visit;
/* Find day array slot for entry */
if (visit_date >= gh_ctx.today) {
slot = 0;
} else if (visit_date >= earliest_date) {
slot = (gh_ctx.today - visit_date) / N_SEC_PER_DAY + 1;
} else {
/* too old */
return true;
}
if (got_treeview == true) {
/* The treeview for global history already exists */
struct global_history_entry *e;
/* Delete any existing entry for this URL */
e = global_history_find(url);
if (e != NULL) {
treeview_delete_node(gh_ctx.tree, e->entry,
TREE_OPTION_SUPPRESS_REDRAW |
TREE_OPTION_SUPPRESS_RESIZE);
}
}
if (global_history_add_entry_internal(url, slot, data,
got_treeview) != NSERROR_OK) {
return false;
}
return true;
}
/**
* Initialise the treeview entry feilds
*
* \return NSERROR_OK on success, or appropriate error otherwise
*/
static nserror global_history_initialise_entry_fields(void)
{
int i;
const char *label;
for (i = 0; i < N_FIELDS; i++)
gh_ctx.fields[i].field = NULL;
gh_ctx.fields[GH_TITLE].flags = TREE_FLAG_DEFAULT;
label = "TreeviewLabelTitle";
label = messages_get(label);
if (lwc_intern_string(label, strlen(label),
&gh_ctx.fields[GH_TITLE].field) !=
lwc_error_ok) {
goto error;
}
gh_ctx.fields[GH_URL].flags = TREE_FLAG_COPY_TEXT;
label = "TreeviewLabelURL";
label = messages_get(label);
if (lwc_intern_string(label, strlen(label),
&gh_ctx.fields[GH_URL].field) !=
lwc_error_ok) {
goto error;
}
gh_ctx.fields[GH_LAST_VISIT].flags = TREE_FLAG_SHOW_NAME;
label = "TreeviewLabelLastVisit";
label = messages_get(label);
if (lwc_intern_string(label, strlen(label),
&gh_ctx.fields[GH_LAST_VISIT].field) !=
lwc_error_ok) {
goto error;
}
gh_ctx.fields[GH_VISITS].flags = TREE_FLAG_SHOW_NAME;
label = "TreeviewLabelVisits";
label = messages_get(label);
if (lwc_intern_string(label, strlen(label),
&gh_ctx.fields[GH_VISITS].field) !=
lwc_error_ok) {
goto error;
}
gh_ctx.fields[GH_PERIOD].flags = TREE_FLAG_DEFAULT;
label = "TreeviewLabelPeriod";
label = messages_get(label);
if (lwc_intern_string(label, strlen(label),
&gh_ctx.fields[GH_PERIOD].field) !=
lwc_error_ok) {
return false;
}
return NSERROR_OK;
error:
for (i = 0; i < N_FIELDS; i++)
if (gh_ctx.fields[i].field != NULL)
lwc_string_unref(gh_ctx.fields[i].field);
return NSERROR_UNKNOWN;
}
/**
* Initialise the time
*
* \return NSERROR_OK on success, or appropriate error otherwise
*/
static nserror global_history_initialise_time(void)
{
struct tm *full_time;
time_t t;
/* get the current time */
t = time(NULL);
if (t == -1) {
LOG("time info unaviable");
return NSERROR_UNKNOWN;
}
/* get the time at the start of today */
full_time = localtime(&t);
full_time->tm_sec = 0;
full_time->tm_min = 0;
full_time->tm_hour = 0;
t = mktime(full_time);
if (t == -1) {
LOG("mktime failed");
return NSERROR_UNKNOWN;
}
gh_ctx.today = t;
gh_ctx.weekday = full_time->tm_wday;
return NSERROR_OK;
}
/**
* Initialise the treeview entries
*
* \return NSERROR_OK on success, or appropriate error otherwise
*/
static nserror global_history_init_entries(void)
{
int i;
nserror err;
/* Itterate over all global history data, inserting it into treeview */
for (i = 0; i < N_DAYS; i++) {
struct global_history_entry *l = NULL;
struct global_history_entry *e = gh_list[i];
/* Insert in reverse order; find last */
while (e != NULL) {
l = e;
e = e->next;
}
/* Insert the entries into the treeview */
while (l != NULL) {
err = global_history_entry_insert(l, i);
if (err != NSERROR_OK) {
return err;
}
l = l->prev;
}
}
return NSERROR_OK;
}
static nserror global_history_tree_node_folder_cb(
struct treeview_node_msg msg, void *data)
{
struct global_history_folder *f = data;
switch (msg.msg) {
case TREE_MSG_NODE_DELETE:
f->folder = NULL;
break;
case TREE_MSG_NODE_EDIT:
break;
case TREE_MSG_NODE_LAUNCH:
break;
}
return NSERROR_OK;
}
static nserror
global_history_tree_node_entry_cb(struct treeview_node_msg msg, void *data)
{
struct global_history_entry *e = data;
nserror ret = NSERROR_OK;
switch (msg.msg) {
case TREE_MSG_NODE_DELETE:
e->entry = NULL;
e->user_delete = msg.data.delete.user;
global_history_delete_entry_internal(e);
break;
case TREE_MSG_NODE_EDIT:
break;
case TREE_MSG_NODE_LAUNCH:
{
struct browser_window *existing = NULL;
enum browser_window_create_flags flags = BW_CREATE_HISTORY;
/* TODO: Set existing to window that new tab appears in */
if (msg.data.node_launch.mouse &
(BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2) ||
existing == NULL) {
/* Shift or Ctrl launch, open in new window rather
* than tab. */
/* TODO: flags ^= BW_CREATE_TAB; */
}
ret = browser_window_create(flags, e->url, NULL,
existing, NULL);
}
break;
}
return ret;
}
struct treeview_callback_table gh_tree_cb_t = {
.folder = global_history_tree_node_folder_cb,
.entry = global_history_tree_node_entry_cb
};
/* Exported interface, documented in global_history.h */
nserror global_history_init(struct core_window_callback_table *cw_t,
void *core_window_handle)
{
nserror err;
err = treeview_init();
if (err != NSERROR_OK) {
return err;
}
LOG("Loading global history");
/* Init. global history treeview time */
err = global_history_initialise_time();
if (err != NSERROR_OK) {
gh_ctx.tree = NULL;
return err;
}
/* Init. global history treeview entry fields */
err = global_history_initialise_entry_fields();
if (err != NSERROR_OK) {
gh_ctx.tree = NULL;
return err;
}
/* Load the entries */
urldb_iterate_entries(global_history_add_entry);
/* Create the global history treeview */
err = treeview_create(&gh_ctx.tree, &gh_tree_cb_t,
N_FIELDS, gh_ctx.fields,
cw_t, core_window_handle,
TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS);
if (err != NSERROR_OK) {
gh_ctx.tree = NULL;
return err;
}
/* Ensure there is a folder for today */
err = global_history_create_dir(GH_TODAY);
if (err != NSERROR_OK) {
return err;
}
/* Add the history to the treeview */
err = global_history_init_entries();
if (err != NSERROR_OK) {
return err;
}
/* Expand the "Today" folder node */
err = treeview_node_expand(gh_ctx.tree,
gh_ctx.folders[GH_TODAY].folder);
if (err != NSERROR_OK) {
return err;
}
/* History tree is built
* We suppress the treeview height callback on entry insertion before
* the treeview is built. */
gh_ctx.built = true;
/* Inform client of window height */
treeview_get_height(gh_ctx.tree);
LOG("Loaded global history");
return NSERROR_OK;
}
/* Exported interface, documented in global_history.h */
nserror global_history_fini(void)
{
int i;
nserror err;
LOG("Finalising global history");
gh_ctx.built = false;
/* Destroy the global history treeview */
err = treeview_destroy(gh_ctx.tree);
/* Free global history treeview entry fields */
for (i = 0; i < N_FIELDS; i++)
if (gh_ctx.fields[i].field != NULL)
lwc_string_unref(gh_ctx.fields[i].field);
err = treeview_fini();
if (err != NSERROR_OK) {
return err;
}
LOG("Finalised global history");
return err;
}
/* Exported interface, documented in global_history.h */
nserror global_history_add(nsurl *url)
{
const struct url_data *data;
/* If we don't have a global history at the moment, just return OK */
if (gh_ctx.tree == NULL)
return NSERROR_OK;
data = urldb_get_url_data(url);
if (data == NULL) {
LOG("Can't add URL to history that's not present in urldb.");
return NSERROR_BAD_PARAMETER;
}
global_history_add_entry(url, data);
return NSERROR_OK;
}
struct treeview_export_walk_ctx {
FILE *fp;
};
/** Callback for treeview_walk node entering */
static nserror global_history_export_enter_cb(void *ctx, void *node_data,
enum treeview_node_type type, bool *abort)
{
struct treeview_export_walk_ctx *tw = ctx;
nserror ret;
if (type == TREE_NODE_ENTRY) {
struct global_history_entry *e = node_data;
char *t_text;
char *u_text;
ret = utf8_to_html(e->data[GH_TITLE].value, "iso-8859-1",
e->data[GH_TITLE].value_len, &t_text);
if (ret != NSERROR_OK)
return NSERROR_SAVE_FAILED;
ret = utf8_to_html(e->data[GH_URL].value, "iso-8859-1",
e->data[GH_URL].value_len, &u_text);
if (ret != NSERROR_OK) {
free(t_text);
return NSERROR_SAVE_FAILED;
}
fprintf(tw->fp, "<li><a href=\"%s\">%s</a></li>\n",
u_text, t_text);
free(t_text);
free(u_text);
} else if (type == TREE_NODE_FOLDER) {
struct global_history_folder *f = node_data;
char *f_text;
ret = utf8_to_html(f->data.value, "iso-8859-1",
f->data.value_len, &f_text);
if (ret != NSERROR_OK)
return NSERROR_SAVE_FAILED;
fprintf(tw->fp, "<li><h4>%s</h4>\n<ul>\n", f_text);
free(f_text);
}
return NSERROR_OK;
}
/** Callback for treeview_walk node leaving */
static nserror global_history_export_leave_cb(void *ctx, void *node_data,
enum treeview_node_type type, bool *abort)
{
struct treeview_export_walk_ctx *tw = ctx;
if (type == TREE_NODE_FOLDER) {
fputs("</ul></li>\n", tw->fp);
}
return NSERROR_OK;
}
/* Exported interface, documented in global_history.h */
nserror global_history_export(const char *path, const char *title)
{
struct treeview_export_walk_ctx tw;
nserror err;
FILE *fp;
fp = fopen(path, "w");
if (fp == NULL)
return NSERROR_SAVE_FAILED;
if (title == NULL)
title = "NetSurf Browsing History";
fputs("<!DOCTYPE html "
"PUBLIC \"//W3C/DTD HTML 4.01//EN\" "
"\"http://www.w3.org/TR/html4/strict.dtd\">\n", fp);
fputs("<html>\n<head>\n", fp);
fputs("<meta http-equiv=\"Content-Type\" "
"content=\"text/html; charset=iso-8859-1\">\n", fp);
fprintf(fp, "<title>%s</title>\n", title);
fputs("</head>\n<body>\n<ul>\n", fp);
tw.fp = fp;
err = treeview_walk(gh_ctx.tree, NULL,
global_history_export_enter_cb,
global_history_export_leave_cb,
&tw, TREE_NODE_ENTRY | TREE_NODE_FOLDER);
if (err != NSERROR_OK)
return err;
fputs("</ul>\n</body>\n</html>\n", fp);
fclose(fp);
return NSERROR_OK;
}
/* Exported interface, documented in global_history.h */
void global_history_redraw(int x, int y, struct rect *clip,
const struct redraw_context *ctx)
{
treeview_redraw(gh_ctx.tree, x, y, clip, ctx);
}
/* Exported interface, documented in global_history.h */
void global_history_mouse_action(browser_mouse_state mouse, int x, int y)
{
treeview_mouse_action(gh_ctx.tree, mouse, x, y);
}
/* Exported interface, documented in global_history.h */
bool global_history_keypress(uint32_t key)
{
return treeview_keypress(gh_ctx.tree, key);
}
/* Exported interface, documented in global_history.h */
bool global_history_has_selection(void)
{
return treeview_has_selection(gh_ctx.tree);
}
/* Exported interface, documented in global_history.h */
bool global_history_get_selection(nsurl **url, const char **title)
{
struct global_history_entry *e;
enum treeview_node_type type;
void *v;
type = treeview_get_selection(gh_ctx.tree, &v);
if (type != TREE_NODE_ENTRY || v == NULL) {
*url = NULL;
*title = NULL;
return false;
}
e = (struct global_history_entry *)v;
*url = e->url;
*title = e->data[GH_TITLE].value;
return true;
}
/* Exported interface, documented in global_history.h */
nserror global_history_expand(bool only_folders)
{
return treeview_expand(gh_ctx.tree, only_folders);
}
/* Exported interface, documented in global_history.h */
nserror global_history_contract(bool all)
{
return treeview_contract(gh_ctx.tree, all);
}