1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-09-13 00:38:32 -04:00
elinks/src/bookmarks/dialogs.c
2007-02-04 15:17:49 +02:00

713 lines
18 KiB
C

/* Bookmarks dialogs */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "elinks.h"
#include "bfu/dialog.h"
#include "bfu/hierbox.h"
#include "bfu/listbox.h"
#include "bookmarks/bookmarks.h"
#include "bookmarks/dialogs.h"
#include "dialogs/edit.h"
#include "intl/gettext/libintl.h"
#include "main/object.h"
#include "protocol/uri.h"
#include "session/session.h"
#include "terminal/terminal.h"
#include "util/conv.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/time.h"
/* Whether to save bookmarks after each modification of their list
* (add/modify/delete). */
#define BOOKMARKS_RESAVE 1
static void
lock_bookmark(struct listbox_item *item)
{
object_lock((struct bookmark *) item->udata);
}
static void
unlock_bookmark(struct listbox_item *item)
{
object_unlock((struct bookmark *) item->udata);
}
static int
is_bookmark_used(struct listbox_item *item)
{
return is_object_used((struct bookmark *) item->udata);
}
static unsigned char *
get_bookmark_text(struct listbox_item *item, struct terminal *term)
{
struct bookmark *bookmark = item->udata;
return stracpy(bookmark->title);
}
static unsigned char *
get_bookmark_info(struct listbox_item *item, struct terminal *term)
{
struct bookmark *bookmark = item->udata;
struct string info;
if (item->type == BI_FOLDER) return NULL;
if (!init_string(&info)) return NULL;
add_format_to_string(&info, "%s: %s", _("Title", term), bookmark->title);
add_format_to_string(&info, "\n%s: %s", _("URL", term), bookmark->url);
return info.source;
}
static struct uri *
get_bookmark_uri(struct listbox_item *item)
{
struct bookmark *bookmark = item->udata;
return bookmark->url && *bookmark->url
? get_translated_uri(bookmark->url, NULL) : NULL;
}
static struct listbox_item *
get_bookmark_root(struct listbox_item *item)
{
struct bookmark *bookmark = item->udata;
return bookmark->root ? bookmark->root->box_item : NULL;
}
static int
can_delete_bookmark(struct listbox_item *item)
{
return 1;
}
static void
delete_bookmark_item(struct listbox_item *item, int last)
{
struct bookmark *bookmark = item->udata;
assert(!is_object_used(bookmark));
delete_bookmark(bookmark);
#ifdef BOOKMARKS_RESAVE
if (last) write_bookmarks();
#endif
}
static struct listbox_ops_messages bookmarks_messages = {
/* cant_delete_item */
N_("Sorry, but the bookmark \"%s\" cannot be deleted."),
/* cant_delete_used_item */
N_("Sorry, but the bookmark \"%s\" is being used by something else."),
/* cant_delete_folder */
N_("Sorry, but the folder \"%s\" cannot be deleted."),
/* cant_delete_used_folder */
N_("Sorry, but the folder \"%s\" is being used by something else."),
/* delete_marked_items_title */
N_("Delete marked bookmarks"),
/* delete_marked_items */
N_("Delete marked bookmarks?"),
/* delete_folder_title */
N_("Delete folder"),
/* delete_folder */
N_("Delete the folder \"%s\" and all bookmarks in it?"),
/* delete_item_title */
N_("Delete bookmark"),
/* delete_item; xgettext:c-format */
N_("Delete this bookmark?"),
/* clear_all_items_title */
N_("Clear all bookmarks"),
/* clear_all_items_title */
N_("Do you really want to remove all bookmarks?"),
};
static const struct listbox_ops bookmarks_listbox_ops = {
lock_bookmark,
unlock_bookmark,
is_bookmark_used,
get_bookmark_text,
get_bookmark_info,
get_bookmark_uri,
get_bookmark_root,
NULL,
can_delete_bookmark,
delete_bookmark_item,
NULL,
&bookmarks_messages,
};
/****************************************************************************
Bookmark manager stuff.
****************************************************************************/
/* Callback for the "add" button in the bookmark manager */
static widget_handler_status_T
push_add_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
launch_bm_add_doc_dialog(dlg_data->win->term, dlg_data,
(struct session *) dlg_data->dlg->udata);
return EVENT_PROCESSED;
}
static
void launch_bm_search_doc_dialog(struct terminal *, struct dialog_data *,
struct session *);
/* Callback for the "search" button in the bookmark manager */
static widget_handler_status_T
push_search_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
launch_bm_search_doc_dialog(dlg_data->win->term, dlg_data,
(struct session *) dlg_data->dlg->udata);
return EVENT_PROCESSED;
}
static void
move_bookmark_after_selected(struct bookmark *bookmark, struct bookmark *selected)
{
if (selected == bookmark->root
|| !selected
|| !selected->box_item
|| !bookmark->box_item)
return;
del_from_list(bookmark->box_item);
del_from_list(bookmark);
add_at_pos(selected, bookmark);
add_at_pos(selected->box_item, bookmark->box_item);
}
static void
do_add_bookmark(struct dialog_data *dlg_data, unsigned char *title, unsigned char *url)
{
struct bookmark *bm = NULL;
struct bookmark *selected = NULL;
struct listbox_data *box = NULL;
if (dlg_data) {
box = get_dlg_listbox_data(dlg_data);
if (box->sel) {
selected = box->sel->udata;
if (box->sel->type == BI_FOLDER && box->sel->expanded) {
bm = selected;
} else {
bm = selected->root;
}
}
}
bm = add_bookmark(bm, 1, title, url);
if (!bm) return;
move_bookmark_after_selected(bm, selected);
#ifdef BOOKMARKS_RESAVE
write_bookmarks();
#endif
if (dlg_data) {
struct widget_data *widget_data = dlg_data->widgets_data;
/* We touch only the actual bookmark dialog, not all of them;
* that's right, right? ;-) --pasky */
listbox_sel(widget_data, bm->box_item);
}
}
/**** ADD FOLDER *****************************************************/
static void
do_add_folder(struct dialog_data *dlg_data, unsigned char *foldername)
{
do_add_bookmark(dlg_data, foldername, NULL);
}
static widget_handler_status_T
push_add_folder_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
input_dialog(dlg_data->win->term, NULL,
N_("Add folder"), N_("Folder name"),
dlg_data, NULL,
MAX_STR_LEN, NULL, 0, 0, NULL,
(void (*)(void *, unsigned char *)) do_add_folder,
NULL);
return EVENT_PROCESSED;
}
/**** ADD SEPARATOR **************************************************/
static widget_handler_status_T
push_add_separator_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
do_add_bookmark(dlg_data, "-", "");
redraw_dialog(dlg_data, 1);
return EVENT_PROCESSED;
}
/**** EDIT ***********************************************************/
/* Called when an edit is complete. */
static void
bookmark_edit_done(void *data) {
struct dialog *dlg = data;
struct bookmark *bm = (struct bookmark *) dlg->udata2;
update_bookmark(bm, dlg->widgets[0].data, dlg->widgets[1].data);
object_unlock(bm);
#ifdef BOOKMARKS_RESAVE
write_bookmarks();
#endif
}
static void
bookmark_edit_cancel(struct dialog *dlg) {
struct bookmark *bm = (struct bookmark *) dlg->udata2;
object_unlock(bm);
}
/* Called when the edit button is pushed */
static widget_handler_status_T
push_edit_button(struct dialog_data *dlg_data, struct widget_data *edit_btn)
{
struct listbox_data *box = get_dlg_listbox_data(dlg_data);
/* Follow the bookmark */
if (box->sel) {
struct bookmark *bm = (struct bookmark *) box->sel->udata;
const unsigned char *title = bm->title;
const unsigned char *url = bm->url;
object_lock(bm);
do_edit_dialog(dlg_data->win->term, 1, N_("Edit bookmark"),
title, url,
(struct session *) dlg_data->dlg->udata, dlg_data,
bookmark_edit_done, bookmark_edit_cancel,
(void *) bm, EDIT_DLG_ADD);
}
return EVENT_PROCESSED;
}
/**** MOVE ***********************************************************/
static struct bookmark *move_cache_root_avoid;
static void
update_depths(struct listbox_item *parent)
{
struct listbox_item *item;
foreach (item, parent->child) {
item->depth = parent->depth + 1;
if (item->type == BI_FOLDER)
update_depths(item);
}
}
enum move_bookmark_flags {
MOVE_BOOKMARK_NONE = 0x00,
MOVE_BOOKMARK_MOVED = 0x01,
MOVE_BOOKMARK_CYCLE = 0x02
};
/* Traverse all bookmarks and move all marked items
* _into_ destb or, if destb is NULL, _after_ dest. */
static enum move_bookmark_flags
do_move_bookmark(struct bookmark *dest, int insert_as_child,
struct list_head *src, struct listbox_data *box)
{
static int move_bookmark_event_id = EVENT_NONE;
struct bookmark *bm, *next;
enum move_bookmark_flags result = MOVE_BOOKMARK_NONE;
set_event_id(move_bookmark_event_id, "bookmark-move");
foreachsafe (bm, next, *src) {
if (!bm->box_item->marked) {
/* Don't move this bookmark itself; but if
* it's a folder, then we'll look inside. */
} else if (bm == dest || bm == move_cache_root_avoid) {
/* Prevent moving a folder into itself. */
result |= MOVE_BOOKMARK_CYCLE;
} else {
struct hierbox_dialog_list_item *item;
result |= MOVE_BOOKMARK_MOVED;
bm->box_item->marked = 0;
trigger_event(move_bookmark_event_id, bm, dest);
foreach (item, bookmark_browser.dialogs) {
struct widget_data *widget_data;
struct listbox_data *box2;
widget_data = item->dlg_data->widgets_data;
box2 = get_listbox_widget_data(widget_data);
if (box2->top == bm->box_item)
listbox_sel_move(widget_data, 1);
}
del_from_list(bm->box_item);
del_from_list(bm);
if (insert_as_child) {
add_to_list(dest->child, bm);
add_to_list(dest->box_item->child, bm->box_item);
bm->root = dest;
insert_as_child = 0;
} else {
add_at_pos(dest, bm);
add_at_pos(dest->box_item, bm->box_item);
bm->root = dest->root;
}
bm->box_item->depth = bm->root
? bm->root->box_item->depth + 1
: 0;
if (bm->box_item->type == BI_FOLDER)
update_depths(bm->box_item);
dest = bm;
/* We don't want to care about anything marked inside
* of the marked folder, let's move it as a whole
* directly. I believe that this is more intuitive.
* --pasky */
continue;
}
if (bm->box_item->type == BI_FOLDER) {
result |= do_move_bookmark(dest, insert_as_child,
&bm->child, box);
}
}
return result;
}
static widget_handler_status_T
push_move_button(struct dialog_data *dlg_data,
struct widget_data *blah)
{
struct listbox_data *box = get_dlg_listbox_data(dlg_data);
struct bookmark *dest = NULL;
int insert_as_child = 0;
enum move_bookmark_flags result;
if (!box->sel) return EVENT_PROCESSED; /* nowhere to move to */
dest = box->sel->udata;
if (box->sel->type == BI_FOLDER && box->sel->expanded) {
insert_as_child = 1;
}
/* Avoid recursion headaches (prevents moving a folder into itself). */
move_cache_root_avoid = NULL;
{
struct bookmark *bm = dest->root;
while (bm) {
if (bm->box_item->marked)
move_cache_root_avoid = bm;
bm = bm->root;
}
}
result = do_move_bookmark(dest, insert_as_child, &bookmarks, box);
if (result & MOVE_BOOKMARK_MOVED) {
bookmarks_set_dirty();
#ifdef BOOKMARKS_RESAVE
write_bookmarks();
#endif
update_hierbox_browser(&bookmark_browser);
}
#ifndef CONFIG_SMALL
else if (result & MOVE_BOOKMARK_CYCLE) {
/* If the user also selected other bookmarks, then
* they have already been moved, and this box doesn't
* appear. */
info_box(dlg_data->win->term, 0,
N_("Cannot move folder inside itself"), ALIGN_LEFT,
N_("You are trying to move the marked folder inside "
"itself. To move the folder to a different "
"location select the new location before pressing "
"the Move button."));
} else {
info_box(dlg_data->win->term, 0,
N_("Nothing to move"), ALIGN_LEFT,
N_("To move bookmarks, first mark all the bookmarks "
"(or folders) you want to move. This can be done "
"with the Insert key if you're using the default "
"key-bindings. An asterisk will appear near all "
"marked bookmarks. Now move to where you want to "
"have the stuff moved to, and press the \"Move\" "
"button."));
}
#endif /* ndef CONFIG_SMALL */
return EVENT_PROCESSED;
}
/**** MANAGEMENT *****************************************************/
static const struct hierbox_browser_button bookmark_buttons[] = {
/* [gettext_accelerator_context(.bookmark_buttons)] */
{ N_("~Goto"), push_hierbox_goto_button, 1 },
{ N_("~Edit"), push_edit_button, 0 },
{ N_("~Delete"), push_hierbox_delete_button, 0 },
{ N_("~Add"), push_add_button, 0 },
{ N_("Add se~parator"), push_add_separator_button, 0 },
{ N_("Add ~folder"), push_add_folder_button, 0 },
{ N_("~Move"), push_move_button, 0 },
{ N_("~Search"), push_search_button, 1 },
#if 0
/* This one is too dangerous, so just let user delete
* the bookmarks file if needed. --Zas */
{ N_("Clear"), push_hierbox_clear_button, 0 },
/* TODO: Would this be useful? --jonas */
{ N_("Save"), push_save_button, 0 },
#endif
};
struct_hierbox_browser(
bookmark_browser,
N_("Bookmark manager"),
bookmark_buttons,
&bookmarks_listbox_ops
);
/* Builds the "Bookmark manager" dialog */
void
bookmark_manager(struct session *ses)
{
free_last_searched_bookmark();
bookmark_browser.expansion_callback = bookmarks_set_dirty;
hierbox_browser(&bookmark_browser, ses);
}
/****************************************************************************\
Bookmark search dialog.
\****************************************************************************/
/* Searchs a substring either in title or url fields (ignoring
* case). If search_title and search_url are not empty, it selects bookmarks
* matching the first OR the second.
*
* Perhaps another behavior could be to search bookmarks matching both
* (replacing OR by AND), but it would break a cool feature: when on a page,
* opening search dialog will have fields corresponding to that page, so
* pressing ok will find any bookmark with that title or url, permitting a
* rapid search of an already existing bookmark. --Zas */
struct bookmark_search_ctx {
unsigned char *url;
unsigned char *title;
int found;
int offset;
};
#define NULL_BOOKMARK_SEARCH_CTX {NULL, NULL, 0, 0}
static int
test_search(struct listbox_item *item, void *data_, int *offset)
{
struct bookmark_search_ctx *ctx = data_;
if (!ctx->offset) {
ctx->found = 0; /* ignore possible match on first item */
} else {
struct bookmark *bm = item->udata;
assert(ctx->title && ctx->url);
ctx->found = (*ctx->title && strcasestr(bm->title, ctx->title))
|| (*ctx->url && strcasestr(bm->url, ctx->url));
if (ctx->found) *offset = 0;
}
ctx->offset++;
return 0;
}
/* Last searched values */
static unsigned char *bm_last_searched_title = NULL;
static unsigned char *bm_last_searched_url = NULL;
void
free_last_searched_bookmark(void)
{
mem_free_set(&bm_last_searched_title, NULL);
mem_free_set(&bm_last_searched_url, NULL);
}
static int
memorize_last_searched_bookmark(struct bookmark_search_ctx *ctx)
{
/* Memorize last searched title */
mem_free_set(&bm_last_searched_title, stracpy(ctx->title));
if (!bm_last_searched_title) return 0;
/* Memorize last searched url */
mem_free_set(&bm_last_searched_url, stracpy(ctx->url));
if (!bm_last_searched_url) {
mem_free_set(&bm_last_searched_title, NULL);
return 0;
}
return 1;
}
/* Search bookmarks */
static void
bookmark_search_do(void *data)
{
struct dialog *dlg = data;
struct bookmark_search_ctx ctx = NULL_BOOKMARK_SEARCH_CTX;
struct listbox_data *box;
struct dialog_data *dlg_data;
assertm(dlg->udata, "Bookmark search with NULL udata in dialog");
if_assert_failed return;
ctx.title = dlg->widgets[0].data;
ctx.url = dlg->widgets[1].data;
if (!ctx.title || !ctx.url)
return;
if (!memorize_last_searched_bookmark(&ctx))
return;
dlg_data = (struct dialog_data *) dlg->udata;
box = get_dlg_listbox_data(dlg_data);
traverse_listbox_items_list(box->sel, box, 0, 0, test_search, &ctx);
if (!ctx.found) return;
listbox_sel_move(dlg_data->widgets_data, ctx.offset - 1);
}
static void
launch_bm_search_doc_dialog(struct terminal *term,
struct dialog_data *parent,
struct session *ses)
{
do_edit_dialog(term, 1, N_("Search bookmarks"),
bm_last_searched_title, bm_last_searched_url,
ses, parent, bookmark_search_do, NULL, NULL,
EDIT_DLG_SEARCH);
}
/****************************************************************************\
Bookmark add dialog.
\****************************************************************************/
/* Adds the bookmark */
static void
bookmark_add_add(void *data)
{
struct dialog *dlg = data;
struct dialog_data *dlg_data = (struct dialog_data *) dlg->udata;
do_add_bookmark(dlg_data, dlg->widgets[0].data, dlg->widgets[1].data);
}
void
launch_bm_add_dialog(struct terminal *term,
struct dialog_data *parent,
struct session *ses,
unsigned char *title,
unsigned char *url)
{
do_edit_dialog(term, 1, N_("Add bookmark"), title, url, ses,
parent, bookmark_add_add, NULL, NULL, EDIT_DLG_ADD);
}
void
launch_bm_add_doc_dialog(struct terminal *term,
struct dialog_data *parent,
struct session *ses)
{
launch_bm_add_dialog(term, parent, ses, NULL, NULL);
}
void
launch_bm_add_link_dialog(struct terminal *term,
struct dialog_data *parent,
struct session *ses)
{
unsigned char title[MAX_STR_LEN], url[MAX_STR_LEN];
launch_bm_add_dialog(term, parent, ses,
get_current_link_name(ses, title, MAX_STR_LEN),
get_current_link_url(ses, url, MAX_STR_LEN));
}
/****************************************************************************\
Bookmark tabs dialog.
\****************************************************************************/
void
bookmark_terminal_tabs_dialog(struct terminal *term)
{
struct string string;
if (!init_string(&string)) return;
add_to_string(&string, _("Saved session", term));
#ifdef HAVE_STRFTIME
add_to_string(&string, " - ");
add_date_to_string(&string, get_opt_str("ui.date_format"), NULL);
#endif
input_dialog(term, NULL,
N_("Bookmark tabs"), N_("Enter folder name"),
term, NULL,
MAX_STR_LEN, string.source, 0, 0, NULL,
(void (*)(void *, unsigned char *)) bookmark_terminal_tabs,
NULL);
done_string(&string);
}