Make Shift-Enter in search dialog and toolbar search entries search backwards
[geany-mirror.git] / src / toolbar.c
blobc25a27bc6a3970c4497dd969bc11985644b50bf2
1 /*
2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2009-2011 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 { "SaveAs", GTK_STOCK_SAVE_AS, NULL, NULL, N_("Save as"), G_CALLBACK(on_save_as1_activate) },
58 { "SaveAll", GEANY_STOCK_SAVE_ALL, NULL, NULL, N_("Save all open files"), G_CALLBACK(on_save_all1_activate) },
59 { "Reload", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked) },
60 { "Close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current file"), G_CALLBACK(on_toolbutton_close_clicked) },
61 { "CloseAll", GEANY_STOCK_CLOSE_ALL, NULL, NULL, N_("Close all open files"), G_CALLBACK(on_toolbutton_close_all_clicked) },
62 { "Cut", GTK_STOCK_CUT, NULL, NULL, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate) },
63 { "Copy", GTK_STOCK_COPY, NULL, NULL, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate) },
64 { "Paste", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate) },
65 { "Delete", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate) },
66 { "Undo", GTK_STOCK_UNDO, NULL, NULL, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate) },
67 { "Redo", GTK_STOCK_REDO, NULL, NULL, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate) },
68 { "NavBack", GTK_STOCK_GO_BACK, NULL, NULL, N_("Navigate back a location"), G_CALLBACK(on_back_activate) },
69 { "NavFor", GTK_STOCK_GO_FORWARD, NULL, NULL, N_("Navigate forward a location"), G_CALLBACK(on_forward_activate) },
70 { "Compile", GTK_STOCK_CONVERT, N_("Compile"), NULL, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked) },
71 { "Run", GTK_STOCK_EXECUTE, NULL, NULL, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked) },
72 { "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) },
73 { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, NULL, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate) },
74 { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, NULL, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate) },
75 { "UnIndent", GTK_STOCK_UNINDENT, NULL, NULL, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate) },
76 { "Indent", GTK_STOCK_INDENT, NULL, NULL, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate) },
77 { "Search", GTK_STOCK_FIND, NULL, NULL, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked) },
78 { "Goto", GTK_STOCK_JUMP_TO, NULL, NULL, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked) },
79 { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, N_("Show the preferences dialog"), G_CALLBACK(on_toolbutton_preferences_clicked) },
80 { "Quit", GTK_STOCK_QUIT, NULL, NULL, N_("Quit Geany"), G_CALLBACK(on_toolbutton_quit_clicked) },
81 { "Print", GTK_STOCK_PRINT, NULL, NULL, N_("Print document"), G_CALLBACK(on_print1_activate) },
82 { "Replace", GTK_STOCK_FIND_AND_REPLACE, NULL, NULL, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate) }
84 const guint ui_entries_n = G_N_ELEMENTS(ui_entries);
87 /* fallback UI definition */
88 const gchar *toolbar_markup =
89 "<ui>"
90 "<toolbar name='GeanyToolbar'>"
91 "<toolitem action='New'/>"
92 "<toolitem action='Open'/>"
93 "<toolitem action='Save'/>"
94 "<toolitem action='SaveAll'/>"
95 "<separator/>"
96 "<toolitem action='Reload'/>"
97 "<toolitem action='Close'/>"
98 "<separator/>"
99 "<toolitem action='NavBack'/>"
100 "<toolitem action='NavFor'/>"
101 "<separator/>"
102 "<toolitem action='Compile'/>"
103 "<toolitem action='Build'/>"
104 "<toolitem action='Run'/>"
105 "<separator/>"
106 "<toolitem action='Color'/>"
107 "<separator/>"
108 "<toolitem action='SearchEntry'/>"
109 "<toolitem action='Search'/>"
110 "<separator/>"
111 "<toolitem action='GotoEntry'/>"
112 "<toolitem action='Goto'/>"
113 "<separator/>"
114 "<toolitem action='Quit'/>"
115 "</toolbar>"
116 "</ui>";
119 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
120 * update the widget pointer in this case (i.e. request it again) or better use
121 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
122 * toolbar is reloaded. */
123 GtkWidget *toolbar_get_widget_by_name(const gchar *name)
125 GtkWidget *widget;
126 gchar *path;
128 g_return_val_if_fail(name != NULL, NULL);
130 path = g_strconcat("/ui/GeanyToolbar/", name, NULL);
131 widget = gtk_ui_manager_get_widget(uim, path);
133 g_free(path);
134 return widget;
138 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
139 * toolbar_get_widget_by_name for details(). */
140 GtkWidget *toolbar_get_widget_child_by_name(const gchar *name)
142 GtkWidget *widget = toolbar_get_widget_by_name(name);
144 if (G_LIKELY(widget != NULL))
145 return gtk_bin_get_child(GTK_BIN(widget));
146 else
147 return NULL;
151 GtkAction *toolbar_get_action_by_name(const gchar *name)
153 g_return_val_if_fail(name != NULL, NULL);
155 return gtk_action_group_get_action(group, name);
159 static void toolbar_item_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data)
161 plugin_items = g_slist_remove(plugin_items, widget);
165 void toolbar_item_ref(GtkToolItem *item)
167 g_return_if_fail(item != NULL);
169 plugin_items = g_slist_append(plugin_items, item);
170 g_signal_connect(item, "destroy", G_CALLBACK(toolbar_item_destroy_cb), NULL);
174 static GtkWidget *toolbar_reload(const gchar *markup)
176 gint i;
177 GSList *l;
178 GtkWidget *entry;
179 GError *error = NULL;
180 const gchar *filename;
181 static guint merge_id = 0;
182 GtkWidget *toolbar_new_file_menu = NULL;
183 GtkWidget *toolbar_recent_files_menu = NULL;
184 GtkWidget *toolbar_build_menu = NULL;
186 /* Cleanup old toolbar */
187 if (merge_id > 0)
189 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
190 foreach_slist(l, plugin_items)
192 g_object_ref(l->data);
193 gtk_container_remove(GTK_CONTAINER(main_widgets.toolbar), GTK_WIDGET(l->data));
195 /* ref and hold the submenus of the New, Open and Build toolbar items */
196 toolbar_new_file_menu = geany_menu_button_action_get_menu(
197 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "New")));
198 g_object_ref(toolbar_new_file_menu);
199 toolbar_recent_files_menu = geany_menu_button_action_get_menu(
200 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Open")));
201 g_object_ref(toolbar_recent_files_menu);
202 toolbar_build_menu = geany_menu_button_action_get_menu(
203 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Build")));
204 g_object_ref(toolbar_build_menu);
206 /* Get rid of it! */
207 gtk_widget_destroy(main_widgets.toolbar);
209 gtk_ui_manager_remove_ui(uim, merge_id);
210 gtk_ui_manager_ensure_update(uim);
213 if (markup != NULL)
215 merge_id = gtk_ui_manager_add_ui_from_string(uim, markup, -1, &error);
217 else
219 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
220 filename = utils_build_path(app->configdir, "ui_toolbar.xml", NULL);
221 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
222 if (merge_id == 0)
224 if (! g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
225 geany_debug("Loading user toolbar UI definition failed (%s).", error->message);
226 g_error_free(error);
227 error = NULL;
229 filename = utils_build_path(app->datadir, "ui_toolbar.xml", NULL);
230 merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error);
233 if (error != NULL)
235 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
236 error->message);
237 g_error_free(error);
238 /* finally load the internally defined markup as fallback */
239 merge_id = gtk_ui_manager_add_ui_from_string(uim, toolbar_markup, -1, NULL);
241 main_widgets.toolbar = gtk_ui_manager_get_widget(uim, "/ui/GeanyToolbar");
242 ui_init_toolbar_widgets();
244 /* add the toolbar again to the main window */
245 if (toolbar_prefs.append_to_menu)
247 GtkWidget *hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
248 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
249 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
251 else
253 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
255 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
256 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
258 gtk_widget_show(main_widgets.toolbar);
260 /* re-add und unref the plugin toolbar items */
261 i = toolbar_get_insert_position();
262 foreach_slist(l, plugin_items)
264 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), l->data, i);
265 g_object_unref(l->data);
266 i++;
268 /* re-add und unref the submenus of menu toolbar items */
269 if (toolbar_new_file_menu != NULL)
271 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
272 gtk_action_group_get_action(group, "New")), toolbar_new_file_menu);
273 g_object_unref(toolbar_new_file_menu);
275 if (toolbar_recent_files_menu != NULL)
277 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
278 gtk_action_group_get_action(group, "Open")), toolbar_recent_files_menu);
279 g_object_unref(toolbar_recent_files_menu);
281 if (toolbar_build_menu != NULL)
283 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
284 gtk_action_group_get_action(group, "Build")), toolbar_build_menu);
285 g_object_unref(toolbar_build_menu);
288 /* update button states */
289 if (main_status.main_window_realized)
291 GeanyDocument *doc = document_get_current();
292 gboolean doc_changed = (doc != NULL) ? doc->changed : FALSE;
294 ui_document_buttons_update();
295 ui_save_buttons_toggle(doc_changed); /* update save all */
296 ui_update_popup_reundo_items(doc);
298 toolbar_apply_settings();
301 /* Signals */
302 g_signal_connect(main_widgets.toolbar, "button-press-event",
303 G_CALLBACK(toolbar_popup_menu), NULL);
304 g_signal_connect(main_widgets.toolbar, "key-press-event",
305 G_CALLBACK(on_escape_key_press_event), NULL);
307 /* We don't need to disconnect those signals as this is done automatically when the entry
308 * widgets are destroyed, happens when the toolbar itself is destroyed. */
309 entry = toolbar_get_widget_child_by_name("SearchEntry");
310 if (entry != NULL)
311 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
312 entry = toolbar_get_widget_child_by_name("GotoEntry");
313 if (entry != NULL)
314 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
317 return main_widgets.toolbar;
321 static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data)
323 const gchar *arg_name = g_param_spec_get_name(arg1);
324 gint value;
326 if (toolbar_prefs.use_gtk_default_style && utils_str_equal(arg_name, "gtk-toolbar-style"))
328 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_style);
329 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), value);
331 else if (toolbar_prefs.use_gtk_default_icon && utils_str_equal(arg_name, "gtk-toolbar-size"))
333 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_size);
334 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), value);
339 GtkWidget *toolbar_init(void)
341 GtkWidget *toolbar;
342 GtkAction *action_new;
343 GtkAction *action_open;
344 GtkAction *action_build;
345 GtkAction *action_searchentry;
346 GtkAction *action_gotoentry;
347 GtkSettings *gtk_settings;
349 uim = gtk_ui_manager_new();
350 group = gtk_action_group_new("GeanyToolbar");
352 gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE);
353 gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL);
355 /* Create our custom actions */
356 action_new = geany_menu_button_action_new(
357 "New", NULL,
358 _("Create a new file"),
359 _("Create a new file from a template"),
360 GTK_STOCK_NEW);
361 g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_toolbutton_new_clicked), NULL);
362 gtk_action_group_add_action(group, action_new);
364 action_open = geany_menu_button_action_new(
365 "Open", NULL,
366 _("Open an existing file"),
367 _("Open a recent file"),
368 GTK_STOCK_OPEN);
369 g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_toolbutton_open_clicked), NULL);
370 gtk_action_group_add_action(group, action_open);
372 action_build = geany_menu_button_action_new(
373 "Build", NULL,
374 _("Build the current file"),
375 _("Choose more build actions"),
376 GEANY_STOCK_BUILD);
377 g_signal_connect(action_build, "button-clicked",
378 G_CALLBACK(build_toolbutton_build_clicked), NULL);
379 gtk_action_group_add_action(group, action_build);
381 action_searchentry = geany_entry_action_new(
382 "SearchEntry", _("Search"), _("Find the entered text in the current file"), FALSE);
383 g_signal_connect(action_searchentry, "entry-activate",
384 G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(FALSE));
385 g_signal_connect(action_searchentry, "entry-activate-backward",
386 G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(TRUE));
387 g_signal_connect(action_searchentry, "entry-changed",
388 G_CALLBACK(on_toolbar_search_entry_changed), NULL);
389 gtk_action_group_add_action(group, action_searchentry);
391 action_gotoentry = geany_entry_action_new(
392 "GotoEntry", _("Goto"), _("Jump to the entered line number"), TRUE);
393 g_signal_connect(action_gotoentry, "entry-activate",
394 G_CALLBACK(on_toolbutton_goto_entry_activate), NULL);
395 gtk_action_group_add_action(group, action_gotoentry);
397 gtk_ui_manager_insert_action_group(uim, group, 0);
399 toolbar = toolbar_reload(NULL);
401 gtk_settings = gtk_widget_get_settings(GTK_WIDGET(toolbar));
402 if (gtk_settings != NULL)
404 g_signal_connect(gtk_settings, "notify::gtk-toolbar-style",
405 G_CALLBACK(toolbar_notify_style_cb), NULL);
408 return toolbar;
412 void toolbar_update_ui(void)
414 static GtkWidget *hbox_menubar = NULL;
415 static GtkWidget *menubar = NULL;
416 GtkWidget *menubar_toolbar_separator = NULL;
417 GtkWidget *parent;
418 GtkToolItem *first_item;
420 if (menubar == NULL)
421 { /* cache widget pointers */
422 hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
423 menubar = ui_lookup_widget(main_widgets.window, "menubar1");
425 /* the separator between the menubar and the toolbar */
426 first_item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets.toolbar), 0);
427 if (first_item != NULL && GTK_IS_SEPARATOR_TOOL_ITEM(first_item))
429 gtk_widget_destroy(GTK_WIDGET(first_item));
432 parent = gtk_widget_get_parent(main_widgets.toolbar);
434 if (toolbar_prefs.append_to_menu)
436 if (parent != NULL)
438 if (parent != hbox_menubar)
439 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
440 * like to do it */
441 g_object_ref(main_widgets.toolbar);
443 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
444 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
445 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
447 g_object_unref(main_widgets.toolbar);
450 else
451 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
453 /* the separator between the menubar and the toolbar */
454 menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new());
455 gtk_widget_show(menubar_toolbar_separator);
456 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar),
457 GTK_TOOL_ITEM(menubar_toolbar_separator), 0);
459 else
461 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
463 if (parent != NULL)
465 if (parent != box)
467 g_object_ref(main_widgets.toolbar);
469 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
470 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
471 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
473 g_object_unref(main_widgets.toolbar);
476 else
478 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
479 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
482 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
483 * hbox and not expand it if the toolbar is appended */
484 gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar,
485 ! (toolbar_prefs.visible && toolbar_prefs.append_to_menu),
486 ! (toolbar_prefs.visible && toolbar_prefs.append_to_menu), 0, GTK_PACK_START);
490 /* Returns the position for adding new toolbar items. The returned position can be used
491 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
492 * with @a geany->main_widgets->toolbar.
493 * The position is always the last one before the Quit button or the very last position if the
494 * Quit button is not the last toolbar item.
496 * @return The position for new toolbar items.
498 gint toolbar_get_insert_position(void)
500 GtkWidget *quit = toolbar_get_widget_by_name("Quit");
501 gint quit_pos = -1, pos;
503 if (quit != NULL)
504 quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit));
506 pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar));
507 if (quit_pos == (pos - 1))
509 /* if the toolbar item before the quit button is a separator, insert new items before */
510 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
511 GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1)))
513 return quit_pos - 1;
515 /* else return the position of the quit button to insert new items before */
516 return quit_pos;
519 return pos;
523 void toolbar_finalize(void)
525 GeanyMenubuttonAction *open_action = GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"));
526 g_object_unref(geany_menu_button_action_get_menu(open_action));
527 geany_menu_button_action_set_menu(open_action, NULL);
529 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
530 g_object_unref(uim);
531 g_object_unref(group);
533 g_slist_free(plugin_items);
537 void toolbar_show_hide(void)
539 ignore_callback = TRUE;
540 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
541 ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible);
542 ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible);
543 ignore_callback = FALSE;
547 /* sets the icon style of the toolbar */
548 static void toolbar_set_icon_style(void)
550 gint icon_style;
552 icon_style = toolbar_prefs.icon_style;
554 if (toolbar_prefs.use_gtk_default_style)
555 icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs.icon_style);
557 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), icon_style);
561 /* sets the icon size of the toolbar */
562 static void toolbar_set_icon_size(void)
564 gint icon_size;
566 icon_size = toolbar_prefs.icon_size;
568 if (toolbar_prefs.use_gtk_default_icon)
569 icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs.icon_size);
571 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), icon_size);
575 void toolbar_apply_settings(void)
577 toolbar_set_icon_style();
578 toolbar_set_icon_size();
582 #define TB_EDITOR_SEPARATOR _("Separator")
583 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
584 typedef struct
586 GtkWidget *dialog;
588 GtkTreeView *tree_available;
589 GtkTreeView *tree_used;
591 GtkListStore *store_available;
592 GtkListStore *store_used;
594 GtkTreePath *last_drag_path;
595 GtkTreeViewDropPosition last_drag_pos;
597 GtkWidget *drag_source;
598 } TBEditorWidget;
600 static const GtkTargetEntry tb_editor_dnd_targets[] =
602 { "GEANY_TB_EDITOR_ROW", 0, 0 }
604 static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets);
606 enum
608 TB_EDITOR_COL_ACTION,
609 TB_EDITOR_COL_LABEL,
610 TB_EDITOR_COL_ICON,
611 TB_EDITOR_COLS_MAX
614 static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name,
615 const gchar **attribute_names,
616 const gchar **attribute_values, gpointer data,
617 GError **error)
619 gint i;
620 GSList **actions = data;
622 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
623 if (utils_str_equal(element_name, "separator"))
624 *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR));
626 for (i = 0; attribute_names[i] != NULL; i++)
628 if (utils_str_equal(attribute_names[i], "action"))
630 *actions = g_slist_append(*actions, g_strdup(attribute_values[i]));
636 static const GMarkupParser tb_editor_xml_parser =
638 tb_editor_handler_start_element, NULL, NULL, NULL, NULL
642 static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error)
644 GMarkupParseContext *context;
645 GSList *list = NULL;
647 context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL);
648 g_markup_parse_context_parse(context, buffer, length, error);
649 g_markup_parse_context_free(context);
651 return list;
655 static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter)
657 gchar *icon = NULL;
658 gchar *label = NULL;
659 gchar *label_clean = NULL;
660 GtkAction *action;
662 action = gtk_action_group_get_action(group, name);
663 if (action == NULL)
665 if (utils_str_equal(name, TB_EDITOR_SEPARATOR))
666 label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL);
667 else
668 return;
670 else
672 g_object_get(action, "icon-name", &icon, NULL);
673 if (icon == NULL)
674 g_object_get(action, "stock-id", &icon, NULL);
676 g_object_get(action, "label", &label, NULL);
677 if (label != NULL)
678 label_clean = utils_str_remove_chars(g_strdup(label), "_");
681 gtk_list_store_set(store, iter,
682 TB_EDITOR_COL_ACTION, name,
683 TB_EDITOR_COL_LABEL, label_clean,
684 TB_EDITOR_COL_ICON, icon,
685 -1);
687 g_free(icon);
688 g_free(label);
689 g_free(label_clean);
693 static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter)
695 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter);
696 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0);
697 gtk_tree_path_free(path);
701 static void tb_editor_free_path(TBEditorWidget *tbw)
703 if (tbw->last_drag_path != NULL)
705 gtk_tree_path_free(tbw->last_drag_path);
706 tbw->last_drag_path = NULL;
711 static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
713 GtkTreeModel *model_used;
714 GtkTreeSelection *selection_used;
715 GtkTreeIter iter_used, iter_new;
716 gchar *action_name;
718 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
719 if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used))
721 gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1);
722 if (gtk_list_store_remove(tbw->store_used, &iter_used))
723 gtk_tree_selection_select_iter(selection_used, &iter_used);
725 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
727 gtk_list_store_append(tbw->store_available, &iter_new);
728 tb_editor_set_item_values(action_name, tbw->store_available, &iter_new);
729 tb_editor_scroll_to_iter(tbw->tree_available, &iter_new);
732 g_free(action_name);
737 static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
739 GtkTreeModel *model_available;
740 GtkTreeSelection *selection_available, *selection_used;
741 GtkTreeIter iter_available, iter_new, iter_selected;
742 gchar *action_name;
744 selection_available = gtk_tree_view_get_selection(tbw->tree_available);
745 if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available))
747 gtk_tree_model_get(model_available, &iter_available,
748 TB_EDITOR_COL_ACTION, &action_name, -1);
749 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
751 if (gtk_list_store_remove(tbw->store_available, &iter_available))
752 gtk_tree_selection_select_iter(selection_available, &iter_available);
755 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
756 if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected))
757 gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected);
758 else
759 gtk_list_store_append(tbw->store_used, &iter_new);
761 tb_editor_set_item_values(action_name, tbw->store_used, &iter_new);
762 tb_editor_scroll_to_iter(tbw->tree_used, &iter_new);
764 g_free(action_name);
769 static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context,
770 gint x, gint y, guint ltime, TBEditorWidget *tbw)
772 if (tbw->last_drag_path != NULL)
773 gtk_tree_path_free(tbw->last_drag_path);
774 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget),
775 &(tbw->last_drag_path), &(tbw->last_drag_pos));
777 return FALSE;
781 static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
782 GtkSelectionData *data, guint info, guint ltime,
783 TBEditorWidget *tbw)
785 GtkTreeIter iter;
786 GtkTreeSelection *selection;
787 GtkTreeModel *model;
788 GdkAtom atom;
789 gchar *name;
791 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
792 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
793 return;
795 gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1);
796 if (! NZV(name))
797 return;
799 atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE);
800 gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name));
802 g_free(name);
804 tbw->drag_source = widget;
808 static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context,
809 gint x, gint y, GtkSelectionData *data, guint info,
810 guint ltime, TBEditorWidget *tbw)
812 GtkTreeView *tree = GTK_TREE_VIEW(widget);
813 gboolean del = FALSE;
815 if (data->length >= 0 && data->format == 8)
817 gboolean is_sep;
818 gchar *text = NULL;
820 text = (gchar*) data->data;
821 is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR);
822 /* If the source of the action is equal to the target, we do just re-order and so need
823 * to delete the separator to get it moved, not just copied. */
824 if (is_sep && widget == tbw->drag_source)
825 is_sep = FALSE;
827 if (tree != tbw->tree_available || ! is_sep)
829 GtkTreeIter iter, iter_before, *iter_before_ptr;
830 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
832 if (tbw->last_drag_path != NULL)
834 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path);
836 if (gtk_list_store_iter_is_valid(store, &iter_before))
837 iter_before_ptr = &iter_before;
838 else
839 iter_before_ptr = NULL;
841 if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE ||
842 tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
843 gtk_list_store_insert_before(store, &iter, iter_before_ptr);
844 else
845 gtk_list_store_insert_after(store, &iter, iter_before_ptr);
847 else
848 gtk_list_store_append(store, &iter);
850 tb_editor_set_item_values(text, store, &iter);
851 tb_editor_scroll_to_iter(tree, &iter);
853 if (tree != tbw->tree_used || ! is_sep)
854 del = TRUE;
857 tbw->drag_source = NULL; /* reset the value just to be sure */
858 tb_editor_free_path(tbw);
859 gtk_drag_finish(context, TRUE, del, ltime);
863 static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path,
864 GtkTreeIter *iter, gpointer data)
866 gchar *action_name;
868 gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1);
870 if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
871 g_string_append_printf(data, "\t\t<separator/>\n");
872 else if (NZV(action_name))
873 g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name);
875 g_free(action_name);
876 return FALSE;
880 static void tb_editor_write_markup(TBEditorWidget *tbw)
882 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
883 const gchar *template = "<ui>\n<!--\n\
884 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
885 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
886 You can re-order all items and freely add and remove available actions.\n\
887 You cannot add new actions which are not listed in the documentation.\n\
888 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
889 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
890 editor in Geany.\n\n\
891 A list of available actions can be found in the documentation included with Geany or\n\
892 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
893 \t<toolbar name='GeanyToolbar'>\n";
894 const gchar *filename = utils_build_path(app->configdir, "ui_toolbar.xml", NULL);
895 GString *str = g_string_new(template);
897 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str);
899 g_string_append(str, "\n\t</toolbar>\n</ui>\n");
901 toolbar_reload(str->str);
903 utils_write_file(filename, str->str);
905 g_string_free(str, TRUE);
909 static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1,
910 GtkTreeIter *arg2, TBEditorWidget *tbw)
912 tb_editor_write_markup(tbw);
916 static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1,
917 TBEditorWidget *tbw)
919 tb_editor_write_markup(tbw);
923 static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent)
925 GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove;
926 GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label;
927 GtkCellRenderer *text_renderer, *icon_renderer;
928 GtkTreeViewColumn *column;
929 TBEditorWidget *tbw = g_new(TBEditorWidget, 1);
931 if (parent == NULL)
932 parent = GTK_WINDOW(main_widgets.window);
934 dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"),
935 parent,
936 GTK_DIALOG_DESTROY_WITH_PARENT,
937 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
938 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
939 gtk_box_set_spacing(GTK_BOX(vbox), 6);
940 gtk_widget_set_name(dialog, "GeanyDialog");
941 gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400);
942 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
944 tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX,
945 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
946 tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX,
947 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
949 label = gtk_label_new(
950 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
951 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
953 tree_available = gtk_tree_view_new();
954 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available));
955 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE);
956 gtk_tree_sortable_set_sort_column_id(
957 GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING);
959 icon_renderer = gtk_cell_renderer_pixbuf_new();
960 column = gtk_tree_view_column_new_with_attributes(
961 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
962 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
964 text_renderer = gtk_cell_renderer_text_new();
965 column = gtk_tree_view_column_new_with_attributes(
966 _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
967 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
969 swin_available = gtk_scrolled_window_new(NULL, NULL);
970 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available),
971 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
972 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_ETCHED_IN);
973 gtk_container_add(GTK_CONTAINER(swin_available), tree_available);
975 tree_used = gtk_tree_view_new();
976 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used));
977 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE);
978 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE);
980 icon_renderer = gtk_cell_renderer_pixbuf_new();
981 column = gtk_tree_view_column_new_with_attributes(
982 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
983 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
985 text_renderer = gtk_cell_renderer_text_new();
986 column = gtk_tree_view_column_new_with_attributes(
987 _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
988 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
990 swin_used = gtk_scrolled_window_new(NULL, NULL);
991 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used),
992 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
993 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_ETCHED_IN);
994 gtk_container_add(GTK_CONTAINER(swin_used), tree_used);
996 /* drag'n'drop */
997 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK,
998 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
999 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available),
1000 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1001 g_signal_connect(tree_available, "drag-data-get",
1002 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1003 g_signal_connect(tree_available, "drag-data-received",
1004 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1005 g_signal_connect(tree_available, "drag-motion",
1006 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1008 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK,
1009 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1010 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used),
1011 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1012 g_signal_connect(tree_used, "drag-data-get",
1013 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1014 g_signal_connect(tree_used, "drag-data-received",
1015 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1016 g_signal_connect(tree_used, "drag-motion",
1017 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1020 button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL);
1021 button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL);
1022 g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw);
1023 g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw);
1025 vbox_buttons = gtk_vbox_new(FALSE, 6);
1026 /* FIXME this is a little hack'ish, any better ideas? */
1027 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1028 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0);
1029 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0);
1030 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1032 hbox = gtk_hbox_new(FALSE, 6);
1033 gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0);
1034 gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
1035 gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0);
1037 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
1038 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1040 gtk_widget_show_all(vbox);
1042 g_object_unref(tbw->store_available);
1043 g_object_unref(tbw->store_used);
1045 tbw->dialog = dialog;
1046 tbw->tree_available = GTK_TREE_VIEW(tree_available);
1047 tbw->tree_used = GTK_TREE_VIEW(tree_used);
1049 tbw->last_drag_path = NULL;
1051 return tbw;
1055 void toolbar_configure(GtkWindow *parent)
1057 gchar *markup;
1058 const gchar *name;
1059 GSList *sl, *used_items;
1060 GList *l, *all_items;
1061 GtkTreeIter iter;
1062 GtkTreePath *path;
1063 TBEditorWidget *tbw;
1065 /* read the current active toolbar items */
1066 markup = gtk_ui_manager_get_ui(uim);
1067 used_items = tb_editor_parse_ui(markup, strlen(markup), NULL);
1068 g_free(markup);
1070 /* get all available actions */
1071 all_items = gtk_action_group_list_actions(group);
1073 /* create the GUI */
1074 tbw = tb_editor_create_dialog(parent);
1076 /* fill the stores */
1077 gtk_list_store_insert_with_values(tbw->store_available, NULL, -1,
1078 TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR,
1079 TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL,
1080 -1);
1081 foreach_list(l, all_items)
1083 name = gtk_action_get_name(l->data);
1084 if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL)
1086 gtk_list_store_append(tbw->store_available, &iter);
1087 tb_editor_set_item_values(name, tbw->store_available, &iter);
1090 foreach_slist(sl, used_items)
1092 gtk_list_store_append(tbw->store_used, &iter);
1093 tb_editor_set_item_values(sl->data, tbw->store_used, &iter);
1095 /* select first item */
1096 path = gtk_tree_path_new_from_string("0");
1097 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path);
1098 gtk_tree_path_free(path);
1100 /* connect the changed signals after populating the store */
1101 g_signal_connect(tbw->store_used, "row-changed",
1102 G_CALLBACK(tb_editor_available_items_changed_cb), tbw);
1103 g_signal_connect(tbw->store_used, "row-deleted",
1104 G_CALLBACK(tb_editor_available_items_deleted_cb), tbw);
1106 /* run it */
1107 gtk_dialog_run(GTK_DIALOG(tbw->dialog));
1109 gtk_widget_destroy(tbw->dialog);
1111 g_slist_foreach(used_items, (GFunc) g_free, NULL);
1112 g_slist_free(used_items);
1113 g_list_free(all_items);
1114 tb_editor_free_path(tbw);
1115 g_free(tbw);