Cache G_TYPE_INSTANCE_GET_PRIVATE() result when initializing an
[geany-mirror.git] / src / toolbar.c
blob9a55ee701748156744c03536daaf913ae16428de
1 /*
2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2009-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * $Id$
24 /**
25 * @file toolbar.h
26 * Toolbar (prefs).
28 /* Utility functions to create the toolbar */
30 #include "geany.h"
31 #include "support.h"
32 #include "ui_utils.h"
33 #include "toolbar.h"
34 #include "callbacks.h"
35 #include "utils.h"
36 #include "dialogs.h"
37 #include "document.h"
38 #include "build.h"
39 #include "main.h"
40 #include "geanymenubuttonaction.h"
41 #include "geanyentryaction.h"
43 #include <string.h>
44 #include <glib/gstdio.h>
47 GeanyToolbarPrefs toolbar_prefs;
48 static GtkUIManager *uim;
49 static GtkActionGroup *group;
50 static GSList *plugin_items = NULL;
52 /* Available toolbar actions
53 * Fields: name, stock_id, label, accelerator, tooltip, callback */
54 const GtkActionEntry ui_entries[] = {
55 /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */
56 { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Save the current file"), G_CALLBACK(on_toolbutton_save_clicked) },
57 { "SaveAll", GEANY_STOCK_SAVE_ALL, NULL, NULL, N_("Save all open files"), G_CALLBACK(on_save_all1_activate) },
58 { "Reload", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked) },
59 { "Close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current file"), G_CALLBACK(on_toolbutton_close_clicked) },
60 { "CloseAll", GEANY_STOCK_CLOSE_ALL, NULL, NULL, N_("Close all open files"), G_CALLBACK(on_toolbutton_close_all_clicked) },
61 { "Cut", GTK_STOCK_CUT, NULL, NULL, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate) },
62 { "Copy", GTK_STOCK_COPY, NULL, NULL, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate) },
63 { "Paste", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate) },
64 { "Delete", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate) },
65 { "Undo", GTK_STOCK_UNDO, NULL, NULL, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate) },
66 { "Redo", GTK_STOCK_REDO, NULL, NULL, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate) },
67 { "NavBack", GTK_STOCK_GO_BACK, NULL, NULL, N_("Navigate back a location"), G_CALLBACK(on_back_activate) },
68 { "NavFor", GTK_STOCK_GO_FORWARD, NULL, NULL, N_("Navigate forward a location"), G_CALLBACK(on_forward_activate) },
69 { "Compile", GTK_STOCK_CONVERT, N_("Compile"), NULL, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked) },
70 { "Run", GTK_STOCK_EXECUTE, NULL, NULL, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked) },
71 { "Color", GTK_STOCK_SELECT_COLOR, N_("Color Chooser"), NULL, N_("Open a color chooser dialog, to interactively pick colors from a palette"), G_CALLBACK(on_show_color_chooser1_activate) },
72 { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, NULL, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate) },
73 { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, NULL, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate) },
74 { "UnIndent", GTK_STOCK_UNINDENT, NULL, NULL, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate) },
75 { "Indent", GTK_STOCK_INDENT, NULL, NULL, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate) },
76 { "Search", GTK_STOCK_FIND, NULL, NULL, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked) },
77 { "Goto", GTK_STOCK_JUMP_TO, NULL, NULL, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked) },
78 { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, N_("Show the preferences dialog"), G_CALLBACK(on_toolbutton_preferences_clicked) },
79 { "Quit", GTK_STOCK_QUIT, NULL, NULL, N_("Quit Geany"), G_CALLBACK(on_toolbutton_quit_clicked) },
80 { "Print", GTK_STOCK_PRINT, NULL, NULL, N_("Print document"), G_CALLBACK(on_print1_activate) },
81 { "Replace", GTK_STOCK_FIND_AND_REPLACE, NULL, NULL, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate) }
83 const guint ui_entries_n = G_N_ELEMENTS(ui_entries);
86 /* fallback UI definition */
87 const gchar *toolbar_markup =
88 "<ui>"
89 "<toolbar name='GeanyToolbar'>"
90 "<toolitem action='New'/>"
91 "<toolitem action='Open'/>"
92 "<toolitem action='Save'/>"
93 "<toolitem action='SaveAll'/>"
94 "<separator/>"
95 "<toolitem action='Reload'/>"
96 "<toolitem action='Close'/>"
97 "<separator/>"
98 "<toolitem action='NavBack'/>"
99 "<toolitem action='NavFor'/>"
100 "<separator/>"
101 "<toolitem action='Compile'/>"
102 "<toolitem action='Build'/>"
103 "<toolitem action='Run'/>"
104 "<separator/>"
105 "<toolitem action='Color'/>"
106 "<separator/>"
107 "<toolitem action='SearchEntry'/>"
108 "<toolitem action='Search'/>"
109 "<separator/>"
110 "<toolitem action='GotoEntry'/>"
111 "<toolitem action='Goto'/>"
112 "<separator/>"
113 "<toolitem action='Quit'/>"
114 "</toolbar>"
115 "</ui>";
118 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
119 * update the widget pointer in this case (i.e. request it again) or better use
120 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
121 * toolbar is reloaded. */
122 GtkWidget *toolbar_get_widget_by_name(const gchar *name)
124 GtkWidget *widget;
125 gchar *path;
127 g_return_val_if_fail(name != NULL, NULL);
129 path = g_strconcat("/ui/GeanyToolbar/", name, NULL);
130 widget = gtk_ui_manager_get_widget(uim, path);
132 g_free(path);
133 return widget;
137 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
138 * toolbar_get_widget_by_name for details(). */
139 GtkWidget *toolbar_get_widget_child_by_name(const gchar *name)
141 GtkWidget *widget = toolbar_get_widget_by_name(name);
143 if (G_LIKELY(widget != NULL))
144 return gtk_bin_get_child(GTK_BIN(widget));
145 else
146 return NULL;
150 GtkAction *toolbar_get_action_by_name(const gchar *name)
152 g_return_val_if_fail(name != NULL, NULL);
154 return gtk_action_group_get_action(group, name);
158 static void toolbar_item_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data)
160 plugin_items = g_slist_remove(plugin_items, widget);
164 void toolbar_item_ref(GtkToolItem *item)
166 g_return_if_fail(item != NULL);
168 plugin_items = g_slist_append(plugin_items, item);
169 g_signal_connect(item, "destroy", G_CALLBACK(toolbar_item_destroy_cb), NULL);
173 static GtkWidget *toolbar_reload(const gchar *markup)
175 gint i;
176 GSList *l;
177 GtkWidget *entry;
178 GError *error = NULL;
179 const gchar *filename;
180 static guint merge_id = 0;
181 GtkWidget *toolbar_new_file_menu = NULL;
182 GtkWidget *toolbar_recent_files_menu = NULL;
183 GtkWidget *toolbar_build_menu = NULL;
185 /* Cleanup old toolbar */
186 if (merge_id > 0)
188 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
189 foreach_slist(l, plugin_items)
191 g_object_ref(l->data);
192 gtk_container_remove(GTK_CONTAINER(main_widgets.toolbar), GTK_WIDGET(l->data));
194 /* ref and hold the submenus of the New, Open and Build toolbar items */
195 toolbar_new_file_menu = geany_menu_button_action_get_menu(
196 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "New")));
197 g_object_ref(toolbar_new_file_menu);
198 toolbar_recent_files_menu = geany_menu_button_action_get_menu(
199 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Open")));
200 g_object_ref(toolbar_recent_files_menu);
201 toolbar_build_menu = geany_menu_button_action_get_menu(
202 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Build")));
203 g_object_ref(toolbar_build_menu);
205 /* Get rid of it! */
206 gtk_widget_destroy(main_widgets.toolbar);
208 gtk_ui_manager_remove_ui(uim, merge_id);
209 gtk_ui_manager_ensure_update(uim);
212 if (markup != NULL)
214 merge_id = gtk_ui_manager_add_ui_from_string(uim, markup, -1, &error);
216 else
218 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
219 filename = utils_build_path(app->configdir, "ui_toolbar.xml", NULL);
220 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
221 if (merge_id == 0)
223 if (! g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
224 geany_debug("Loading user toolbar UI definition failed (%s).", error->message);
225 g_error_free(error);
226 error = NULL;
228 filename = utils_build_path(app->datadir, "ui_toolbar.xml", NULL);
229 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
232 if (error != NULL)
234 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
235 error->message);
236 g_error_free(error);
237 /* finally load the internally defined markup as fallback */
238 merge_id = gtk_ui_manager_add_ui_from_string(uim, toolbar_markup, -1, NULL);
240 main_widgets.toolbar = gtk_ui_manager_get_widget(uim, "/ui/GeanyToolbar");
241 ui_init_toolbar_widgets();
243 /* add the toolbar again to the main window */
244 if (toolbar_prefs.append_to_menu)
246 GtkWidget *hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
247 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
248 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
250 else
252 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
254 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
255 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
257 gtk_widget_show(main_widgets.toolbar);
259 /* re-add und unref the plugin toolbar items */
260 i = toolbar_get_insert_position();
261 foreach_slist(l, plugin_items)
263 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), l->data, i);
264 g_object_unref(l->data);
265 i++;
267 /* re-add und unref the submenus of menu toolbar items */
268 if (toolbar_new_file_menu != NULL)
270 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
271 gtk_action_group_get_action(group, "New")), toolbar_new_file_menu);
272 g_object_unref(toolbar_new_file_menu);
274 if (toolbar_recent_files_menu != NULL)
276 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
277 gtk_action_group_get_action(group, "Open")), toolbar_recent_files_menu);
278 g_object_unref(toolbar_recent_files_menu);
280 if (toolbar_build_menu != NULL)
282 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
283 gtk_action_group_get_action(group, "Build")), toolbar_build_menu);
284 g_object_unref(toolbar_build_menu);
287 /* update button states */
288 if (main_status.main_window_realized)
290 GeanyDocument *doc = document_get_current();
292 ui_document_buttons_update();
293 ui_save_buttons_toggle(doc->changed); /* update save all */
294 ui_update_popup_reundo_items(doc);
296 toolbar_apply_settings();
299 /* Signals */
300 g_signal_connect(main_widgets.toolbar, "button-press-event",
301 G_CALLBACK(toolbar_popup_menu), NULL);
302 g_signal_connect(main_widgets.toolbar, "key-press-event",
303 G_CALLBACK(on_escape_key_press_event), NULL);
305 /* We don't need to disconnect those signals as this is done automatically when the entry
306 * widgets are destroyed, happens when the toolbar itself is destroyed. */
307 entry = toolbar_get_widget_child_by_name("SearchEntry");
308 if (entry != NULL)
309 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
310 entry = toolbar_get_widget_child_by_name("GotoEntry");
311 if (entry != NULL)
312 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
315 return main_widgets.toolbar;
319 GtkWidget *toolbar_init(void)
321 GtkWidget *toolbar;
322 GtkAction *action_new;
323 GtkAction *action_open;
324 GtkAction *action_build;
325 GtkAction *action_searchentry;
326 GtkAction *action_gotoentry;
328 uim = gtk_ui_manager_new();
329 group = gtk_action_group_new("GeanyToolbar");
331 gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE);
332 gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL);
334 /* Create our custom actions */
335 action_new = geany_menu_button_action_new(
336 "New", NULL,
337 _("Create a new file"),
338 _("Create a new file from a template"),
339 GTK_STOCK_NEW);
340 g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_toolbutton_new_clicked), NULL);
341 gtk_action_group_add_action(group, action_new);
343 action_open = geany_menu_button_action_new(
344 "Open", NULL,
345 _("Open an existing file"),
346 _("Open a recent file"),
347 GTK_STOCK_OPEN);
348 g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_toolbutton_open_clicked), NULL);
349 gtk_action_group_add_action(group, action_open);
351 action_build = geany_menu_button_action_new(
352 "Build", NULL,
353 _("Build the current file"),
354 _("Choose more build actions"),
355 GEANY_STOCK_BUILD);
356 g_signal_connect(action_build, "button-clicked",
357 G_CALLBACK(build_toolbutton_build_clicked), NULL);
358 gtk_action_group_add_action(group, action_build);
360 action_searchentry = geany_entry_action_new(
361 "SearchEntry", _("Search"), _("Find the entered text in the current file"), FALSE);
362 g_signal_connect(action_searchentry, "entry-activate",
363 G_CALLBACK(on_toolbar_search_entry_changed), GINT_TO_POINTER(FALSE));
364 g_signal_connect(action_searchentry, "entry-changed",
365 G_CALLBACK(on_toolbar_search_entry_changed), GINT_TO_POINTER(TRUE));
366 gtk_action_group_add_action(group, action_searchentry);
368 action_gotoentry = geany_entry_action_new(
369 "GotoEntry", _("Goto"), _("Jump to the entered line number"), TRUE);
370 g_signal_connect(action_gotoentry, "entry-activate",
371 G_CALLBACK(on_toolbutton_goto_entry_activate), NULL);
372 gtk_action_group_add_action(group, action_gotoentry);
374 gtk_ui_manager_insert_action_group(uim, group, 0);
376 toolbar = toolbar_reload(NULL);
378 return toolbar;
382 void toolbar_update_ui(void)
384 static GtkWidget *hbox_menubar = NULL;
385 static GtkWidget *menubar = NULL;
386 static GtkWidget *menubar_toolbar_separator = NULL;
387 GtkWidget *parent;
389 if (menubar == NULL)
390 { /* cache widget pointers */
391 hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
392 menubar = ui_lookup_widget(main_widgets.window, "menubar1");
394 menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new());
395 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar),
396 GTK_TOOL_ITEM(menubar_toolbar_separator), 0);
399 parent = gtk_widget_get_parent(main_widgets.toolbar);
401 if (toolbar_prefs.append_to_menu)
403 if (parent != NULL)
405 if (parent != hbox_menubar)
406 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
407 * like to do it */
408 g_object_ref(main_widgets.toolbar);
410 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
411 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
412 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
414 g_object_unref(main_widgets.toolbar);
417 else
418 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
420 else
422 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
424 if (parent != NULL)
426 if (parent != box)
428 g_object_ref(main_widgets.toolbar);
430 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
431 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
432 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
434 g_object_unref(main_widgets.toolbar);
437 else
439 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
440 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
443 /* the separator between the menubar and the toolbar */
444 ui_widget_show_hide(menubar_toolbar_separator, toolbar_prefs.append_to_menu);
445 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
446 * hbox and not expand it if the toolbar is appended */
447 gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar,
448 ! toolbar_prefs.append_to_menu, ! toolbar_prefs.append_to_menu, 0, GTK_PACK_START);
452 /* Returns the position for adding new toolbar items. The returned position can be used
453 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
454 * with @a geany->main_widgets->toolbar.
455 * The position is always the last one before the Quit button or the very last position if the
456 * Quit button is not the last toolbar item.
458 * @return The position for new toolbar items.
460 gint toolbar_get_insert_position(void)
462 GtkWidget *quit = toolbar_get_widget_by_name("Quit");
463 gint quit_pos = -1, pos;
465 if (quit != NULL)
466 quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit));
468 pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar));
469 if (quit_pos == (pos - 1))
471 /* if the toolbar item before the quit button is a separator, insert new items before */
472 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
473 GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1)))
475 return quit_pos - 1;
477 /* else return the position of the quit button to insert new items before */
478 return quit_pos;
481 return pos;
485 void toolbar_finalize(void)
487 g_object_unref(geany_menu_button_action_get_menu(
488 GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"))));
490 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
491 g_object_unref(uim);
492 g_object_unref(group);
494 g_slist_free(plugin_items);
498 void toolbar_show_hide(void)
500 ignore_callback = TRUE;
501 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
502 ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible);
503 ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible);
504 ignore_callback = FALSE;
508 void toolbar_apply_settings(void)
510 /* sets the icon style of the toolbar */
511 switch (toolbar_prefs.icon_style)
513 default:
514 case GTK_TOOLBAR_BOTH:
516 /*gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "images_and_text1")), TRUE);*/
517 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(ui_widgets.toolbar_menu, "images_and_text2")), TRUE);
518 break;
520 case GTK_TOOLBAR_ICONS:
522 /*gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "images_only1")), TRUE);*/
523 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(ui_widgets.toolbar_menu, "images_only2")), TRUE);
524 break;
526 case GTK_TOOLBAR_TEXT:
528 /*gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "text_only1")), TRUE);*/
529 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui_lookup_widget(ui_widgets.toolbar_menu, "text_only2")), TRUE);
530 break;
533 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), toolbar_prefs.icon_style);
535 /* sets the icon size of the toolbar, use user preferences (.gtkrc) if not set */
536 if (toolbar_prefs.icon_size == GTK_ICON_SIZE_SMALL_TOOLBAR ||
537 toolbar_prefs.icon_size == GTK_ICON_SIZE_LARGE_TOOLBAR ||
538 toolbar_prefs.icon_size == GTK_ICON_SIZE_MENU)
540 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), toolbar_prefs.icon_size);
545 #define TB_EDITOR_SEPARATOR _("Separator")
546 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
547 typedef struct
549 GtkWidget *dialog;
551 GtkTreeView *tree_available;
552 GtkTreeView *tree_used;
554 GtkListStore *store_available;
555 GtkListStore *store_used;
557 GtkTreePath *last_drag_path;
558 GtkTreeViewDropPosition last_drag_pos;
560 GtkWidget *drag_source;
561 } TBEditorWidget;
563 static const GtkTargetEntry tb_editor_dnd_targets[] =
565 { "GEANY_TB_EDITOR_ROW", 0, 0 }
567 static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets);
569 enum
571 TB_EDITOR_COL_ACTION,
572 TB_EDITOR_COL_LABEL,
573 TB_EDITOR_COL_ICON,
574 TB_EDITOR_COLS_MAX
577 static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name,
578 const gchar **attribute_names,
579 const gchar **attribute_values, gpointer data,
580 GError **error)
582 gint i;
583 GSList **actions = data;
585 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
586 if (utils_str_equal(element_name, "separator"))
587 *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR));
589 for (i = 0; attribute_names[i] != NULL; i++)
591 if (utils_str_equal(attribute_names[i], "action"))
593 *actions = g_slist_append(*actions, g_strdup(attribute_values[i]));
599 static const GMarkupParser tb_editor_xml_parser =
601 tb_editor_handler_start_element, NULL, NULL, NULL, NULL
605 static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error)
607 GMarkupParseContext *context;
608 GSList *list = NULL;
610 context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL);
611 g_markup_parse_context_parse(context, buffer, length, error);
612 g_markup_parse_context_free(context);
614 return list;
618 static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter)
620 gchar *icon = NULL;
621 gchar *label = NULL;
622 gchar *label_clean = NULL;
623 GtkAction *action;
625 action = gtk_action_group_get_action(group, name);
626 if (action == NULL)
628 if (utils_str_equal(name, TB_EDITOR_SEPARATOR))
629 label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL);
630 else
631 return;
633 else
635 g_object_get(action, "icon-name", &icon, NULL);
636 if (icon == NULL)
637 g_object_get(action, "stock-id", &icon, NULL);
639 g_object_get(action, "label", &label, NULL);
640 if (label != NULL)
641 label_clean = utils_str_remove_chars(g_strdup(label), "_");
644 gtk_list_store_set(store, iter,
645 TB_EDITOR_COL_ACTION, name,
646 TB_EDITOR_COL_LABEL, label_clean,
647 TB_EDITOR_COL_ICON, icon,
648 -1);
650 g_free(icon);
651 g_free(label);
652 g_free(label_clean);
656 static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter)
658 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter);
659 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0);
660 gtk_tree_path_free(path);
664 static void tb_editor_free_path(TBEditorWidget *tbw)
666 if (tbw->last_drag_path != NULL)
668 gtk_tree_path_free(tbw->last_drag_path);
669 tbw->last_drag_path = NULL;
674 static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
676 GtkTreeModel *model_used;
677 GtkTreeSelection *selection_used;
678 GtkTreeIter iter_used, iter_new;
679 gchar *action_name;
681 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
682 if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used))
684 gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1);
685 if (gtk_list_store_remove(tbw->store_used, &iter_used))
686 gtk_tree_selection_select_iter(selection_used, &iter_used);
688 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
690 gtk_list_store_append(tbw->store_available, &iter_new);
691 tb_editor_set_item_values(action_name, tbw->store_available, &iter_new);
692 tb_editor_scroll_to_iter(tbw->tree_available, &iter_new);
695 g_free(action_name);
700 static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
702 GtkTreeModel *model_available;
703 GtkTreeSelection *selection_available, *selection_used;
704 GtkTreeIter iter_available, iter_new, iter_selected;
705 gchar *action_name;
707 selection_available = gtk_tree_view_get_selection(tbw->tree_available);
708 if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available))
710 gtk_tree_model_get(model_available, &iter_available,
711 TB_EDITOR_COL_ACTION, &action_name, -1);
712 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
714 if (gtk_list_store_remove(tbw->store_available, &iter_available))
715 gtk_tree_selection_select_iter(selection_available, &iter_available);
718 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
719 if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected))
720 gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected);
721 else
722 gtk_list_store_append(tbw->store_used, &iter_new);
724 tb_editor_set_item_values(action_name, tbw->store_used, &iter_new);
725 tb_editor_scroll_to_iter(tbw->tree_used, &iter_new);
727 g_free(action_name);
732 static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context,
733 gint x, gint y, guint ltime, TBEditorWidget *tbw)
735 if (tbw->last_drag_path != NULL)
736 gtk_tree_path_free(tbw->last_drag_path);
737 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget),
738 &(tbw->last_drag_path), &(tbw->last_drag_pos));
740 return FALSE;
744 static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
745 GtkSelectionData *data, guint info, guint ltime,
746 TBEditorWidget *tbw)
748 GtkTreeIter iter;
749 GtkTreeSelection *selection;
750 GtkTreeModel *model;
751 GdkAtom atom;
752 gchar *name;
754 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
755 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
756 return;
758 gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1);
759 if (! NZV(name))
760 return;
762 atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE);
763 gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name));
765 g_free(name);
767 tbw->drag_source = widget;
771 static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context,
772 gint x, gint y, GtkSelectionData *data, guint info,
773 guint ltime, TBEditorWidget *tbw)
775 GtkTreeView *tree = GTK_TREE_VIEW(widget);
776 gboolean del = FALSE;
778 if (data->length >= 0 && data->format == 8)
780 gboolean is_sep;
781 gchar *text = NULL;
783 text = (gchar*) data->data;
784 is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR);
785 /* If the source of the action is equal to the target, we do just re-order and so need
786 * to delete the separator to get it moved, not just copied. */
787 if (is_sep && widget == tbw->drag_source)
788 is_sep = FALSE;
790 if (tree != tbw->tree_available || ! is_sep)
792 GtkTreeIter iter, iter_before, *iter_before_ptr;
793 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
795 if (tbw->last_drag_path != NULL)
797 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path);
799 if (gtk_list_store_iter_is_valid(store, &iter_before))
800 iter_before_ptr = &iter_before;
801 else
802 iter_before_ptr = NULL;
804 if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE ||
805 tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
806 gtk_list_store_insert_before(store, &iter, iter_before_ptr);
807 else
808 gtk_list_store_insert_after(store, &iter, iter_before_ptr);
810 else
811 gtk_list_store_append(store, &iter);
813 tb_editor_set_item_values(text, store, &iter);
814 tb_editor_scroll_to_iter(tree, &iter);
816 if (tree != tbw->tree_used || ! is_sep)
817 del = TRUE;
820 tbw->drag_source = NULL; /* reset the value just to be sure */
821 tb_editor_free_path(tbw);
822 gtk_drag_finish(context, TRUE, del, ltime);
826 static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path,
827 GtkTreeIter *iter, gpointer data)
829 gchar *action_name;
831 gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1);
833 if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
834 g_string_append_printf(data, "\t\t<separator/>\n");
835 else if (NZV(action_name))
836 g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name);
838 g_free(action_name);
839 return FALSE;
843 static void tb_editor_write_markup(TBEditorWidget *tbw)
845 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
846 const gchar *template = "<ui>\n<!--\n\
847 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
848 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
849 You can re-order all items and freely add and remove available actions.\n\
850 You cannot add new actions which are not listed in the documentation.\n\
851 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
852 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
853 editor in Geany.\n\n\
854 A list of available actions can be found in the documentation included with Geany or\n\
855 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
856 \t<toolbar name='GeanyToolbar'>\n";
857 const gchar *filename = utils_build_path(app->configdir, "ui_toolbar.xml", NULL);
858 GString *str = g_string_new(template);
860 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str);
862 g_string_append(str, "\n\t</toolbar>\n</ui>\n");
864 toolbar_reload(str->str);
866 utils_write_file(filename, str->str);
868 g_string_free(str, TRUE);
872 static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1,
873 GtkTreeIter *arg2, TBEditorWidget *tbw)
875 tb_editor_write_markup(tbw);
879 static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1,
880 TBEditorWidget *tbw)
882 tb_editor_write_markup(tbw);
886 static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent)
888 GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove;
889 GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label;
890 GtkCellRenderer *text_renderer, *icon_renderer;
891 GtkTreeViewColumn *column;
892 TBEditorWidget *tbw = g_new(TBEditorWidget, 1);
894 if (parent == NULL)
895 parent = GTK_WINDOW(main_widgets.window);
897 dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"),
898 parent,
899 GTK_DIALOG_DESTROY_WITH_PARENT,
900 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
901 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
902 gtk_box_set_spacing(GTK_BOX(vbox), 6);
903 gtk_widget_set_name(dialog, "GeanyDialog");
904 gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400);
905 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
907 tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX,
908 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
909 tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX,
910 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
912 label = gtk_label_new(
913 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
914 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
916 tree_available = gtk_tree_view_new();
917 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available));
918 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE);
919 gtk_tree_sortable_set_sort_column_id(
920 GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING);
922 icon_renderer = gtk_cell_renderer_pixbuf_new();
923 column = gtk_tree_view_column_new_with_attributes(
924 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
925 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
927 text_renderer = gtk_cell_renderer_text_new();
928 column = gtk_tree_view_column_new_with_attributes(
929 _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
930 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
932 swin_available = gtk_scrolled_window_new(NULL, NULL);
933 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available),
934 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
935 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_ETCHED_IN);
936 gtk_container_add(GTK_CONTAINER(swin_available), tree_available);
938 tree_used = gtk_tree_view_new();
939 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used));
940 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE);
941 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE);
943 icon_renderer = gtk_cell_renderer_pixbuf_new();
944 column = gtk_tree_view_column_new_with_attributes(
945 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
946 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
948 text_renderer = gtk_cell_renderer_text_new();
949 column = gtk_tree_view_column_new_with_attributes(
950 _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
951 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
953 swin_used = gtk_scrolled_window_new(NULL, NULL);
954 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used),
955 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
956 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_ETCHED_IN);
957 gtk_container_add(GTK_CONTAINER(swin_used), tree_used);
959 /* drag'n'drop */
960 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK,
961 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
962 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available),
963 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
964 g_signal_connect(tree_available, "drag-data-get",
965 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
966 g_signal_connect(tree_available, "drag-data-received",
967 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
968 g_signal_connect(tree_available, "drag-motion",
969 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
971 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK,
972 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
973 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used),
974 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
975 g_signal_connect(tree_used, "drag-data-get",
976 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
977 g_signal_connect(tree_used, "drag-data-received",
978 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
979 g_signal_connect(tree_used, "drag-motion",
980 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
983 button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL);
984 button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL);
985 g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw);
986 g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw);
988 vbox_buttons = gtk_vbox_new(FALSE, 6);
989 /* FIXME this is a little hack'ish, any better ideas? */
990 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
991 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0);
992 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0);
993 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
995 hbox = gtk_hbox_new(FALSE, 6);
996 gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0);
997 gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
998 gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0);
1000 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
1001 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1003 gtk_widget_show_all(vbox);
1005 g_object_unref(tbw->store_available);
1006 g_object_unref(tbw->store_used);
1008 tbw->dialog = dialog;
1009 tbw->tree_available = GTK_TREE_VIEW(tree_available);
1010 tbw->tree_used = GTK_TREE_VIEW(tree_used);
1012 tbw->last_drag_path = NULL;
1014 return tbw;
1018 void toolbar_configure(GtkWindow *parent)
1020 gchar *markup;
1021 const gchar *name;
1022 GSList *sl, *used_items;
1023 GList *l, *all_items;
1024 GtkTreeIter iter;
1025 GtkTreePath *path;
1026 TBEditorWidget *tbw;
1028 /* read the current active toolbar items */
1029 markup = gtk_ui_manager_get_ui(uim);
1030 used_items = tb_editor_parse_ui(markup, strlen(markup), NULL);
1031 g_free(markup);
1033 /* get all available actions */
1034 all_items = gtk_action_group_list_actions(group);
1036 /* create the GUI */
1037 tbw = tb_editor_create_dialog(parent);
1039 /* fill the stores */
1040 gtk_list_store_insert_with_values(tbw->store_available, NULL, -1,
1041 TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR,
1042 TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL,
1043 -1);
1044 foreach_list(l, all_items)
1046 name = gtk_action_get_name(l->data);
1047 if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL)
1049 gtk_list_store_append(tbw->store_available, &iter);
1050 tb_editor_set_item_values(name, tbw->store_available, &iter);
1053 foreach_slist(sl, used_items)
1055 gtk_list_store_append(tbw->store_used, &iter);
1056 tb_editor_set_item_values(sl->data, tbw->store_used, &iter);
1058 /* select first item */
1059 path = gtk_tree_path_new_from_string("0");
1060 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path);
1061 gtk_tree_path_free(path);
1063 /* connect the changed signals after populating the store */
1064 g_signal_connect(tbw->store_used, "row-changed",
1065 G_CALLBACK(tb_editor_available_items_changed_cb), tbw);
1066 g_signal_connect(tbw->store_used, "row-deleted",
1067 G_CALLBACK(tb_editor_available_items_deleted_cb), tbw);
1069 /* run it */
1070 gtk_dialog_run(GTK_DIALOG(tbw->dialog));
1072 gtk_widget_destroy(tbw->dialog);
1074 g_slist_foreach(used_items, (GFunc) g_free, NULL);
1075 g_slist_free(used_items);
1076 g_list_free(all_items);
1077 tb_editor_free_path(tbw);
1078 g_free(tbw);