2005-09-15 09:58:31 -04:00
|
|
|
/* 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"
|
2006-06-14 03:11:51 -04:00
|
|
|
#include "bfu/hierbox.h"
|
|
|
|
#include "bfu/listbox.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#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
|
2005-11-24 09:38:47 -05:00
|
|
|
? get_translated_uri(bookmark->url, NULL) : NULL;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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"),
|
2006-05-27 01:51:24 -04:00
|
|
|
/* delete_item; xgettext:c-format */
|
2005-09-15 09:58:31 -04:00
|
|
|
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?"),
|
|
|
|
};
|
|
|
|
|
2007-02-04 08:17:49 -05:00
|
|
|
static const struct listbox_ops bookmarks_listbox_ops = {
|
2005-09-15 09:58:31 -04:00
|
|
|
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) {
|
2006-06-06 00:28:32 -04:00
|
|
|
selected = box->sel->udata;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
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 */
|
2006-05-27 20:12:08 -04:00
|
|
|
listbox_sel(widget_data, bm->box_item);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**** 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-05-21 09:46:23 -04:00
|
|
|
enum move_bookmark_flags {
|
|
|
|
MOVE_BOOKMARK_NONE = 0x00,
|
|
|
|
MOVE_BOOKMARK_MOVED = 0x01,
|
|
|
|
MOVE_BOOKMARK_CYCLE = 0x02
|
|
|
|
};
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
/* Traverse all bookmarks and move all marked items
|
2007-04-01 07:59:28 -04:00
|
|
|
* _into_ dest or, if insert_as_child is 0, _after_ dest. */
|
2006-05-21 09:46:23 -04:00
|
|
|
static enum move_bookmark_flags
|
2006-06-15 16:53:38 -04:00
|
|
|
do_move_bookmark(struct bookmark *dest, int insert_as_child,
|
|
|
|
struct list_head *src, struct listbox_data *box)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
static int move_bookmark_event_id = EVENT_NONE;
|
|
|
|
struct bookmark *bm, *next;
|
2006-05-21 09:46:23 -04:00
|
|
|
enum move_bookmark_flags result = MOVE_BOOKMARK_NONE;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
set_event_id(move_bookmark_event_id, "bookmark-move");
|
|
|
|
|
|
|
|
foreachsafe (bm, next, *src) {
|
2006-05-21 09:46:23 -04:00
|
|
|
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 {
|
2006-06-15 17:14:51 -04:00
|
|
|
struct hierbox_dialog_list_item *item;
|
|
|
|
|
2006-05-21 09:46:23 -04:00
|
|
|
result |= MOVE_BOOKMARK_MOVED;
|
2005-09-15 09:58:31 -04:00
|
|
|
bm->box_item->marked = 0;
|
|
|
|
|
2006-06-15 16:53:38 -04:00
|
|
|
trigger_event(move_bookmark_event_id, bm, dest);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2006-06-15 17:14:51 -04:00
|
|
|
foreach (item, bookmark_browser.dialogs) {
|
|
|
|
struct widget_data *widget_data;
|
|
|
|
struct listbox_data *box2;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2006-06-15 17:14:51 -04:00
|
|
|
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
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
del_from_list(bm->box_item);
|
|
|
|
del_from_list(bm);
|
2006-06-15 16:53:38 -04:00
|
|
|
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;
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
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) {
|
2006-06-15 16:53:38 -04:00
|
|
|
result |= do_move_bookmark(dest, insert_as_child,
|
2006-05-21 09:46:23 -04:00
|
|
|
&bm->child, box);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
2006-05-21 09:46:23 -04:00
|
|
|
|
|
|
|
return result;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2006-06-15 16:53:38 -04:00
|
|
|
int insert_as_child = 0;
|
2006-05-21 09:46:23 -04:00
|
|
|
enum move_bookmark_flags result;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (!box->sel) return EVENT_PROCESSED; /* nowhere to move to */
|
|
|
|
|
|
|
|
dest = box->sel->udata;
|
|
|
|
if (box->sel->type == BI_FOLDER && box->sel->expanded) {
|
2006-06-15 16:53:38 -04:00
|
|
|
insert_as_child = 1;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-06-15 16:53:38 -04:00
|
|
|
result = do_move_bookmark(dest, insert_as_child, &bookmarks, box);
|
2006-05-21 09:46:23 -04:00
|
|
|
if (result & MOVE_BOOKMARK_MOVED) {
|
|
|
|
bookmarks_set_dirty();
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#ifdef BOOKMARKS_RESAVE
|
2006-05-21 09:46:23 -04:00
|
|
|
write_bookmarks();
|
2005-09-15 09:58:31 -04:00
|
|
|
#endif
|
2006-06-15 17:14:51 -04:00
|
|
|
update_hierbox_browser(&bookmark_browser);
|
2006-06-01 14:23:31 -04:00
|
|
|
}
|
|
|
|
#ifndef CONFIG_SMALL
|
|
|
|
else if (result & MOVE_BOOKMARK_CYCLE) {
|
2006-05-21 09:46:23 -04:00
|
|
|
/* 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."));
|
|
|
|
}
|
2006-06-01 14:23:31 -04:00
|
|
|
#endif /* ndef CONFIG_SMALL */
|
2006-05-21 09:46:23 -04:00
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
return EVENT_PROCESSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**** MANAGEMENT *****************************************************/
|
|
|
|
|
2007-02-04 06:54:07 -05:00
|
|
|
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)] */
|
2005-09-15 09:58:31 -04:00
|
|
|
{ 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 */
|
2007-02-04 06:54:07 -05:00
|
|
|
{ N_("Save"), push_save_button, 0 },
|
2005-09-15 09:58:31 -04:00
|
|
|
#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) {
|
2006-06-06 01:08:01 -04:00
|
|
|
mem_free_set(&bm_last_searched_title, NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
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;
|
|
|
|
|
2007-03-11 06:22:02 -04:00
|
|
|
assertm(dlg->udata != NULL, "Bookmark search with NULL udata in dialog");
|
2005-09-15 09:58:31 -04:00
|
|
|
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);
|
|
|
|
}
|