1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00
elinks/src/bookmarks/dialogs.c

713 lines
18 KiB
C
Raw Normal View History

/* 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_ dest or, if insert_as_child is 0, _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);
}
2006-07-27 03:51:10 -04:00
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[] = {
Here is a framework that detects cases where a PO file assigns the same accelerator key to multiple buttons in a dialog box or to multiple items in a menu. ELinks already has some support for this but it requires the translator to run ELinks and manually scan through all menus and dialogs. The attached changes make it possible to quickly detect and list any conflicts, including ones that can only occur on operating systems or configurations that the translator is not currently using. The changes have no immediate effect on the elinks executable or the MO files. PO files become larger, however. The scheme works like this: - Like before, accelerator keys in translatable strings are tagged with the tilde (~) character. - Whenever a C source file defines an accelerator key, it must assign one or more named "contexts" to it. The translations in the PO files inherit these contexts. If multiple strings use the same accelerator (case insensitive) in the same context, that's a conflict and can be detected automatically. - The contexts are defined with "gettext_accelerator_context" comments in source files. These comments delimit regions where all translatable strings containing tildes are given the same contexts. There must be one special comment at the top of the region; it lists the contexts assigned to that region. The region automatically ends at the end of the function (found with regexp /^\}/), but it can also be closed explicitly with another special comment. The comments are formatted like this: /* [gettext_accelerator_context(foo, bar, baz)] begins a region that uses the contexts "foo", "bar", and "baz". The comma is the delimiter; whitespace is optional. [gettext_accelerator_context()] ends the region. */ The scripts don't currently check whether this syntax occurs inside or outside comments. - The names of contexts consist of C identifiers delimited with periods. I typically used the name of a function that sets up a dialog, or the name of an array where the items of a menu are listed. There is a special feature for static functions: if the name begins with a period, then the period will be replaced with the name of the source file and a colon. - If a menu is programmatically generated from multiple parts, of which some are never used together, so that it is safe to use the same accelerators in them, then it is necessary to define multiple contexts for the same menu. link_menu() in src/viewer/text/link.c is the most complex example of this. - During make update-po: - A Perl script (po/gather-accelerator-contexts.pl) reads po/elinks.pot, scans the source files listed in it for "gettext_accelerator_context" comments, and rewrites po/elinks.pot with "accelerator_context" comments that indicate the contexts of each msgid: the union of all contexts of all of its uses in the source files. It also removes any "gettext_accelerator_context" comments that xgettext --add-comments has copied to elinks.pot. - If po/gather-accelerator-contexts.pl does not find any contexts for some use of an msgid that seems to contain an accelerator (because it contains a tilde), it warns. If the tilde refers to e.g. "~/.elinks" and does not actually mark an accelerator, the warning can be silenced by specifying the special context "IGNORE", which the script otherwise ignores. - msgmerge copies the "accelerator_context" comments from po/elinks.pot to po/*.po. Translators do not edit those comments. - During make check-po: - Another Perl script (po/check-accelerator-contexts.pl) reads po/*.po and keeps track of which accelerators have been bound in each context. It warns about any conflicts it finds. This script does not access the C source files; thus it does not matter if the line numbers in "#:" lines are out of date. This implementation is not perfect and I am not proposing to add it to the main source tree at this time. Specifically: - It introduces compile-time dependencies on Perl and Locale::PO. There should be a configure-time or compile-time check so that the new features are skipped if the prerequisites are missing. - When the scripts include msgstr strings in warnings, they should transcode them from the charset of the PO file to the one specified by the user's locale. - It is not adequately documented (well, except perhaps here). - po/check-accelerator-contexts.pl reports the same conflict multiple times if it occurs in multiple contexts. - The warning messages should include line numbers, so that users of Emacs could conveniently edit the conflicting part of the PO file. This is not feasible with the current version of Locale::PO. - Locale::PO does not understand #~ lines and spews warnings about them. There is an ugly hack to hide these warnings. - Jonas Fonseca suggested the script could propose accelerators that are still available. This has not been implemented. There are three files attached: - po/gather-accelerator-contexts.pl: Augments elinks.pot with context information. - po/check-accelerator-contexts.pl: Checks conflicts. - accelerator-contexts.diff: Makes po/Makefile run the scripts, and adds special comments to source files.
2005-12-04 18:38:29 -05:00
/* [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 != NULL, "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);
}