1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 #include "toolbar-prefs.h"
26 
27 #include <string.h>
28 #include <glib/gi18n.h>
29 
30 #include "balsa-app.h"
31 #include "balsa-icons.h"
32 #include "main-window.h"
33 #include "message-window.h"
34 #include "sendmsg-window.h"
35 #include "toolbar-factory.h"
36 
37 #if HAVE_MACOSX_DESKTOP
38 #  include "macosx-helpers.h"
39 #endif
40 
41 #ifndef MAX
42 #define MAX(a, b) ((a) > (b) ? (a) : (b))
43 #endif
44 
45 /* Enumeration for GtkTreeModel columns. */
46 enum {
47     TP_TEXT_COLUMN,
48     TP_ICON_COLUMN,
49     TP_ITEM_COLUMN,
50     TP_N_COLUMNS
51 };
52 
53 static GtkWidget *customize_widget;
54 
55 /* Structure associated with each notebook page. */
56 typedef struct ToolbarPage_ ToolbarPage;
57 struct ToolbarPage_ {
58     BalsaToolbarModel *model;
59     GtkWidget *available;
60     GtkWidget *current;
61     GtkWidget *toolbar;
62     GtkWidget *add_button;
63     GtkWidget *remove_button;
64     GtkWidget *back_button;
65     GtkWidget *forward_button;
66     GtkWidget *standard_button;
67 };
68 
69 /* Callbacks. */
70 static void tp_dialog_response_cb(GtkDialog * dialog, gint response,
71                                   gpointer data);
72 static void add_button_cb(GtkWidget *, ToolbarPage * page);
73 static void remove_button_cb(GtkWidget *, ToolbarPage * page);
74 static void back_button_cb(GtkWidget *, ToolbarPage * page);
75 static void forward_button_cb(GtkWidget *, ToolbarPage * page);
76 static void wrap_toggled_cb(GtkWidget * widget, GtkNotebook * notebook);
77 static void available_selection_changed_cb(GtkTreeSelection * selection,
78                                            ToolbarPage * page);
79 static void current_selection_changed_cb(GtkTreeSelection * selection,
80                                          ToolbarPage * page);
81 static void available_row_activated_cb(GtkTreeView * treeview,
82                                        GtkTreeIter * arg1,
83                                        GtkTreePath * arg2,
84                                        ToolbarPage * page);
85 static void current_row_activated_cb(GtkTreeView * treeview,
86                                      GtkTreeIter * arg1,
87                                      GtkTreePath * arg2,
88                                      ToolbarPage * page);
89 
90 /* Helpers. */
91 static GtkWidget *create_toolbar_page(BalsaToolbarModel * model,
92                                       GtkUIManager * ui_manager);
93 static GtkWidget *tp_list_new(void);
94 static gboolean tp_list_iter_is_first(GtkWidget * list, GtkTreeIter * iter);
95 static gboolean tp_list_iter_is_last(GtkWidget * list, GtkTreeIter * iter);
96 static void tp_page_refresh_available(ToolbarPage * page);
97 static void tp_page_refresh_current(ToolbarPage * page);
98 static void tp_page_refresh_preview(ToolbarPage * page);
99 static void tp_page_swap_rows(ToolbarPage * page, gboolean forward);
100 static void tp_page_add_selected(ToolbarPage * page);
101 static void tp_page_remove_selected(ToolbarPage * page);
102 static void tp_store_set(GtkListStore * store, GtkTreeIter * iter,
103                          gint item);
104 static void replace_nl_with_space(char* str);
105 
106 /* Public methods. */
107 
108 /* create the toolbar-customization dialog
109  */
110 void
customize_dialog_cb(GtkWidget * widget,gpointer data)111 customize_dialog_cb(GtkWidget * widget, gpointer data)
112 {
113     GtkWidget *notebook;
114     GtkWidget *child;
115     GtkWidget *option_frame;
116     GtkWidget *option_box;
117     GtkWidget *wrap_button;
118     GtkWidget *active_window = data;
119     BalsaToolbarModel *model;
120     BalsaToolbarType   type;
121     GtkUIManager * ui_manager;
122     GtkWidget *content_area;
123 
124     /* There can only be one */
125     if (customize_widget) {
126         gtk_window_present(GTK_WINDOW(customize_widget));
127         return;
128     }
129 
130     customize_widget =
131         gtk_dialog_new_with_buttons(_("Customize Toolbars"),
132                                     GTK_WINDOW(active_window),
133                                     GTK_DIALOG_DESTROY_WITH_PARENT,
134                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
135                                     GTK_STOCK_HELP,  GTK_RESPONSE_HELP,
136                                     NULL);
137 #if HAVE_MACOSX_DESKTOP
138     libbalsa_macosx_menu_for_parent(customize_widget, GTK_WINDOW(active_window));
139 #endif
140     g_object_add_weak_pointer(G_OBJECT(customize_widget),
141                               (gpointer) & customize_widget);
142     g_signal_connect(G_OBJECT(customize_widget), "response",
143                      G_CALLBACK(tp_dialog_response_cb), NULL);
144 
145     notebook = gtk_notebook_new();
146     content_area =
147         gtk_dialog_get_content_area(GTK_DIALOG(customize_widget));
148     gtk_container_add(GTK_CONTAINER(content_area), notebook);
149 
150     gtk_window_set_wmclass(GTK_WINDOW(customize_widget), "customize",
151                            "Balsa");
152     gtk_window_set_default_size(GTK_WINDOW(customize_widget), 600, 440);
153 
154     /* The order of pages must be consistent with the BalsaToolbarType
155      * enum. */
156     model = balsa_window_get_toolbar_model();
157     ui_manager = balsa_window_ui_manager_new(NULL);
158     child = create_toolbar_page(model, ui_manager);
159     g_object_unref(ui_manager);
160     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child,
161                              gtk_label_new(_("Main window")));
162 
163     model = sendmsg_window_get_toolbar_model();
164     ui_manager = sendmsg_window_ui_manager_new(NULL);
165     child = create_toolbar_page(model, ui_manager);
166     g_object_unref(ui_manager);
167     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child,
168                              gtk_label_new(_("Compose window")));
169 
170     model = message_window_get_toolbar_model();
171     ui_manager = message_window_ui_manager_new(NULL);
172     child = create_toolbar_page(model, ui_manager);
173     g_object_unref(ui_manager);
174     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child,
175                              gtk_label_new(_("Message window")));
176 
177     option_frame = gtk_frame_new(_("Toolbar options"));
178     gtk_container_set_border_width(GTK_CONTAINER(option_frame), 6);
179     gtk_box_pack_start(GTK_BOX(content_area), option_frame, FALSE, FALSE, 0);
180 
181     option_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
182     gtk_container_set_border_width(GTK_CONTAINER(option_box), 6);
183     gtk_container_add(GTK_CONTAINER(option_frame), option_box);
184 
185     wrap_button =
186         gtk_check_button_new_with_mnemonic(_("_Wrap button labels"));
187     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wrap_button),
188                                  balsa_app.toolbar_wrap_button_text);
189     g_signal_connect(G_OBJECT(wrap_button), "toggled",
190                      G_CALLBACK(wrap_toggled_cb), notebook);
191     gtk_box_pack_start(GTK_BOX(option_box), wrap_button, FALSE, FALSE, 0);
192 
193     gtk_widget_show_all(customize_widget);
194 
195     /* Now that the pages are shown, we can switch to the page
196      * corresponding to the toolbar that the user clicked on. */
197     type =
198         GPOINTER_TO_INT(g_object_get_data
199                         (G_OBJECT(widget), BALSA_TOOLBAR_MODEL_TYPE));
200     gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), type);
201 }
202 
203 /* get_toolbar_button_index:
204    id - button id
205    returns -1 on failure.
206 */
207 int
get_toolbar_button_index(const char * id)208 get_toolbar_button_index(const char *id)
209 {
210     int i;
211 
212     g_return_val_if_fail(id, -1);
213 
214     for(i=0; i<toolbar_button_count; i++) {
215 	if(!strcmp(id, toolbar_buttons[i].pixmap_id))
216 	    return i;
217     }
218     return -1;
219 }
220 
221 /* Callbacks. */
222 
223 #define BALSA_KEY_TOOLBAR_PAGE "balsa-toolbar-page"
224 
225 /* Callback for the wrap_buttons' "toggled" signal. */
226 static void
wrap_toggled_cb(GtkWidget * widget,GtkNotebook * notebook)227 wrap_toggled_cb(GtkWidget * widget, GtkNotebook * notebook)
228 {
229     gint i;
230     GtkWidget *child;
231     ToolbarPage *page;
232 
233     balsa_app.toolbar_wrap_button_text =
234         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
235 
236     for (i = 0; (child = gtk_notebook_get_nth_page(notebook, i)); i++) {
237         page = g_object_get_data(G_OBJECT(child), BALSA_KEY_TOOLBAR_PAGE);
238         balsa_toolbar_model_changed(page->model);
239     }
240 }
241 
242 /* Button callbacks: each makes the appropriate change to the
243  * page->current GtkTreeView, then refreshes the page's
244  * BalsaToolbarModel and GtkToolbar; add_button_cb and remove_button_cb
245  * also refresh the page's available GtkTreeView.
246  */
247 static void
back_button_cb(GtkWidget * widget,ToolbarPage * page)248 back_button_cb(GtkWidget * widget, ToolbarPage * page)
249 {
250     tp_page_swap_rows(page, FALSE);
251 }
252 
253 static void
forward_button_cb(GtkWidget * widget,ToolbarPage * page)254 forward_button_cb(GtkWidget * widget, ToolbarPage * page)
255 {
256     tp_page_swap_rows(page, TRUE);
257 }
258 
259 static void
add_button_cb(GtkWidget * widget,ToolbarPage * page)260 add_button_cb(GtkWidget *widget, ToolbarPage * page)
261 {
262     tp_page_add_selected(page);
263 }
264 
265 static void
remove_button_cb(GtkWidget * widget,ToolbarPage * page)266 remove_button_cb(GtkWidget *widget, ToolbarPage * page)
267 {
268     tp_page_remove_selected(page);
269 }
270 
271 static void
standard_button_cb(GtkWidget * widget,ToolbarPage * page)272 standard_button_cb(GtkWidget *widget, ToolbarPage * page)
273 {
274     balsa_toolbar_model_clear(page->model);
275     gtk_widget_set_sensitive(page->standard_button, FALSE);
276     tp_page_refresh_available(page);
277     tp_page_refresh_current(page);
278     balsa_toolbar_model_changed(page->model);
279 }
280 
281 static void
style_button_cb(GtkWidget * widget,ToolbarPage * page)282 style_button_cb(GtkWidget *widget, ToolbarPage * page)
283 {
284     gboolean handled;
285 
286     g_signal_emit_by_name(page->toolbar, "popup-menu", &handled);
287 }
288 
289 /* Callback for the "row-activated" signal for the available list. */
290 static void
available_row_activated_cb(GtkTreeView * treeview,GtkTreeIter * arg1,GtkTreePath * arg2,ToolbarPage * page)291 available_row_activated_cb(GtkTreeView * treeview, GtkTreeIter * arg1,
292                         GtkTreePath * arg2, ToolbarPage * page)
293 {
294     tp_page_add_selected(page);
295 }
296 
297 /* Callback for the selection "changed" signal for the available list. */
298 static void
available_selection_changed_cb(GtkTreeSelection * selection,ToolbarPage * page)299 available_selection_changed_cb(GtkTreeSelection * selection,
300                             ToolbarPage * page)
301 {
302     gtk_widget_set_sensitive(page->add_button,
303                              gtk_tree_selection_get_selected(selection,
304                                                              NULL, NULL));
305 }
306 
307 /* Callback for the "row-activated" signal for the current list. */
308 static void
current_row_activated_cb(GtkTreeView * treeview,GtkTreeIter * arg1,GtkTreePath * arg2,ToolbarPage * page)309 current_row_activated_cb(GtkTreeView * treeview, GtkTreeIter * arg1,
310                       GtkTreePath * arg2, ToolbarPage * page)
311 {
312     tp_page_remove_selected(page);
313 }
314 
315 /* Callback for the selection "changed" signal for the destination list. */
316 static void
current_selection_changed_cb(GtkTreeSelection * selection,ToolbarPage * page)317 current_selection_changed_cb(GtkTreeSelection * selection, ToolbarPage * page)
318 {
319     GtkTreeModel *model;
320     GtkTreeIter iter;
321     gboolean remove = FALSE;
322     gboolean back = FALSE;
323     gboolean forward = FALSE;
324 
325     if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
326         remove = TRUE;
327         back =
328             !tp_list_iter_is_first(page->current,
329                                    &iter);
330         forward =
331             !tp_list_iter_is_last(page->current,
332                                   &iter);
333     }
334     gtk_widget_set_sensitive(page->remove_button, remove);
335     gtk_widget_set_sensitive(page->back_button, back);
336     gtk_widget_set_sensitive(page->forward_button, forward);
337 }
338 
339 /* Callback for the "response" signal of the dialog. */
340 static void
tp_dialog_response_cb(GtkDialog * dialog,gint response,gpointer data)341 tp_dialog_response_cb(GtkDialog * dialog, gint response, gpointer data)
342 {
343     GdkScreen *screen;
344     GError *err = NULL;
345 
346     switch (response) {
347     case GTK_RESPONSE_DELETE_EVENT:
348     case GTK_RESPONSE_CLOSE:
349         gtk_widget_destroy(GTK_WIDGET(dialog));
350         break;
351     case GTK_RESPONSE_HELP:
352         screen = gtk_widget_get_screen(GTK_WIDGET(dialog));
353         gtk_show_uri(screen, "ghelp:balsa?toolbar-prefs",
354                      gtk_get_current_event_time(), &err);
355         if (err) {
356             balsa_information(LIBBALSA_INFORMATION_WARNING,
357 		    _("Error displaying toolbar help: %s\n"),
358 		    err->message);
359             g_error_free(err);
360         }
361         break;
362     default:
363         break;
364     }
365 }
366 
367 /* Helpers. */
368 
369 /* Create a page for the main notebook.
370  */
371 static GtkWidget*
create_toolbar_page(BalsaToolbarModel * model,GtkUIManager * ui_manager)372 create_toolbar_page(BalsaToolbarModel * model, GtkUIManager * ui_manager)
373 {
374     GtkWidget *outer_box;
375     GtkWidget *toolbar_frame, *toolbar_scroll;
376     GtkWidget *toolbar_ctlbox;
377     GtkWidget *lower_ctlbox, *button_box, *move_button_box, *center_button_box;
378     GtkWidget *list_frame, *list_scroll;
379     GtkWidget *destination_frame, *destination_scroll;
380     GtkWidget *style_button;
381     ToolbarPage *page;
382     GtkTreeSelection *selection;
383 
384     page = g_new(ToolbarPage, 1);
385     page->model = model;
386 
387     /* The "window itself" */
388     outer_box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
389     g_object_set_data_full(G_OBJECT(outer_box), BALSA_KEY_TOOLBAR_PAGE,
390                            page, g_free);
391 
392     /* Preview display */
393     toolbar_frame=gtk_frame_new(_("Preview"));
394     gtk_container_set_border_width(GTK_CONTAINER(toolbar_frame), 5);
395     gtk_box_pack_start(GTK_BOX(outer_box), toolbar_frame, FALSE, FALSE, 0);
396 
397     toolbar_ctlbox=gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
398     gtk_container_add(GTK_CONTAINER(toolbar_frame), toolbar_ctlbox);
399     gtk_container_set_border_width(GTK_CONTAINER(toolbar_ctlbox), 5);
400 
401     /* The ui-manager has actions but no ui, so we add an empty toolbar. */
402     gtk_ui_manager_add_ui_from_string(ui_manager,
403                                       "<ui>"
404                                       "  <toolbar name='Toolbar'/>"
405                                       "</ui>",
406                                       -1, NULL);
407 
408     /* The preview is an actual, fully functional toolbar */
409     page->toolbar = balsa_toolbar_new(model, ui_manager);
410     gtk_widget_set_sensitive(page->toolbar, FALSE);
411 
412     /* embedded in a scrolled_window */
413     toolbar_scroll=gtk_scrolled_window_new(NULL, NULL);
414     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(toolbar_scroll),
415 				   GTK_POLICY_AUTOMATIC,
416                                    GTK_POLICY_NEVER);
417 
418     gtk_box_pack_start(GTK_BOX(toolbar_ctlbox), toolbar_scroll,
419                        TRUE, TRUE, 0);
420 
421 #if GTK_CHECK_VERSION(3, 8, 0)
422     gtk_container_add(GTK_CONTAINER(toolbar_scroll), page->toolbar);
423 #else                           /* GTK_CHECK_VERSION(3, 8, 0) */
424     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(toolbar_scroll),
425                                           page->toolbar);
426 #endif                          /* GTK_CHECK_VERSION(3, 8, 0) */
427 
428     /* Button box */
429     button_box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
430     gtk_box_pack_start(GTK_BOX(toolbar_ctlbox), button_box, FALSE, FALSE, 0);
431 
432     /* Standard button */
433     page->standard_button =
434         gtk_button_new_with_mnemonic(_("_Restore toolbar to standard buttons"));
435     gtk_container_add(GTK_CONTAINER(button_box), page->standard_button);
436 
437     /* Style button */
438     style_button = gtk_button_new_with_mnemonic(_("Toolbar _style..."));
439     gtk_container_add(GTK_CONTAINER(button_box), style_button);
440 
441     /* Done with preview */
442 
443     /* Box for lower half of window */
444     lower_ctlbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
445     gtk_container_set_border_width(GTK_CONTAINER(lower_ctlbox), 5);
446 
447     gtk_box_pack_start(GTK_BOX(outer_box), lower_ctlbox, TRUE, TRUE, 0);
448 
449     /* A list to show the available items */
450     list_scroll=gtk_scrolled_window_new(NULL, NULL);
451 
452     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(list_scroll),
453 				   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
454 
455     list_frame=gtk_frame_new(_("Available buttons"));
456     page->available = tp_list_new();
457 
458     gtk_box_pack_start(GTK_BOX(lower_ctlbox), list_frame,
459 		       TRUE, TRUE, 0);
460     gtk_container_add(GTK_CONTAINER(list_frame), list_scroll);
461     gtk_container_add(GTK_CONTAINER(list_scroll), page->available);
462 
463     /* Done with available list */
464 
465     /* Another list to show the current tools */
466     destination_scroll=gtk_scrolled_window_new(NULL, NULL);
467 
468     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(destination_scroll),
469 				   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
470 
471     destination_frame=gtk_frame_new(_("Current toolbar"));
472     page->current = tp_list_new();
473 
474     /* Done with destination list */
475 
476     /* Button box */
477     center_button_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
478     gtk_box_set_homogeneous(GTK_BOX(center_button_box), TRUE);
479 
480     button_box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
481 
482     gtk_box_pack_start(GTK_BOX(lower_ctlbox), center_button_box,
483 		       FALSE, FALSE, 0);
484 
485     gtk_box_pack_start(GTK_BOX(center_button_box), button_box,
486 		       FALSE, FALSE, 0);
487 
488     page->back_button =
489         balsa_stock_button_with_label(GTK_STOCK_GO_UP, _("Up"));
490     gtk_box_pack_start(GTK_BOX(button_box), page->back_button, FALSE, FALSE, 0);
491 
492     move_button_box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
493     gtk_box_pack_start(GTK_BOX(button_box), move_button_box, FALSE, FALSE, 0);
494 
495     page->remove_button =
496         balsa_stock_button_with_label(GTK_STOCK_GO_BACK, "-");
497     gtk_box_pack_start(GTK_BOX(move_button_box), page->remove_button,
498                        FALSE, FALSE, 0);
499 
500     page->add_button =
501         balsa_stock_button_with_label(GTK_STOCK_GO_FORWARD, "+");
502     gtk_box_pack_start(GTK_BOX(move_button_box), page->add_button, FALSE, FALSE, 0);
503 
504     page->forward_button =
505         balsa_stock_button_with_label(GTK_STOCK_GO_DOWN, _("Down"));
506     gtk_box_pack_start(GTK_BOX(button_box), page->forward_button, FALSE, FALSE, 0);
507 
508     /* Pack destination list */
509     gtk_box_pack_start(GTK_BOX(lower_ctlbox), destination_frame, TRUE, TRUE, 0);
510     gtk_container_add(GTK_CONTAINER(destination_frame), destination_scroll);
511     gtk_container_add(GTK_CONTAINER(destination_scroll), page->current);
512 
513     /* UI signals */
514     g_signal_connect(G_OBJECT(page->available), "row-activated",
515                      G_CALLBACK(available_row_activated_cb), page);
516     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(page->available));
517     g_signal_connect(G_OBJECT(selection), "changed",
518 		       G_CALLBACK(available_selection_changed_cb), page);
519 
520     g_signal_connect(G_OBJECT(page->current), "row-activated",
521                      G_CALLBACK(current_row_activated_cb), page);
522     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
523     g_signal_connect(G_OBJECT(selection), "changed",
524 		       G_CALLBACK(current_selection_changed_cb), page);
525 
526     g_signal_connect(G_OBJECT(page->add_button), "clicked",
527 		       G_CALLBACK(add_button_cb), page);
528     g_signal_connect(G_OBJECT(page->remove_button), "clicked",
529 		       G_CALLBACK(remove_button_cb), page);
530     g_signal_connect(G_OBJECT(page->forward_button), "clicked",
531 		       G_CALLBACK(forward_button_cb), page);
532     g_signal_connect(G_OBJECT(page->back_button), "clicked",
533 		       G_CALLBACK(back_button_cb), page);
534 
535     g_signal_connect(G_OBJECT(page->standard_button), "clicked",
536 		       G_CALLBACK(standard_button_cb), page);
537     g_signal_connect(G_OBJECT(style_button), "clicked",
538 		       G_CALLBACK(style_button_cb), page);
539 
540     gtk_widget_set_sensitive(page->add_button, FALSE);
541     gtk_widget_set_sensitive(page->remove_button, FALSE);
542     gtk_widget_set_sensitive(page->back_button, FALSE);
543     gtk_widget_set_sensitive(page->forward_button, FALSE);
544     gtk_widget_set_sensitive(page->standard_button,
545                              !balsa_toolbar_model_is_standard(model));
546 
547     tp_page_refresh_available(page);
548     tp_page_refresh_current(page);
549 
550     return outer_box;
551 }
552 
553 /* Refresh the page's available GtkTreeView.
554  */
555 static void
tp_page_refresh_available(ToolbarPage * page)556 tp_page_refresh_available(ToolbarPage * page)
557 {
558     GtkTreeSelection *selection =
559         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->available));
560     GtkTreeModel *model;
561     GtkTreeIter iter;
562     GtkTreePath *path;
563     GHashTable *legal = balsa_toolbar_model_get_legal(page->model);
564     GSList *current = balsa_toolbar_model_get_current(page->model);
565     int item;
566 
567     /* save currently selected path, or point path to the first row */
568     if (gtk_tree_selection_get_selected(selection, &model, &iter))
569         path = gtk_tree_model_get_path(model, &iter);
570     else {
571         path = gtk_tree_path_new();
572         gtk_tree_path_down(path);
573     }
574     gtk_list_store_clear(GTK_LIST_STORE(model));
575 
576     for (item = 0; item < toolbar_button_count; item++) {
577         if (item > 0
578             && (!g_hash_table_lookup(legal,
579                                      toolbar_buttons[item].pixmap_id)
580                 || g_slist_find_custom(current,
581                                        toolbar_buttons[item].pixmap_id,
582                                        (GCompareFunc) strcmp)))
583             continue;
584 
585         gtk_list_store_append(GTK_LIST_STORE(model), &iter);
586         tp_store_set(GTK_LIST_STORE(model), &iter, item);
587     }
588 
589     if (gtk_tree_model_get_iter(model, &iter, path)
590         || gtk_tree_path_prev(path)) {
591         gtk_tree_selection_select_path(selection, path);
592         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(page->available),
593                                      path, NULL, FALSE, 0, 0);
594     }
595     gtk_tree_path_free(path);
596 }
597 
598 /* Refresh the page's current GtkTreeView.
599  */
600 static void
tp_page_refresh_current(ToolbarPage * page)601 tp_page_refresh_current(ToolbarPage * page)
602 {
603     GtkTreeSelection *selection =
604         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
605     GtkTreeModel *model;
606     GtkTreeIter iter;
607     GtkTreePath *path;
608     int item;
609     GSList *list;
610 
611     /* save currently selected path, or point path to the first row */
612     if (gtk_tree_selection_get_selected(selection, &model, &iter))
613         path = gtk_tree_model_get_path(model, &iter);
614     else {
615         path = gtk_tree_path_new();
616         gtk_tree_path_down(path);
617     }
618     gtk_list_store_clear(GTK_LIST_STORE(model));
619 
620     for (list = balsa_toolbar_model_get_current(page->model); list;
621          list = g_slist_next(list)) {
622         item = get_toolbar_button_index(list->data);
623         if (item < 0)
624             continue;
625 
626         gtk_list_store_append(GTK_LIST_STORE(model), &iter);
627         tp_store_set(GTK_LIST_STORE(model), &iter, item);
628     }
629 
630     if (gtk_tree_model_get_iter(model, &iter, path)
631         || gtk_tree_path_prev(path)) {
632         gtk_tree_selection_select_path(selection, path);
633         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(page->current),
634                                      path, NULL, FALSE, 0, 0);
635     }
636     gtk_tree_path_free(path);
637 }
638 
639 /* Refresh a page after its destination TreeView has changed.
640  */
641 static void
tp_page_refresh_preview(ToolbarPage * page)642 tp_page_refresh_preview(ToolbarPage * page)
643 {
644     GtkTreeModel *model =
645         gtk_tree_view_get_model(GTK_TREE_VIEW(page->current));
646     GtkTreeIter iter;
647     gboolean valid;
648 
649     balsa_toolbar_model_clear(page->model);
650     for (valid = gtk_tree_model_get_iter_first(model, &iter);
651          valid;
652          valid = gtk_tree_model_iter_next(model, &iter)) {
653         gint item;
654 
655         gtk_tree_model_get(model, &iter, TP_ITEM_COLUMN, &item, -1);
656         if (item >= 0 && item < toolbar_button_count)
657             balsa_toolbar_model_insert_icon(page->model,
658                                             toolbar_buttons[item].pixmap_id,
659                                             -1);
660     }
661 }
662 
663 /* Create a GtkTreeView for an icon list.
664  */
665 static GtkWidget *
tp_list_new(void)666 tp_list_new(void)
667 {
668     GtkListStore *list_store;
669     GtkTreeView *view;
670     GtkCellRenderer *renderer;
671     GtkTreeViewColumn *column;
672 
673     list_store = gtk_list_store_new(TP_N_COLUMNS,
674                                     G_TYPE_STRING,   /* TP_TEXT_COLUMN */
675                                     GDK_TYPE_PIXBUF, /* TP_ICON_COLUMN */
676                                     G_TYPE_INT       /* TP_ITEM_COLUMN */
677                                     );
678     view = GTK_TREE_VIEW(gtk_tree_view_new_with_model
679                          (GTK_TREE_MODEL(list_store)));
680     g_object_unref(list_store);
681 
682     renderer = gtk_cell_renderer_text_new();
683     column =
684         gtk_tree_view_column_new_with_attributes(NULL, renderer, "text",
685                                                  TP_TEXT_COLUMN, NULL);
686     gtk_tree_view_append_column(view, column);
687 
688     renderer = gtk_cell_renderer_pixbuf_new();
689     column =
690         gtk_tree_view_column_new_with_attributes(NULL, renderer, "pixbuf",
691                                                  TP_ICON_COLUMN, NULL);
692     gtk_tree_view_append_column(view, column);
693 
694     gtk_tree_view_set_headers_visible(view, FALSE);
695     return GTK_WIDGET(view);
696 }
697 
698 /* Test whether the iter addresses the first row.
699  */
700 static gboolean
tp_list_iter_is_first(GtkWidget * list,GtkTreeIter * iter)701 tp_list_iter_is_first(GtkWidget * list, GtkTreeIter * iter)
702 {
703     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
704     GtkTreePath *path = gtk_tree_model_get_path(model, iter);
705     gboolean ret_val = !gtk_tree_path_prev(path);
706     gtk_tree_path_free(path);
707     return ret_val;
708 }
709 
710 /* Test whether the iter addresses the last row.
711  */
712 static gboolean
tp_list_iter_is_last(GtkWidget * list,GtkTreeIter * iter)713 tp_list_iter_is_last(GtkWidget * list, GtkTreeIter * iter)
714 {
715     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
716     GtkTreeIter tmp_iter = *iter;
717     return !gtk_tree_model_iter_next(model, &tmp_iter);
718 }
719 
720 /* Swap the currently selected row in page->current
721  * with the next or previous row.
722  */
723 static void
tp_page_swap_rows(ToolbarPage * page,gboolean forward)724 tp_page_swap_rows(ToolbarPage * page, gboolean forward)
725 {
726     GtkTreeModel *model =
727         gtk_tree_view_get_model(GTK_TREE_VIEW(page->current));
728     GtkTreeSelection *selection =
729         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
730     GtkTreeIter iter;
731     GtkTreeIter tmp_iter;
732     GtkTreePath *tmp_path;
733     gint item, tmp_item;
734 
735     selection =
736         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
737     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
738         return;
739 
740     if (forward) {
741         tmp_iter = iter;
742         if (!gtk_tree_model_iter_next(model, &tmp_iter))
743             return;
744         tmp_path = gtk_tree_model_get_path(model, &tmp_iter);
745     } else {
746         tmp_path = gtk_tree_model_get_path(model, &iter);
747         if (!gtk_tree_path_prev(tmp_path)) {
748             gtk_tree_path_free(tmp_path);
749             return;
750         }
751         gtk_tree_model_get_iter(model, &tmp_iter, tmp_path);
752     }
753 
754     gtk_tree_model_get(model, &iter, TP_ITEM_COLUMN, &item, -1);
755     gtk_tree_model_get(model, &tmp_iter, TP_ITEM_COLUMN, &tmp_item, -1);
756     tp_store_set(GTK_LIST_STORE(model), &tmp_iter, item);
757     tp_store_set(GTK_LIST_STORE(model), &iter, tmp_item);
758 
759     gtk_tree_selection_select_path(selection, tmp_path);
760     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(page->current),
761                                  tmp_path, NULL, FALSE, 0, 0);
762     gtk_tree_path_free(tmp_path);
763 
764     gtk_widget_set_sensitive(page->standard_button, TRUE);
765     tp_page_refresh_preview(page);
766     balsa_toolbar_model_changed(page->model);
767 }
768 
769 /* Add an item to a GtkTreeView's GtkListStore.
770  */
771 static void
tp_store_set(GtkListStore * store,GtkTreeIter * iter,gint item)772 tp_store_set(GtkListStore * store, GtkTreeIter * iter, gint item)
773 {
774     GdkPixbuf *pixbuf;
775     gchar *text;
776 
777     text = g_strdup(balsa_toolbar_button_text(item));
778     replace_nl_with_space(text);
779     pixbuf =
780         (item > 0 ? gtk_widget_render_icon_pixbuf(customize_widget,
781                                                   toolbar_buttons[item].
782                                                   pixmap_id,
783                                                   GTK_ICON_SIZE_LARGE_TOOLBAR)
784          : NULL);
785     gtk_list_store_set(store, iter,
786                        TP_TEXT_COLUMN, text,
787                        TP_ICON_COLUMN, pixbuf,
788                        TP_ITEM_COLUMN, item,
789                        -1);
790     g_free(text);
791     if (pixbuf)
792         g_object_unref(pixbuf);
793 }
794 
795 /* Add an item to the current list.
796  */
797 static void
tp_page_add_selected(ToolbarPage * page)798 tp_page_add_selected(ToolbarPage * page)
799 {
800     gint item = 0;
801     GtkTreeSelection *selection;
802     GtkTreeModel *model;
803     GtkTreeIter iter;
804     GtkTreeIter sibling;
805     GtkTreePath *path;
806 
807     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(page->available));
808     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
809 	return;
810     gtk_tree_model_get(model, &iter, TP_ITEM_COLUMN, &item, -1);
811 
812     selection =
813         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
814     if (gtk_tree_selection_get_selected(selection, &model, &sibling))
815         gtk_list_store_insert_before(GTK_LIST_STORE(model), &iter,
816                                      &sibling);
817     else
818         gtk_list_store_append(GTK_LIST_STORE(model), &iter);
819 
820     tp_store_set(GTK_LIST_STORE(model), &iter, item);
821 
822     path = gtk_tree_model_get_path(model, &iter);
823     gtk_tree_selection_select_path(selection, path);
824     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(page->current), path, NULL,
825                                  FALSE, 0, 0);
826     gtk_tree_path_free(path);
827 
828     gtk_widget_set_sensitive(page->standard_button, TRUE);
829     tp_page_refresh_preview(page);
830     tp_page_refresh_available(page);
831     balsa_toolbar_model_changed(page->model);
832 }
833 
834 /* Remove an item from the page's current list.
835  */
836 static void
tp_page_remove_selected(ToolbarPage * page)837 tp_page_remove_selected(ToolbarPage * page)
838 {
839     GtkTreeSelection *selection =
840         gtk_tree_view_get_selection(GTK_TREE_VIEW(page->current));
841     GtkTreeModel *model;
842     GtkTreeIter iter;
843     GtkTreePath *path;
844 
845     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
846         return;
847     path = gtk_tree_model_get_path(model, &iter);
848 
849     gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
850 
851     if (gtk_tree_model_get_iter(model, &iter, path)
852         || gtk_tree_path_prev(path)) {
853         gtk_tree_selection_select_path(selection, path);
854         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(page->current),
855                                      path, NULL, FALSE, 0, 0);
856     }
857     gtk_tree_path_free(path);
858 
859     gtk_widget_set_sensitive(page->standard_button, TRUE);
860     tp_page_refresh_preview(page);
861     tp_page_refresh_available(page);
862     balsa_toolbar_model_changed(page->model);
863 }
864 
865 /* Unwrap text.
866  */
867 static void
replace_nl_with_space(char * str)868 replace_nl_with_space(char* str)
869 {
870     while(*str) {
871 	if(*str == '\n')
872 	    *str=' ';
873 	str++;
874     }
875 }
876