Update of German translation
[geany-mirror.git] / src / toolbar.c
blob1501e1bc03323ae085b7994f589f45bf61773159
1 /*
2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009 The Geany contributors
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 of the License, or
9 * (at your option) any later version.
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.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 /**
22 * @file toolbar.h
23 * Toolbar (prefs).
25 /* Utility functions to create the toolbar */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include "toolbar.h"
33 #include "app.h"
34 #include "build.h"
35 #include "callbacks.h"
36 #include "document.h"
37 #include "geanyentryaction.h"
38 #include "geanymenubuttonaction.h"
39 #include "main.h"
40 #include "support.h"
41 #include "ui_utils.h"
42 #include "utils.h"
44 #include <string.h>
45 #include <glib/gstdio.h>
48 GeanyToolbarPrefs toolbar_prefs;
49 static GtkUIManager *uim;
50 static GtkActionGroup *group;
51 static GSList *plugin_items = NULL;
53 /* Available toolbar actions
54 * Fields: name, stock_id, label, accelerator, tooltip, callback */
55 static const GtkActionEntry ui_entries[] = {
56 /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */
57 { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Save the current file"), G_CALLBACK(on_save1_activate) },
58 { "SaveAs", GTK_STOCK_SAVE_AS, NULL, NULL, N_("Save as"), G_CALLBACK(on_save_as1_activate) },
59 { "SaveAll", GEANY_STOCK_SAVE_ALL, NULL, NULL, N_("Save all open files"), G_CALLBACK(on_save_all1_activate) },
60 { "Reload", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked) },
61 { "Close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current file"), G_CALLBACK(on_close1_activate) },
62 { "CloseAll", GEANY_STOCK_CLOSE_ALL, NULL, NULL, N_("Close all open files"), G_CALLBACK(on_close_all1_activate) },
63 { "Cut", GTK_STOCK_CUT, NULL, NULL, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate) },
64 { "Copy", GTK_STOCK_COPY, NULL, NULL, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate) },
65 { "Paste", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate) },
66 { "Delete", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate) },
67 { "Undo", GTK_STOCK_UNDO, NULL, NULL, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate) },
68 { "Redo", GTK_STOCK_REDO, NULL, NULL, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate) },
69 { "NavBack", GTK_STOCK_GO_BACK, NULL, NULL, N_("Navigate back a location"), G_CALLBACK(on_toolbutton_back_activate) },
70 { "NavFor", GTK_STOCK_GO_FORWARD, NULL, NULL, N_("Navigate forward a location"), G_CALLBACK(on_toolbutton_forward_activate) },
71 { "Compile", GTK_STOCK_CONVERT, N_("Compile"), NULL, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked) },
72 { "Run", GTK_STOCK_EXECUTE, NULL, NULL, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked) },
73 { "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) },
74 { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, NULL, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate) },
75 { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, NULL, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate) },
76 { "UnIndent", GTK_STOCK_UNINDENT, NULL, NULL, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate) },
77 { "Indent", GTK_STOCK_INDENT, NULL, NULL, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate) },
78 { "Search", GTK_STOCK_FIND, NULL, NULL, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked) },
79 { "Goto", GTK_STOCK_JUMP_TO, NULL, NULL, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked) },
80 { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, N_("Show the preferences dialog"), G_CALLBACK(on_preferences1_activate) },
81 { "Quit", GTK_STOCK_QUIT, NULL, NULL, N_("Quit Geany"), G_CALLBACK(on_quit1_activate) },
82 { "Print", GTK_STOCK_PRINT, NULL, NULL, N_("Print document"), G_CALLBACK(on_print1_activate) },
83 { "Replace", GTK_STOCK_FIND_AND_REPLACE, NULL, NULL, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate) }
85 static const guint ui_entries_n = G_N_ELEMENTS(ui_entries);
88 /* fallback UI definition */
89 static const gchar *toolbar_markup =
90 "<ui>"
91 "<toolbar name='GeanyToolbar'>"
92 "<toolitem action='New'/>"
93 "<toolitem action='Open'/>"
94 "<toolitem action='Save'/>"
95 "<toolitem action='SaveAll'/>"
96 "<separator/>"
97 "<toolitem action='Reload'/>"
98 "<toolitem action='Close'/>"
99 "<separator/>"
100 "<toolitem action='NavBack'/>"
101 "<toolitem action='NavFor'/>"
102 "<separator/>"
103 "<toolitem action='Compile'/>"
104 "<toolitem action='Build'/>"
105 "<toolitem action='Run'/>"
106 "<separator/>"
107 "<toolitem action='Color'/>"
108 "<separator/>"
109 "<toolitem action='SearchEntry'/>"
110 "<toolitem action='Search'/>"
111 "<separator/>"
112 "<toolitem action='GotoEntry'/>"
113 "<toolitem action='Goto'/>"
114 "<separator/>"
115 "<toolitem action='Quit'/>"
116 "</toolbar>"
117 "</ui>";
120 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
121 * update the widget pointer in this case (i.e. request it again) or better use
122 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
123 * toolbar is reloaded. */
124 GtkWidget *toolbar_get_widget_by_name(const gchar *name)
126 GtkWidget *widget;
127 gchar *path;
129 g_return_val_if_fail(name != NULL, NULL);
131 path = g_strconcat("/ui/GeanyToolbar/", name, NULL);
132 widget = gtk_ui_manager_get_widget(uim, path);
134 g_free(path);
135 return widget;
139 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
140 * toolbar_get_widget_by_name for details(). */
141 GtkWidget *toolbar_get_widget_child_by_name(const gchar *name)
143 GtkWidget *widget = toolbar_get_widget_by_name(name);
145 if (G_LIKELY(widget != NULL))
146 return gtk_bin_get_child(GTK_BIN(widget));
147 else
148 return NULL;
152 GtkAction *toolbar_get_action_by_name(const gchar *name)
154 g_return_val_if_fail(name != NULL, NULL);
156 return gtk_action_group_get_action(group, name);
160 static void toolbar_item_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data)
162 plugin_items = g_slist_remove(plugin_items, widget);
166 void toolbar_item_ref(GtkToolItem *item)
168 g_return_if_fail(item != NULL);
170 plugin_items = g_slist_append(plugin_items, item);
171 g_signal_connect(item, "destroy", G_CALLBACK(toolbar_item_destroy_cb), NULL);
175 static GtkWidget *toolbar_reload(const gchar *markup)
177 gint i;
178 GSList *l;
179 GtkWidget *entry;
180 GError *error = NULL;
181 gchar *filename;
182 static guint merge_id = 0;
183 GtkWidget *toolbar_new_file_menu = NULL;
184 GtkWidget *toolbar_recent_files_menu = NULL;
185 GtkWidget *toolbar_build_menu = NULL;
187 /* Cleanup old toolbar */
188 if (merge_id > 0)
190 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
191 foreach_slist(l, plugin_items)
193 g_object_ref(l->data);
194 gtk_container_remove(GTK_CONTAINER(main_widgets.toolbar), GTK_WIDGET(l->data));
196 /* ref and hold the submenus of the New, Open and Build toolbar items */
197 toolbar_new_file_menu = geany_menu_button_action_get_menu(
198 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "New")));
199 g_object_ref(toolbar_new_file_menu);
200 toolbar_recent_files_menu = geany_menu_button_action_get_menu(
201 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Open")));
202 g_object_ref(toolbar_recent_files_menu);
203 toolbar_build_menu = geany_menu_button_action_get_menu(
204 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Build")));
205 g_object_ref(toolbar_build_menu);
207 /* Get rid of it! */
208 gtk_widget_destroy(main_widgets.toolbar);
210 gtk_ui_manager_remove_ui(uim, merge_id);
211 gtk_ui_manager_ensure_update(uim);
214 if (markup != NULL)
216 merge_id = gtk_ui_manager_add_ui_from_string(uim, markup, -1, &error);
218 else
220 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
221 filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL);
222 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
223 if (merge_id == 0)
225 if (! g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
226 geany_debug("Loading user toolbar UI definition failed (%s).", error->message);
227 g_error_free(error);
228 error = NULL;
230 SETPTR(filename, g_build_filename(app->datadir, "ui_toolbar.xml", NULL));
231 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
233 g_free(filename);
235 if (error != NULL)
237 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
238 error->message);
239 g_error_free(error);
240 /* finally load the internally defined markup as fallback */
241 merge_id = gtk_ui_manager_add_ui_from_string(uim, toolbar_markup, -1, NULL);
243 main_widgets.toolbar = gtk_ui_manager_get_widget(uim, "/ui/GeanyToolbar");
244 ui_init_toolbar_widgets();
246 /* add the toolbar again to the main window */
247 if (toolbar_prefs.append_to_menu)
249 GtkWidget *hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
250 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
251 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
253 else
255 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
257 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
258 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
260 gtk_widget_show(main_widgets.toolbar);
262 /* re-add und unref the plugin toolbar items */
263 i = toolbar_get_insert_position();
264 foreach_slist(l, plugin_items)
266 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), l->data, i);
267 g_object_unref(l->data);
268 i++;
270 /* re-add und unref the submenus of menu toolbar items */
271 if (toolbar_new_file_menu != NULL)
273 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
274 gtk_action_group_get_action(group, "New")), toolbar_new_file_menu);
275 g_object_unref(toolbar_new_file_menu);
277 if (toolbar_recent_files_menu != NULL)
279 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
280 gtk_action_group_get_action(group, "Open")), toolbar_recent_files_menu);
281 g_object_unref(toolbar_recent_files_menu);
283 if (toolbar_build_menu != NULL)
285 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
286 gtk_action_group_get_action(group, "Build")), toolbar_build_menu);
287 g_object_unref(toolbar_build_menu);
290 /* update button states */
291 if (main_status.main_window_realized)
293 GeanyDocument *doc = document_get_current();
294 gboolean doc_changed = (doc != NULL) ? doc->changed : FALSE;
296 ui_document_buttons_update();
297 ui_save_buttons_toggle(doc_changed); /* update save all */
298 ui_update_popup_reundo_items(doc);
300 toolbar_apply_settings();
303 /* Signals */
304 g_signal_connect(main_widgets.toolbar, "button-press-event",
305 G_CALLBACK(toolbar_popup_menu), NULL);
306 g_signal_connect(main_widgets.toolbar, "key-press-event",
307 G_CALLBACK(on_escape_key_press_event), NULL);
309 /* We don't need to disconnect those signals as this is done automatically when the entry
310 * widgets are destroyed, happens when the toolbar itself is destroyed. */
311 entry = toolbar_get_widget_child_by_name("SearchEntry");
312 if (entry != NULL)
313 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
314 entry = toolbar_get_widget_child_by_name("GotoEntry");
315 if (entry != NULL)
316 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
318 return main_widgets.toolbar;
322 static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data)
324 const gchar *arg_name = g_param_spec_get_name(arg1);
325 gint value;
327 if (toolbar_prefs.use_gtk_default_style && utils_str_equal(arg_name, "gtk-toolbar-style"))
329 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_style);
330 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), value);
332 else if (toolbar_prefs.use_gtk_default_icon && utils_str_equal(arg_name, "gtk-toolbar-size"))
334 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_size);
335 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), value);
340 GtkWidget *toolbar_init(void)
342 GtkWidget *toolbar;
343 GtkAction *action_new;
344 GtkAction *action_open;
345 GtkAction *action_build;
346 GtkAction *action_searchentry;
347 GtkAction *action_gotoentry;
348 GtkSettings *gtk_settings;
350 uim = gtk_ui_manager_new();
351 group = gtk_action_group_new("GeanyToolbar");
353 gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE);
354 gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL);
356 /* Create our custom actions */
357 action_new = geany_menu_button_action_new(
358 "New", NULL,
359 _("Create a new file"),
360 _("Create a new file from a template"),
361 GTK_STOCK_NEW);
362 g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_new1_activate), NULL);
363 gtk_action_group_add_action(group, action_new);
365 action_open = geany_menu_button_action_new(
366 "Open", NULL,
367 _("Open an existing file"),
368 _("Open a recent file"),
369 GTK_STOCK_OPEN);
370 g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_open1_activate), NULL);
371 gtk_action_group_add_action(group, action_open);
373 action_build = geany_menu_button_action_new(
374 "Build", NULL,
375 _("Build the current file"),
376 _("Choose more build actions"),
377 GEANY_STOCK_BUILD);
378 g_signal_connect(action_build, "button-clicked",
379 G_CALLBACK(build_toolbutton_build_clicked), NULL);
380 gtk_action_group_add_action(group, action_build);
382 action_searchentry = geany_entry_action_new(
383 "SearchEntry", _("Search Field"), _("Find the entered text in the current file"), FALSE);
384 g_signal_connect(action_searchentry, "entry-activate",
385 G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(FALSE));
386 g_signal_connect(action_searchentry, "entry-activate-backward",
387 G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(TRUE));
388 g_signal_connect(action_searchentry, "entry-changed",
389 G_CALLBACK(on_toolbar_search_entry_changed), NULL);
390 gtk_action_group_add_action(group, action_searchentry);
392 action_gotoentry = geany_entry_action_new(
393 "GotoEntry", _("Goto Field"), _("Jump to the entered line number"), TRUE);
394 g_signal_connect(action_gotoentry, "entry-activate",
395 G_CALLBACK(on_toolbutton_goto_entry_activate), NULL);
396 gtk_action_group_add_action(group, action_gotoentry);
398 gtk_ui_manager_insert_action_group(uim, group, 0);
400 toolbar = toolbar_reload(NULL);
401 #if GTK_CHECK_VERSION(3, 0, 0)
402 gtk_style_context_add_class(gtk_widget_get_style_context(toolbar), "primary-toolbar");
403 #endif
405 gtk_settings = gtk_widget_get_settings(GTK_WIDGET(toolbar));
406 if (gtk_settings != NULL)
408 g_signal_connect(gtk_settings, "notify::gtk-toolbar-style",
409 G_CALLBACK(toolbar_notify_style_cb), NULL);
412 return toolbar;
416 void toolbar_update_ui(void)
418 static GtkWidget *hbox_menubar = NULL;
419 static GtkWidget *menubar = NULL;
420 GtkWidget *parent;
421 GtkToolItem *first_item;
423 if (menubar == NULL)
424 { /* cache widget pointers */
425 hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
426 menubar = ui_lookup_widget(main_widgets.window, "menubar1");
428 /* the separator between the menubar and the toolbar */
429 first_item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets.toolbar), 0);
430 if (first_item != NULL && GTK_IS_SEPARATOR_TOOL_ITEM(first_item))
432 gtk_widget_destroy(GTK_WIDGET(first_item));
435 parent = gtk_widget_get_parent(main_widgets.toolbar);
437 if (toolbar_prefs.append_to_menu)
439 GtkWidget *menubar_toolbar_separator;
441 if (parent != NULL)
443 if (parent != hbox_menubar)
444 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
445 * like to do it */
446 g_object_ref(main_widgets.toolbar);
448 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
449 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
450 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
452 g_object_unref(main_widgets.toolbar);
455 else
456 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
458 /* the separator between the menubar and the toolbar */
459 menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new());
460 gtk_widget_show(menubar_toolbar_separator);
461 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar),
462 GTK_TOOL_ITEM(menubar_toolbar_separator), 0);
464 else
466 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
468 if (parent != NULL)
470 if (parent != box)
472 g_object_ref(main_widgets.toolbar);
474 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
475 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
476 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
478 g_object_unref(main_widgets.toolbar);
481 else
483 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
484 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
487 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
488 * hbox and not expand it if the toolbar is appended */
489 gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar,
490 ! (toolbar_prefs.visible && toolbar_prefs.append_to_menu), TRUE, 0, GTK_PACK_START);
494 /* Returns the position for adding new toolbar items. The returned position can be used
495 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
496 * with @a geany->main_widgets->toolbar.
497 * The position is always the last one before the Quit button or the very last position if the
498 * Quit button is not the last toolbar item.
500 * @return The position for new toolbar items.
502 gint toolbar_get_insert_position(void)
504 GtkWidget *quit = toolbar_get_widget_by_name("Quit");
505 gint quit_pos = -1, pos;
507 if (quit != NULL)
508 quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit));
510 pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar));
511 if (quit_pos == (pos - 1))
513 /* if the toolbar item before the quit button is a separator, insert new items before */
514 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
515 GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1)))
517 return quit_pos - 1;
519 /* else return the position of the quit button to insert new items before */
520 return quit_pos;
523 return pos;
527 void toolbar_finalize(void)
529 GeanyMenubuttonAction *open_action = GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"));
530 g_object_unref(geany_menu_button_action_get_menu(open_action));
531 geany_menu_button_action_set_menu(open_action, NULL);
533 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
534 g_object_unref(uim);
535 g_object_unref(group);
537 g_slist_free(plugin_items);
541 void toolbar_show_hide(void)
543 ignore_callback = TRUE;
544 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
545 ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible);
546 ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible);
547 ignore_callback = FALSE;
551 /* sets the icon style of the toolbar */
552 static void toolbar_set_icon_style(void)
554 gint icon_style;
556 icon_style = toolbar_prefs.icon_style;
558 if (toolbar_prefs.use_gtk_default_style)
559 icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs.icon_style);
561 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), icon_style);
565 /* sets the icon size of the toolbar */
566 static void toolbar_set_icon_size(void)
568 gint icon_size;
570 icon_size = toolbar_prefs.icon_size;
572 if (toolbar_prefs.use_gtk_default_icon)
573 icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs.icon_size);
575 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), icon_size);
579 void toolbar_apply_settings(void)
581 toolbar_set_icon_style();
582 toolbar_set_icon_size();
586 #define TB_EDITOR_SEPARATOR _("Separator")
587 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
588 typedef struct
590 GtkWidget *dialog;
592 GtkTreeView *tree_available;
593 GtkTreeView *tree_used;
595 GtkListStore *store_available;
596 GtkListStore *store_used;
598 GtkTreePath *last_drag_path;
599 GtkTreeViewDropPosition last_drag_pos;
601 GtkWidget *drag_source;
602 } TBEditorWidget;
604 static const GtkTargetEntry tb_editor_dnd_targets[] =
606 { "GEANY_TB_EDITOR_ROW", 0, 0 }
608 static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets);
610 enum
612 TB_EDITOR_COL_ACTION,
613 TB_EDITOR_COL_LABEL,
614 TB_EDITOR_COL_ICON,
615 TB_EDITOR_COLS_MAX
618 static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name,
619 const gchar **attribute_names,
620 const gchar **attribute_values, gpointer data,
621 GError **error)
623 gint i;
624 GSList **actions = data;
626 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
627 if (utils_str_equal(element_name, "separator"))
628 *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR));
630 for (i = 0; attribute_names[i] != NULL; i++)
632 if (utils_str_equal(attribute_names[i], "action"))
634 *actions = g_slist_append(*actions, g_strdup(attribute_values[i]));
640 static const GMarkupParser tb_editor_xml_parser =
642 tb_editor_handler_start_element, NULL, NULL, NULL, NULL
646 static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error)
648 GMarkupParseContext *context;
649 GSList *list = NULL;
651 context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL);
652 g_markup_parse_context_parse(context, buffer, length, error);
653 g_markup_parse_context_free(context);
655 return list;
659 static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter)
661 gchar *icon = NULL;
662 gchar *label = NULL;
663 gchar *label_clean = NULL;
664 GtkAction *action;
666 action = gtk_action_group_get_action(group, name);
667 if (action == NULL)
669 if (utils_str_equal(name, TB_EDITOR_SEPARATOR))
670 label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL);
671 else
672 return;
674 else
676 g_object_get(action, "icon-name", &icon, NULL);
677 if (icon == NULL)
678 g_object_get(action, "stock-id", &icon, NULL);
680 g_object_get(action, "label", &label, NULL);
681 if (label != NULL)
682 label_clean = utils_str_remove_chars(g_strdup(label), "_");
685 gtk_list_store_set(store, iter,
686 TB_EDITOR_COL_ACTION, name,
687 TB_EDITOR_COL_LABEL, label_clean,
688 TB_EDITOR_COL_ICON, icon,
689 -1);
691 g_free(icon);
692 g_free(label);
693 g_free(label_clean);
697 static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter)
699 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter);
700 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0);
701 gtk_tree_path_free(path);
705 static void tb_editor_free_path(TBEditorWidget *tbw)
707 if (tbw->last_drag_path != NULL)
709 gtk_tree_path_free(tbw->last_drag_path);
710 tbw->last_drag_path = NULL;
715 static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
717 GtkTreeModel *model_used;
718 GtkTreeSelection *selection_used;
719 GtkTreeIter iter_used, iter_new;
720 gchar *action_name;
722 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
723 if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used))
725 gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1);
726 if (gtk_list_store_remove(tbw->store_used, &iter_used))
727 gtk_tree_selection_select_iter(selection_used, &iter_used);
729 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
731 gtk_list_store_append(tbw->store_available, &iter_new);
732 tb_editor_set_item_values(action_name, tbw->store_available, &iter_new);
733 tb_editor_scroll_to_iter(tbw->tree_available, &iter_new);
736 g_free(action_name);
741 static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
743 GtkTreeModel *model_available;
744 GtkTreeSelection *selection_available, *selection_used;
745 GtkTreeIter iter_available, iter_new, iter_selected;
746 gchar *action_name;
748 selection_available = gtk_tree_view_get_selection(tbw->tree_available);
749 if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available))
751 gtk_tree_model_get(model_available, &iter_available,
752 TB_EDITOR_COL_ACTION, &action_name, -1);
753 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
755 if (gtk_list_store_remove(tbw->store_available, &iter_available))
756 gtk_tree_selection_select_iter(selection_available, &iter_available);
759 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
760 if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected))
761 gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected);
762 else
763 gtk_list_store_append(tbw->store_used, &iter_new);
765 tb_editor_set_item_values(action_name, tbw->store_used, &iter_new);
766 tb_editor_scroll_to_iter(tbw->tree_used, &iter_new);
768 g_free(action_name);
773 static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context,
774 gint x, gint y, guint ltime, TBEditorWidget *tbw)
776 if (tbw->last_drag_path != NULL)
777 gtk_tree_path_free(tbw->last_drag_path);
778 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget),
779 &(tbw->last_drag_path), &(tbw->last_drag_pos));
781 return FALSE;
785 static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
786 GtkSelectionData *data, guint info, guint ltime,
787 TBEditorWidget *tbw)
789 GtkTreeIter iter;
790 GtkTreeSelection *selection;
791 GtkTreeModel *model;
792 GdkAtom atom;
793 gchar *name;
795 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
796 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
797 return;
799 gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1);
800 if (G_UNLIKELY(EMPTY(name)))
802 g_free(name);
803 return;
806 atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE);
807 gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name));
809 g_free(name);
811 tbw->drag_source = widget;
815 static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context,
816 gint x, gint y, GtkSelectionData *data, guint info,
817 guint ltime, TBEditorWidget *tbw)
819 GtkTreeView *tree = GTK_TREE_VIEW(widget);
820 gboolean del = FALSE;
822 if (gtk_selection_data_get_length(data) >= 0 && gtk_selection_data_get_format(data) == 8)
824 gboolean is_sep;
825 gchar *text = NULL;
827 text = (gchar*) gtk_selection_data_get_data(data);
828 is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR);
829 /* If the source of the action is equal to the target, we do just re-order and so need
830 * to delete the separator to get it moved, not just copied. */
831 if (is_sep && widget == tbw->drag_source)
832 is_sep = FALSE;
834 if (tree != tbw->tree_available || ! is_sep)
836 GtkTreeIter iter, iter_before, *iter_before_ptr;
837 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
839 if (tbw->last_drag_path != NULL)
841 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path);
843 if (gtk_list_store_iter_is_valid(store, &iter_before))
844 iter_before_ptr = &iter_before;
845 else
846 iter_before_ptr = NULL;
848 if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE ||
849 tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
850 gtk_list_store_insert_before(store, &iter, iter_before_ptr);
851 else
852 gtk_list_store_insert_after(store, &iter, iter_before_ptr);
854 else
855 gtk_list_store_append(store, &iter);
857 tb_editor_set_item_values(text, store, &iter);
858 tb_editor_scroll_to_iter(tree, &iter);
860 if (tree != tbw->tree_used || ! is_sep)
861 del = TRUE;
864 tbw->drag_source = NULL; /* reset the value just to be sure */
865 tb_editor_free_path(tbw);
866 gtk_drag_finish(context, TRUE, del, ltime);
870 static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path,
871 GtkTreeIter *iter, gpointer data)
873 gchar *action_name;
875 gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1);
877 if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
878 g_string_append_printf(data, "\t\t<separator/>\n");
879 else if (G_LIKELY(!EMPTY(action_name)))
880 g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name);
882 g_free(action_name);
883 return FALSE;
887 static void tb_editor_write_markup(TBEditorWidget *tbw)
889 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
890 const gchar *template = "<ui>\n<!--\n\
891 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
892 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
893 You can re-order all items and freely add and remove available actions.\n\
894 You cannot add new actions which are not listed in the documentation.\n\
895 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
896 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
897 editor in Geany.\n\n\
898 A list of available actions can be found in the documentation included with Geany or\n\
899 at https://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
900 \t<toolbar name='GeanyToolbar'>\n";
901 gchar *filename;
902 GString *str = g_string_new(template);
904 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str);
906 g_string_append(str, "\n\t</toolbar>\n</ui>\n");
908 toolbar_reload(str->str);
910 filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL);
911 utils_write_file(filename, str->str);
912 g_free(filename);
914 g_string_free(str, TRUE);
918 static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1,
919 GtkTreeIter *arg2, TBEditorWidget *tbw)
921 tb_editor_write_markup(tbw);
925 static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1,
926 TBEditorWidget *tbw)
928 tb_editor_write_markup(tbw);
932 static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent)
934 GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove;
935 GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label;
936 GtkCellRenderer *text_renderer, *icon_renderer;
937 GtkTreeViewColumn *column;
938 TBEditorWidget *tbw = g_new(TBEditorWidget, 1);
940 if (parent == NULL)
941 parent = GTK_WINDOW(main_widgets.window);
943 dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"),
944 parent,
945 GTK_DIALOG_DESTROY_WITH_PARENT,
946 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
947 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
948 gtk_box_set_spacing(GTK_BOX(vbox), 6);
949 gtk_widget_set_name(dialog, "GeanyDialog");
950 gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400);
951 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
953 tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX,
954 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
955 tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX,
956 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
958 label = gtk_label_new(
959 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
960 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
962 tree_available = gtk_tree_view_new();
963 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available));
964 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE);
965 gtk_tree_sortable_set_sort_column_id(
966 GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING);
968 icon_renderer = gtk_cell_renderer_pixbuf_new();
969 column = gtk_tree_view_column_new_with_attributes(
970 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
971 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
973 text_renderer = gtk_cell_renderer_text_new();
974 column = gtk_tree_view_column_new_with_attributes(
975 _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
976 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
978 swin_available = gtk_scrolled_window_new(NULL, NULL);
979 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available),
980 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
981 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_IN);
982 gtk_container_add(GTK_CONTAINER(swin_available), tree_available);
984 tree_used = gtk_tree_view_new();
985 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used));
986 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE);
987 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE);
989 icon_renderer = gtk_cell_renderer_pixbuf_new();
990 column = gtk_tree_view_column_new_with_attributes(
991 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
992 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
994 text_renderer = gtk_cell_renderer_text_new();
995 column = gtk_tree_view_column_new_with_attributes(
996 _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
997 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
999 swin_used = gtk_scrolled_window_new(NULL, NULL);
1000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used),
1001 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1002 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_IN);
1003 gtk_container_add(GTK_CONTAINER(swin_used), tree_used);
1005 /* drag'n'drop */
1006 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK,
1007 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1008 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available),
1009 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1010 g_signal_connect(tree_available, "drag-data-get",
1011 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1012 g_signal_connect(tree_available, "drag-data-received",
1013 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1014 g_signal_connect(tree_available, "drag-motion",
1015 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1017 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK,
1018 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1019 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used),
1020 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1021 g_signal_connect(tree_used, "drag-data-get",
1022 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1023 g_signal_connect(tree_used, "drag-data-received",
1024 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1025 g_signal_connect(tree_used, "drag-motion",
1026 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1029 button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL);
1030 button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL);
1031 g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw);
1032 g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw);
1034 vbox_buttons = gtk_vbox_new(FALSE, 6);
1035 /* FIXME this is a little hack'ish, any better ideas? */
1036 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1037 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0);
1038 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0);
1039 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1041 hbox = gtk_hbox_new(FALSE, 6);
1042 gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0);
1043 gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
1044 gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0);
1046 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
1047 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1049 gtk_widget_show_all(vbox);
1051 g_object_unref(tbw->store_available);
1052 g_object_unref(tbw->store_used);
1054 tbw->dialog = dialog;
1055 tbw->tree_available = GTK_TREE_VIEW(tree_available);
1056 tbw->tree_used = GTK_TREE_VIEW(tree_used);
1058 tbw->last_drag_path = NULL;
1060 return tbw;
1064 void toolbar_configure(GtkWindow *parent)
1066 gchar *markup;
1067 GSList *sl, *used_items;
1068 GList *l, *all_items;
1069 GtkTreePath *path;
1070 TBEditorWidget *tbw;
1072 /* read the current active toolbar items */
1073 markup = gtk_ui_manager_get_ui(uim);
1074 used_items = tb_editor_parse_ui(markup, -1, NULL);
1075 g_free(markup);
1077 /* get all available actions */
1078 all_items = gtk_action_group_list_actions(group);
1080 /* create the GUI */
1081 tbw = tb_editor_create_dialog(parent);
1083 /* fill the stores */
1084 gtk_list_store_insert_with_values(tbw->store_available, NULL, -1,
1085 TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR,
1086 TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL,
1087 -1);
1088 foreach_list(l, all_items)
1090 const gchar *name = gtk_action_get_name(l->data);
1092 if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL)
1094 GtkTreeIter iter;
1096 gtk_list_store_append(tbw->store_available, &iter);
1097 tb_editor_set_item_values(name, tbw->store_available, &iter);
1100 foreach_slist(sl, used_items)
1102 GtkTreeIter iter;
1104 gtk_list_store_append(tbw->store_used, &iter);
1105 tb_editor_set_item_values(sl->data, tbw->store_used, &iter);
1107 /* select first item */
1108 path = gtk_tree_path_new_from_string("0");
1109 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path);
1110 gtk_tree_path_free(path);
1112 /* connect the changed signals after populating the store */
1113 g_signal_connect(tbw->store_used, "row-changed",
1114 G_CALLBACK(tb_editor_available_items_changed_cb), tbw);
1115 g_signal_connect(tbw->store_used, "row-deleted",
1116 G_CALLBACK(tb_editor_available_items_deleted_cb), tbw);
1118 /* run it */
1119 gtk_dialog_run(GTK_DIALOG(tbw->dialog));
1121 gtk_widget_destroy(tbw->dialog);
1123 g_slist_foreach(used_items, (GFunc) g_free, NULL);
1124 g_slist_free(used_items);
1125 g_list_free(all_items);
1126 tb_editor_free_path(tbw);
1127 g_free(tbw);