move-link-up-line: segfault when cursor was below last line.
[elinks/kon.git] / src / bookmarks / dialogs.c
blob662110891f74e0c512b85d8a22b3d26d09a0ff92
1 /* Bookmarks dialogs */
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
5 #endif
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
11 #include <string.h>
13 #include "elinks.h"
15 #include "bfu/dialog.h"
16 #include "bfu/hierbox.h"
17 #include "bfu/listbox.h"
18 #include "bookmarks/bookmarks.h"
19 #include "bookmarks/dialogs.h"
20 #include "dialogs/edit.h"
21 #include "intl/gettext/libintl.h"
22 #include "main/object.h"
23 #include "protocol/uri.h"
24 #include "session/session.h"
25 #include "terminal/terminal.h"
26 #include "util/conv.h"
27 #include "util/error.h"
28 #include "util/memory.h"
29 #include "util/time.h"
32 /* Whether to save bookmarks after each modification of their list
33 * (add/modify/delete). */
34 #define BOOKMARKS_RESAVE 1
37 static void
38 lock_bookmark(struct listbox_item *item)
40 object_lock((struct bookmark *) item->udata);
43 static void
44 unlock_bookmark(struct listbox_item *item)
46 object_unlock((struct bookmark *) item->udata);
49 static int
50 is_bookmark_used(struct listbox_item *item)
52 return is_object_used((struct bookmark *) item->udata);
55 static unsigned char *
56 get_bookmark_text(struct listbox_item *item, struct terminal *term)
58 struct bookmark *bookmark = item->udata;
60 return stracpy(bookmark->title);
63 static unsigned char *
64 get_bookmark_info(struct listbox_item *item, struct terminal *term)
66 struct bookmark *bookmark = item->udata;
67 struct string info;
69 if (item->type == BI_FOLDER) return NULL;
70 if (!init_string(&info)) return NULL;
72 add_format_to_string(&info, "%s: %s", _("Title", term), bookmark->title);
73 add_format_to_string(&info, "\n%s: %s", _("URL", term), bookmark->url);
75 return info.source;
78 static struct uri *
79 get_bookmark_uri(struct listbox_item *item)
81 struct bookmark *bookmark = item->udata;
83 return bookmark->url && *bookmark->url
84 ? get_translated_uri(bookmark->url, NULL) : NULL;
87 static struct listbox_item *
88 get_bookmark_root(struct listbox_item *item)
90 struct bookmark *bookmark = item->udata;
92 return bookmark->root ? bookmark->root->box_item : NULL;
95 static int
96 can_delete_bookmark(struct listbox_item *item)
98 return 1;
101 static void
102 delete_bookmark_item(struct listbox_item *item, int last)
104 struct bookmark *bookmark = item->udata;
106 assert(!is_object_used(bookmark));
108 delete_bookmark(bookmark);
110 #ifdef BOOKMARKS_RESAVE
111 if (last) write_bookmarks();
112 #endif
115 static struct listbox_ops_messages bookmarks_messages = {
116 /* cant_delete_item */
117 N_("Sorry, but the bookmark \"%s\" cannot be deleted."),
118 /* cant_delete_used_item */
119 N_("Sorry, but the bookmark \"%s\" is being used by something else."),
120 /* cant_delete_folder */
121 N_("Sorry, but the folder \"%s\" cannot be deleted."),
122 /* cant_delete_used_folder */
123 N_("Sorry, but the folder \"%s\" is being used by something else."),
124 /* delete_marked_items_title */
125 N_("Delete marked bookmarks"),
126 /* delete_marked_items */
127 N_("Delete marked bookmarks?"),
128 /* delete_folder_title */
129 N_("Delete folder"),
130 /* delete_folder */
131 N_("Delete the folder \"%s\" and all bookmarks in it?"),
132 /* delete_item_title */
133 N_("Delete bookmark"),
134 /* delete_item; xgettext:c-format */
135 N_("Delete this bookmark?"),
136 /* clear_all_items_title */
137 N_("Clear all bookmarks"),
138 /* clear_all_items_title */
139 N_("Do you really want to remove all bookmarks?"),
142 static const struct listbox_ops bookmarks_listbox_ops = {
143 lock_bookmark,
144 unlock_bookmark,
145 is_bookmark_used,
146 get_bookmark_text,
147 get_bookmark_info,
148 get_bookmark_uri,
149 get_bookmark_root,
150 NULL,
151 can_delete_bookmark,
152 delete_bookmark_item,
153 NULL,
154 &bookmarks_messages,
157 /****************************************************************************
158 Bookmark manager stuff.
159 ****************************************************************************/
161 /* Callback for the "add" button in the bookmark manager */
162 static widget_handler_status_T
163 push_add_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
165 launch_bm_add_doc_dialog(dlg_data->win->term, dlg_data,
166 (struct session *) dlg_data->dlg->udata);
167 return EVENT_PROCESSED;
171 static
172 void launch_bm_search_doc_dialog(struct terminal *, struct dialog_data *,
173 struct session *);
176 /* Callback for the "search" button in the bookmark manager */
177 static widget_handler_status_T
178 push_search_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
180 launch_bm_search_doc_dialog(dlg_data->win->term, dlg_data,
181 (struct session *) dlg_data->dlg->udata);
182 return EVENT_PROCESSED;
186 static void
187 move_bookmark_after_selected(struct bookmark *bookmark, struct bookmark *selected)
189 if (selected == bookmark->root
190 || !selected
191 || !selected->box_item
192 || !bookmark->box_item)
193 return;
195 del_from_list(bookmark->box_item);
196 del_from_list(bookmark);
198 add_at_pos(selected, bookmark);
199 add_at_pos(selected->box_item, bookmark->box_item);
202 static void
203 do_add_bookmark(struct dialog_data *dlg_data, unsigned char *title, unsigned char *url)
205 struct bookmark *bm = NULL;
206 struct bookmark *selected = NULL;
207 struct listbox_data *box = NULL;
209 if (dlg_data) {
210 box = get_dlg_listbox_data(dlg_data);
212 if (box->sel) {
213 selected = box->sel->udata;
215 if (box->sel->type == BI_FOLDER && box->sel->expanded) {
216 bm = selected;
217 } else {
218 bm = selected->root;
223 bm = add_bookmark(bm, 1, title, url);
224 if (!bm) return;
226 move_bookmark_after_selected(bm, selected);
228 #ifdef BOOKMARKS_RESAVE
229 write_bookmarks();
230 #endif
232 if (dlg_data) {
233 struct widget_data *widget_data = dlg_data->widgets_data;
235 /* We touch only the actual bookmark dialog, not all of them;
236 * that's right, right? ;-) --pasky */
237 listbox_sel(widget_data, bm->box_item);
242 /**** ADD FOLDER *****************************************************/
244 static void
245 do_add_folder(struct dialog_data *dlg_data, unsigned char *foldername)
247 do_add_bookmark(dlg_data, foldername, NULL);
250 static widget_handler_status_T
251 push_add_folder_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
253 input_dialog(dlg_data->win->term, NULL,
254 N_("Add folder"), N_("Folder name"),
255 dlg_data, NULL,
256 MAX_STR_LEN, NULL, 0, 0, NULL,
257 (void (*)(void *, unsigned char *)) do_add_folder,
258 NULL);
259 return EVENT_PROCESSED;
263 /**** ADD SEPARATOR **************************************************/
265 static widget_handler_status_T
266 push_add_separator_button(struct dialog_data *dlg_data, struct widget_data *widget_data)
268 do_add_bookmark(dlg_data, "-", "");
269 redraw_dialog(dlg_data, 1);
270 return EVENT_PROCESSED;
274 /**** EDIT ***********************************************************/
276 /* Called when an edit is complete. */
277 static void
278 bookmark_edit_done(void *data) {
279 struct dialog *dlg = data;
280 struct bookmark *bm = (struct bookmark *) dlg->udata2;
282 update_bookmark(bm, dlg->widgets[0].data, dlg->widgets[1].data);
283 object_unlock(bm);
285 #ifdef BOOKMARKS_RESAVE
286 write_bookmarks();
287 #endif
290 static void
291 bookmark_edit_cancel(struct dialog *dlg) {
292 struct bookmark *bm = (struct bookmark *) dlg->udata2;
294 object_unlock(bm);
297 /* Called when the edit button is pushed */
298 static widget_handler_status_T
299 push_edit_button(struct dialog_data *dlg_data, struct widget_data *edit_btn)
301 struct listbox_data *box = get_dlg_listbox_data(dlg_data);
303 /* Follow the bookmark */
304 if (box->sel) {
305 struct bookmark *bm = (struct bookmark *) box->sel->udata;
306 const unsigned char *title = bm->title;
307 const unsigned char *url = bm->url;
309 object_lock(bm);
310 do_edit_dialog(dlg_data->win->term, 1, N_("Edit bookmark"),
311 title, url,
312 (struct session *) dlg_data->dlg->udata, dlg_data,
313 bookmark_edit_done, bookmark_edit_cancel,
314 (void *) bm, EDIT_DLG_ADD);
317 return EVENT_PROCESSED;
321 /**** MOVE ***********************************************************/
323 static struct bookmark *move_cache_root_avoid;
325 static void
326 update_depths(struct listbox_item *parent)
328 struct listbox_item *item;
330 foreach (item, parent->child) {
331 item->depth = parent->depth + 1;
332 if (item->type == BI_FOLDER)
333 update_depths(item);
337 enum move_bookmark_flags {
338 MOVE_BOOKMARK_NONE = 0x00,
339 MOVE_BOOKMARK_MOVED = 0x01,
340 MOVE_BOOKMARK_CYCLE = 0x02
343 /* Traverse all bookmarks and move all marked items
344 * _into_ dest or, if insert_as_child is 0, _after_ dest. */
345 static enum move_bookmark_flags
346 do_move_bookmark(struct bookmark *dest, int insert_as_child,
347 LIST_OF(struct bookmark) *src, struct listbox_data *box)
349 static int move_bookmark_event_id = EVENT_NONE;
350 struct bookmark *bm, *next;
351 enum move_bookmark_flags result = MOVE_BOOKMARK_NONE;
353 set_event_id(move_bookmark_event_id, "bookmark-move");
355 foreachsafe (bm, next, *src) {
356 if (!bm->box_item->marked) {
357 /* Don't move this bookmark itself; but if
358 * it's a folder, then we'll look inside. */
359 } else if (bm == dest || bm == move_cache_root_avoid) {
360 /* Prevent moving a folder into itself. */
361 result |= MOVE_BOOKMARK_CYCLE;
362 } else {
363 struct hierbox_dialog_list_item *item;
365 result |= MOVE_BOOKMARK_MOVED;
366 bm->box_item->marked = 0;
368 trigger_event(move_bookmark_event_id, bm, dest);
370 foreach (item, bookmark_browser.dialogs) {
371 struct widget_data *widget_data;
372 struct listbox_data *box2;
374 widget_data = item->dlg_data->widgets_data;
375 box2 = get_listbox_widget_data(widget_data);
377 if (box2->top == bm->box_item)
378 listbox_sel_move(widget_data, 1);
381 del_from_list(bm->box_item);
382 del_from_list(bm);
383 if (insert_as_child) {
384 add_to_list(dest->child, bm);
385 add_to_list(dest->box_item->child, bm->box_item);
386 bm->root = dest;
387 insert_as_child = 0;
388 } else {
389 add_at_pos(dest, bm);
390 add_at_pos(dest->box_item, bm->box_item);
391 bm->root = dest->root;
394 bm->box_item->depth = bm->root
395 ? bm->root->box_item->depth + 1
396 : 0;
398 if (bm->box_item->type == BI_FOLDER)
399 update_depths(bm->box_item);
401 dest = bm;
403 /* We don't want to care about anything marked inside
404 * of the marked folder, let's move it as a whole
405 * directly. I believe that this is more intuitive.
406 * --pasky */
407 continue;
410 if (bm->box_item->type == BI_FOLDER) {
411 result |= do_move_bookmark(dest, insert_as_child,
412 &bm->child, box);
416 return result;
419 static widget_handler_status_T
420 push_move_button(struct dialog_data *dlg_data,
421 struct widget_data *blah)
423 struct listbox_data *box = get_dlg_listbox_data(dlg_data);
424 struct bookmark *dest = NULL;
425 int insert_as_child = 0;
426 enum move_bookmark_flags result;
428 if (!box->sel) return EVENT_PROCESSED; /* nowhere to move to */
430 dest = box->sel->udata;
431 if (box->sel->type == BI_FOLDER && box->sel->expanded) {
432 insert_as_child = 1;
434 /* Avoid recursion headaches (prevents moving a folder into itself). */
435 move_cache_root_avoid = NULL;
437 struct bookmark *bm = dest->root;
439 while (bm) {
440 if (bm->box_item->marked)
441 move_cache_root_avoid = bm;
442 bm = bm->root;
446 result = do_move_bookmark(dest, insert_as_child, &bookmarks, box);
447 if (result & MOVE_BOOKMARK_MOVED) {
448 bookmarks_set_dirty();
450 #ifdef BOOKMARKS_RESAVE
451 write_bookmarks();
452 #endif
453 update_hierbox_browser(&bookmark_browser);
455 #ifndef CONFIG_SMALL
456 else if (result & MOVE_BOOKMARK_CYCLE) {
457 /* If the user also selected other bookmarks, then
458 * they have already been moved, and this box doesn't
459 * appear. */
460 info_box(dlg_data->win->term, 0,
461 N_("Cannot move folder inside itself"), ALIGN_LEFT,
462 N_("You are trying to move the marked folder inside "
463 "itself. To move the folder to a different "
464 "location select the new location before pressing "
465 "the Move button."));
466 } else {
467 info_box(dlg_data->win->term, 0,
468 N_("Nothing to move"), ALIGN_LEFT,
469 N_("To move bookmarks, first mark all the bookmarks "
470 "(or folders) you want to move. This can be done "
471 "with the Insert key if you're using the default "
472 "key-bindings. An asterisk will appear near all "
473 "marked bookmarks. Now move to where you want to "
474 "have the stuff moved to, and press the \"Move\" "
475 "button."));
477 #endif /* ndef CONFIG_SMALL */
479 return EVENT_PROCESSED;
483 /**** MANAGEMENT *****************************************************/
485 static const struct hierbox_browser_button bookmark_buttons[] = {
486 /* [gettext_accelerator_context(.bookmark_buttons)] */
487 { N_("~Goto"), push_hierbox_goto_button, 1 },
488 { N_("~Edit"), push_edit_button, 0 },
489 { N_("~Delete"), push_hierbox_delete_button, 0 },
490 { N_("~Add"), push_add_button, 0 },
491 { N_("Add se~parator"), push_add_separator_button, 0 },
492 { N_("Add ~folder"), push_add_folder_button, 0 },
493 { N_("~Move"), push_move_button, 0 },
494 { N_("~Search"), push_search_button, 1 },
495 #if 0
496 /* This one is too dangerous, so just let user delete
497 * the bookmarks file if needed. --Zas */
498 { N_("Clear"), push_hierbox_clear_button, 0 },
500 /* TODO: Would this be useful? --jonas */
501 { N_("Save"), push_save_button, 0 },
502 #endif
505 struct_hierbox_browser(
506 bookmark_browser,
507 N_("Bookmark manager"),
508 bookmark_buttons,
509 &bookmarks_listbox_ops
512 /* Builds the "Bookmark manager" dialog */
513 void
514 bookmark_manager(struct session *ses)
516 free_last_searched_bookmark();
517 bookmark_browser.expansion_callback = bookmarks_set_dirty;
518 hierbox_browser(&bookmark_browser, ses);
522 /****************************************************************************\
523 Bookmark search dialog.
524 \****************************************************************************/
527 /* Searchs a substring either in title or url fields (ignoring
528 * case). If search_title and search_url are not empty, it selects bookmarks
529 * matching the first OR the second.
531 * Perhaps another behavior could be to search bookmarks matching both
532 * (replacing OR by AND), but it would break a cool feature: when on a page,
533 * opening search dialog will have fields corresponding to that page, so
534 * pressing ok will find any bookmark with that title or url, permitting a
535 * rapid search of an already existing bookmark. --Zas */
537 struct bookmark_search_ctx {
538 unsigned char *url;
539 unsigned char *title;
540 int found;
541 int offset;
544 #define NULL_BOOKMARK_SEARCH_CTX {NULL, NULL, 0, 0}
546 static int
547 test_search(struct listbox_item *item, void *data_, int *offset)
549 struct bookmark_search_ctx *ctx = data_;
551 if (!ctx->offset) {
552 ctx->found = 0; /* ignore possible match on first item */
553 } else {
554 struct bookmark *bm = item->udata;
556 assert(ctx->title && ctx->url);
558 ctx->found = (*ctx->title && strcasestr(bm->title, ctx->title))
559 || (*ctx->url && strcasestr(bm->url, ctx->url));
561 if (ctx->found) *offset = 0;
563 ctx->offset++;
565 return 0;
568 /* Last searched values */
569 static unsigned char *bm_last_searched_title = NULL;
570 static unsigned char *bm_last_searched_url = NULL;
572 void
573 free_last_searched_bookmark(void)
575 mem_free_set(&bm_last_searched_title, NULL);
576 mem_free_set(&bm_last_searched_url, NULL);
579 static int
580 memorize_last_searched_bookmark(struct bookmark_search_ctx *ctx)
582 /* Memorize last searched title */
583 mem_free_set(&bm_last_searched_title, stracpy(ctx->title));
584 if (!bm_last_searched_title) return 0;
586 /* Memorize last searched url */
587 mem_free_set(&bm_last_searched_url, stracpy(ctx->url));
588 if (!bm_last_searched_url) {
589 mem_free_set(&bm_last_searched_title, NULL);
590 return 0;
593 return 1;
596 /* Search bookmarks */
597 static void
598 bookmark_search_do(void *data)
600 struct dialog *dlg = data;
601 struct bookmark_search_ctx ctx = NULL_BOOKMARK_SEARCH_CTX;
602 struct listbox_data *box;
603 struct dialog_data *dlg_data;
605 assertm(dlg->udata != NULL, "Bookmark search with NULL udata in dialog");
606 if_assert_failed return;
608 ctx.title = dlg->widgets[0].data;
609 ctx.url = dlg->widgets[1].data;
611 if (!ctx.title || !ctx.url)
612 return;
614 if (!memorize_last_searched_bookmark(&ctx))
615 return;
617 dlg_data = (struct dialog_data *) dlg->udata;
618 box = get_dlg_listbox_data(dlg_data);
620 traverse_listbox_items_list(box->sel, box, 0, 0, test_search, &ctx);
621 if (!ctx.found) return;
623 listbox_sel_move(dlg_data->widgets_data, ctx.offset - 1);
627 static void
628 launch_bm_search_doc_dialog(struct terminal *term,
629 struct dialog_data *parent,
630 struct session *ses)
632 do_edit_dialog(term, 1, N_("Search bookmarks"),
633 bm_last_searched_title, bm_last_searched_url,
634 ses, parent, bookmark_search_do, NULL, NULL,
635 EDIT_DLG_SEARCH);
640 /****************************************************************************\
641 Bookmark add dialog.
642 \****************************************************************************/
644 /* Adds the bookmark */
645 static void
646 bookmark_add_add(void *data)
648 struct dialog *dlg = data;
649 struct dialog_data *dlg_data = (struct dialog_data *) dlg->udata;
651 do_add_bookmark(dlg_data, dlg->widgets[0].data, dlg->widgets[1].data);
654 void
655 launch_bm_add_dialog(struct terminal *term,
656 struct dialog_data *parent,
657 struct session *ses,
658 unsigned char *title,
659 unsigned char *url)
661 do_edit_dialog(term, 1, N_("Add bookmark"), title, url, ses,
662 parent, bookmark_add_add, NULL, NULL, EDIT_DLG_ADD);
665 void
666 launch_bm_add_doc_dialog(struct terminal *term,
667 struct dialog_data *parent,
668 struct session *ses)
670 launch_bm_add_dialog(term, parent, ses, NULL, NULL);
673 void
674 launch_bm_add_link_dialog(struct terminal *term,
675 struct dialog_data *parent,
676 struct session *ses)
678 unsigned char title[MAX_STR_LEN], url[MAX_STR_LEN];
680 launch_bm_add_dialog(term, parent, ses,
681 get_current_link_name(ses, title, MAX_STR_LEN),
682 get_current_link_url(ses, url, MAX_STR_LEN));
686 /****************************************************************************\
687 Bookmark tabs dialog.
688 \****************************************************************************/
690 void
691 bookmark_terminal_tabs_dialog(struct terminal *term)
693 struct string string;
695 if (!init_string(&string)) return;
697 add_to_string(&string, _("Saved session", term));
699 #ifdef HAVE_STRFTIME
700 add_to_string(&string, " - ");
701 add_date_to_string(&string, get_opt_str("ui.date_format"), NULL);
702 #endif
704 input_dialog(term, NULL,
705 N_("Bookmark tabs"), N_("Enter folder name"),
706 term, NULL,
707 MAX_STR_LEN, string.source, 0, 0, NULL,
708 (void (*)(void *, unsigned char *)) bookmark_terminal_tabs,
709 NULL);
711 done_string(&string);