r5162 | eht16 | 2010-08-15 13:53:09 +0100 (Sun, 15 Aug 2010) | 1 line
[geany-mirror.git] / src / toolbar.c
blobadca3d88188f7c3b5a5a2d11f6e3181500e081b1
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();
291 gboolean doc_changed = (doc != NULL) ? doc->changed : FALSE;
293 ui_document_buttons_update();
294 ui_save_buttons_toggle(doc_changed); /* update save all */
295 ui_update_popup_reundo_items(doc);
297 toolbar_apply_settings();
300 /* Signals */
301 g_signal_connect(main_widgets.toolbar, "button-press-event",
302 G_CALLBACK(toolbar_popup_menu), NULL);
303 g_signal_connect(main_widgets.toolbar, "key-press-event",
304 G_CALLBACK(on_escape_key_press_event), NULL);
306 /* We don't need to disconnect those signals as this is done automatically when the entry
307 * widgets are destroyed, happens when the toolbar itself is destroyed. */
308 entry = toolbar_get_widget_child_by_name("SearchEntry");
309 if (entry != NULL)
310 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
311 entry = toolbar_get_widget_child_by_name("GotoEntry");
312 if (entry != NULL)
313 g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
316 return main_widgets.toolbar;
320 static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data)
322 const gchar *arg_name = g_param_spec_get_name(arg1);
323 gint value;
325 if (toolbar_prefs.use_gtk_default_style && utils_str_equal(arg_name, "gtk-toolbar-style"))
327 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_style);
328 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), value);
330 else if (toolbar_prefs.use_gtk_default_icon && utils_str_equal(arg_name, "gtk-toolbar-size"))
332 value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_size);
333 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), value);
338 GtkWidget *toolbar_init(void)
340 GtkWidget *toolbar;
341 GtkAction *action_new;
342 GtkAction *action_open;
343 GtkAction *action_build;
344 GtkAction *action_searchentry;
345 GtkAction *action_gotoentry;
346 GtkSettings *gtk_settings;
348 uim = gtk_ui_manager_new();
349 group = gtk_action_group_new("GeanyToolbar");
351 gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE);
352 gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL);
354 /* Create our custom actions */
355 action_new = geany_menu_button_action_new(
356 "New", NULL,
357 _("Create a new file"),
358 _("Create a new file from a template"),
359 GTK_STOCK_NEW);
360 g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_toolbutton_new_clicked), NULL);
361 gtk_action_group_add_action(group, action_new);
363 action_open = geany_menu_button_action_new(
364 "Open", NULL,
365 _("Open an existing file"),
366 _("Open a recent file"),
367 GTK_STOCK_OPEN);
368 g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_toolbutton_open_clicked), NULL);
369 gtk_action_group_add_action(group, action_open);
371 action_build = geany_menu_button_action_new(
372 "Build", NULL,
373 _("Build the current file"),
374 _("Choose more build actions"),
375 GEANY_STOCK_BUILD);
376 g_signal_connect(action_build, "button-clicked",
377 G_CALLBACK(build_toolbutton_build_clicked), NULL);
378 gtk_action_group_add_action(group, action_build);
380 action_searchentry = geany_entry_action_new(
381 "SearchEntry", _("Search"), _("Find the entered text in the current file"), FALSE);
382 g_signal_connect(action_searchentry, "entry-activate",
383 G_CALLBACK(on_toolbar_search_entry_changed), GINT_TO_POINTER(FALSE));
384 g_signal_connect(action_searchentry, "entry-changed",
385 G_CALLBACK(on_toolbar_search_entry_changed), GINT_TO_POINTER(TRUE));
386 gtk_action_group_add_action(group, action_searchentry);
388 action_gotoentry = geany_entry_action_new(
389 "GotoEntry", _("Goto"), _("Jump to the entered line number"), TRUE);
390 g_signal_connect(action_gotoentry, "entry-activate",
391 G_CALLBACK(on_toolbutton_goto_entry_activate), NULL);
392 gtk_action_group_add_action(group, action_gotoentry);
394 gtk_ui_manager_insert_action_group(uim, group, 0);
396 toolbar = toolbar_reload(NULL);
398 gtk_settings = gtk_widget_get_settings(GTK_WIDGET(toolbar));
399 if (gtk_settings != NULL)
401 g_signal_connect(gtk_settings, "notify::gtk-toolbar-style",
402 G_CALLBACK(toolbar_notify_style_cb), NULL);
405 return toolbar;
409 void toolbar_update_ui(void)
411 static GtkWidget *hbox_menubar = NULL;
412 static GtkWidget *menubar = NULL;
413 GtkWidget *menubar_toolbar_separator = NULL;
414 GtkWidget *parent;
415 GtkToolItem *first_item;
417 if (menubar == NULL)
418 { /* cache widget pointers */
419 hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar");
420 menubar = ui_lookup_widget(main_widgets.window, "menubar1");
422 /* the separator between the menubar and the toolbar */
423 first_item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets.toolbar), 0);
424 if (first_item != NULL && GTK_IS_SEPARATOR_TOOL_ITEM(first_item))
426 gtk_widget_destroy(GTK_WIDGET(first_item));
429 parent = gtk_widget_get_parent(main_widgets.toolbar);
431 if (toolbar_prefs.append_to_menu)
433 if (parent != NULL)
435 if (parent != hbox_menubar)
436 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
437 * like to do it */
438 g_object_ref(main_widgets.toolbar);
440 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
441 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
442 gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1);
444 g_object_unref(main_widgets.toolbar);
447 else
448 gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0);
450 /* the separator between the menubar and the toolbar */
451 menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new());
452 gtk_widget_show(menubar_toolbar_separator);
453 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar),
454 GTK_TOOL_ITEM(menubar_toolbar_separator), 0);
456 else
458 GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1");
460 if (parent != NULL)
462 if (parent != box)
464 g_object_ref(main_widgets.toolbar);
466 gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar);
467 gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0);
468 gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1);
470 g_object_unref(main_widgets.toolbar);
473 else
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);
479 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
480 * hbox and not expand it if the toolbar is appended */
481 gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar,
482 ! toolbar_prefs.append_to_menu, ! toolbar_prefs.append_to_menu, 0, GTK_PACK_START);
486 /* Returns the position for adding new toolbar items. The returned position can be used
487 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
488 * with @a geany->main_widgets->toolbar.
489 * The position is always the last one before the Quit button or the very last position if the
490 * Quit button is not the last toolbar item.
492 * @return The position for new toolbar items.
494 gint toolbar_get_insert_position(void)
496 GtkWidget *quit = toolbar_get_widget_by_name("Quit");
497 gint quit_pos = -1, pos;
499 if (quit != NULL)
500 quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit));
502 pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar));
503 if (quit_pos == (pos - 1))
505 /* if the toolbar item before the quit button is a separator, insert new items before */
506 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
507 GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1)))
509 return quit_pos - 1;
511 /* else return the position of the quit button to insert new items before */
512 return quit_pos;
515 return pos;
519 void toolbar_finalize(void)
521 GeanyMenubuttonAction *open_action = GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"));
522 g_object_unref(geany_menu_button_action_get_menu(open_action));
523 geany_menu_button_action_set_menu(open_action, NULL);
525 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
526 g_object_unref(uim);
527 g_object_unref(group);
529 g_slist_free(plugin_items);
533 void toolbar_show_hide(void)
535 ignore_callback = TRUE;
536 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
537 ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible);
538 ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible);
539 ignore_callback = FALSE;
543 /* sets the icon style of the toolbar */
544 static void toolbar_set_icon_style(void)
546 gint icon_style;
548 icon_style = toolbar_prefs.icon_style;
550 if (toolbar_prefs.use_gtk_default_style)
551 icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs.icon_style);
553 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), icon_style);
557 /* sets the icon size of the toolbar */
558 static void toolbar_set_icon_size(void)
560 gint icon_size;
562 icon_size = toolbar_prefs.icon_size;
564 if (toolbar_prefs.use_gtk_default_icon)
565 icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs.icon_size);
567 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), icon_size);
571 void toolbar_apply_settings(void)
573 toolbar_set_icon_style();
574 toolbar_set_icon_size();
578 #define TB_EDITOR_SEPARATOR _("Separator")
579 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
580 typedef struct
582 GtkWidget *dialog;
584 GtkTreeView *tree_available;
585 GtkTreeView *tree_used;
587 GtkListStore *store_available;
588 GtkListStore *store_used;
590 GtkTreePath *last_drag_path;
591 GtkTreeViewDropPosition last_drag_pos;
593 GtkWidget *drag_source;
594 } TBEditorWidget;
596 static const GtkTargetEntry tb_editor_dnd_targets[] =
598 { "GEANY_TB_EDITOR_ROW", 0, 0 }
600 static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets);
602 enum
604 TB_EDITOR_COL_ACTION,
605 TB_EDITOR_COL_LABEL,
606 TB_EDITOR_COL_ICON,
607 TB_EDITOR_COLS_MAX
610 static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name,
611 const gchar **attribute_names,
612 const gchar **attribute_values, gpointer data,
613 GError **error)
615 gint i;
616 GSList **actions = data;
618 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
619 if (utils_str_equal(element_name, "separator"))
620 *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR));
622 for (i = 0; attribute_names[i] != NULL; i++)
624 if (utils_str_equal(attribute_names[i], "action"))
626 *actions = g_slist_append(*actions, g_strdup(attribute_values[i]));
632 static const GMarkupParser tb_editor_xml_parser =
634 tb_editor_handler_start_element, NULL, NULL, NULL, NULL
638 static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error)
640 GMarkupParseContext *context;
641 GSList *list = NULL;
643 context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL);
644 g_markup_parse_context_parse(context, buffer, length, error);
645 g_markup_parse_context_free(context);
647 return list;
651 static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter)
653 gchar *icon = NULL;
654 gchar *label = NULL;
655 gchar *label_clean = NULL;
656 GtkAction *action;
658 action = gtk_action_group_get_action(group, name);
659 if (action == NULL)
661 if (utils_str_equal(name, TB_EDITOR_SEPARATOR))
662 label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL);
663 else
664 return;
666 else
668 g_object_get(action, "icon-name", &icon, NULL);
669 if (icon == NULL)
670 g_object_get(action, "stock-id", &icon, NULL);
672 g_object_get(action, "label", &label, NULL);
673 if (label != NULL)
674 label_clean = utils_str_remove_chars(g_strdup(label), "_");
677 gtk_list_store_set(store, iter,
678 TB_EDITOR_COL_ACTION, name,
679 TB_EDITOR_COL_LABEL, label_clean,
680 TB_EDITOR_COL_ICON, icon,
681 -1);
683 g_free(icon);
684 g_free(label);
685 g_free(label_clean);
689 static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter)
691 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter);
692 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0);
693 gtk_tree_path_free(path);
697 static void tb_editor_free_path(TBEditorWidget *tbw)
699 if (tbw->last_drag_path != NULL)
701 gtk_tree_path_free(tbw->last_drag_path);
702 tbw->last_drag_path = NULL;
707 static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
709 GtkTreeModel *model_used;
710 GtkTreeSelection *selection_used;
711 GtkTreeIter iter_used, iter_new;
712 gchar *action_name;
714 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
715 if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used))
717 gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1);
718 if (gtk_list_store_remove(tbw->store_used, &iter_used))
719 gtk_tree_selection_select_iter(selection_used, &iter_used);
721 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
723 gtk_list_store_append(tbw->store_available, &iter_new);
724 tb_editor_set_item_values(action_name, tbw->store_available, &iter_new);
725 tb_editor_scroll_to_iter(tbw->tree_available, &iter_new);
728 g_free(action_name);
733 static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
735 GtkTreeModel *model_available;
736 GtkTreeSelection *selection_available, *selection_used;
737 GtkTreeIter iter_available, iter_new, iter_selected;
738 gchar *action_name;
740 selection_available = gtk_tree_view_get_selection(tbw->tree_available);
741 if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available))
743 gtk_tree_model_get(model_available, &iter_available,
744 TB_EDITOR_COL_ACTION, &action_name, -1);
745 if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
747 if (gtk_list_store_remove(tbw->store_available, &iter_available))
748 gtk_tree_selection_select_iter(selection_available, &iter_available);
751 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
752 if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected))
753 gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected);
754 else
755 gtk_list_store_append(tbw->store_used, &iter_new);
757 tb_editor_set_item_values(action_name, tbw->store_used, &iter_new);
758 tb_editor_scroll_to_iter(tbw->tree_used, &iter_new);
760 g_free(action_name);
765 static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context,
766 gint x, gint y, guint ltime, TBEditorWidget *tbw)
768 if (tbw->last_drag_path != NULL)
769 gtk_tree_path_free(tbw->last_drag_path);
770 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget),
771 &(tbw->last_drag_path), &(tbw->last_drag_pos));
773 return FALSE;
777 static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
778 GtkSelectionData *data, guint info, guint ltime,
779 TBEditorWidget *tbw)
781 GtkTreeIter iter;
782 GtkTreeSelection *selection;
783 GtkTreeModel *model;
784 GdkAtom atom;
785 gchar *name;
787 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
788 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
789 return;
791 gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1);
792 if (! NZV(name))
793 return;
795 atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE);
796 gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name));
798 g_free(name);
800 tbw->drag_source = widget;
804 static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context,
805 gint x, gint y, GtkSelectionData *data, guint info,
806 guint ltime, TBEditorWidget *tbw)
808 GtkTreeView *tree = GTK_TREE_VIEW(widget);
809 gboolean del = FALSE;
811 if (data->length >= 0 && data->format == 8)
813 gboolean is_sep;
814 gchar *text = NULL;
816 text = (gchar*) data->data;
817 is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR);
818 /* If the source of the action is equal to the target, we do just re-order and so need
819 * to delete the separator to get it moved, not just copied. */
820 if (is_sep && widget == tbw->drag_source)
821 is_sep = FALSE;
823 if (tree != tbw->tree_available || ! is_sep)
825 GtkTreeIter iter, iter_before, *iter_before_ptr;
826 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
828 if (tbw->last_drag_path != NULL)
830 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path);
832 if (gtk_list_store_iter_is_valid(store, &iter_before))
833 iter_before_ptr = &iter_before;
834 else
835 iter_before_ptr = NULL;
837 if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE ||
838 tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
839 gtk_list_store_insert_before(store, &iter, iter_before_ptr);
840 else
841 gtk_list_store_insert_after(store, &iter, iter_before_ptr);
843 else
844 gtk_list_store_append(store, &iter);
846 tb_editor_set_item_values(text, store, &iter);
847 tb_editor_scroll_to_iter(tree, &iter);
849 if (tree != tbw->tree_used || ! is_sep)
850 del = TRUE;
853 tbw->drag_source = NULL; /* reset the value just to be sure */
854 tb_editor_free_path(tbw);
855 gtk_drag_finish(context, TRUE, del, ltime);
859 static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path,
860 GtkTreeIter *iter, gpointer data)
862 gchar *action_name;
864 gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1);
866 if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR))
867 g_string_append_printf(data, "\t\t<separator/>\n");
868 else if (NZV(action_name))
869 g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name);
871 g_free(action_name);
872 return FALSE;
876 static void tb_editor_write_markup(TBEditorWidget *tbw)
878 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
879 const gchar *template = "<ui>\n<!--\n\
880 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
881 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
882 You can re-order all items and freely add and remove available actions.\n\
883 You cannot add new actions which are not listed in the documentation.\n\
884 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
885 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
886 editor in Geany.\n\n\
887 A list of available actions can be found in the documentation included with Geany or\n\
888 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
889 \t<toolbar name='GeanyToolbar'>\n";
890 const gchar *filename = utils_build_path(app->configdir, "ui_toolbar.xml", NULL);
891 GString *str = g_string_new(template);
893 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str);
895 g_string_append(str, "\n\t</toolbar>\n</ui>\n");
897 toolbar_reload(str->str);
899 utils_write_file(filename, str->str);
901 g_string_free(str, TRUE);
905 static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1,
906 GtkTreeIter *arg2, TBEditorWidget *tbw)
908 tb_editor_write_markup(tbw);
912 static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1,
913 TBEditorWidget *tbw)
915 tb_editor_write_markup(tbw);
919 static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent)
921 GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove;
922 GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label;
923 GtkCellRenderer *text_renderer, *icon_renderer;
924 GtkTreeViewColumn *column;
925 TBEditorWidget *tbw = g_new(TBEditorWidget, 1);
927 if (parent == NULL)
928 parent = GTK_WINDOW(main_widgets.window);
930 dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"),
931 parent,
932 GTK_DIALOG_DESTROY_WITH_PARENT,
933 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
934 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
935 gtk_box_set_spacing(GTK_BOX(vbox), 6);
936 gtk_widget_set_name(dialog, "GeanyDialog");
937 gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400);
938 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
940 tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX,
941 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
942 tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX,
943 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
945 label = gtk_label_new(
946 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
947 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
949 tree_available = gtk_tree_view_new();
950 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available));
951 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE);
952 gtk_tree_sortable_set_sort_column_id(
953 GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING);
955 icon_renderer = gtk_cell_renderer_pixbuf_new();
956 column = gtk_tree_view_column_new_with_attributes(
957 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
958 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
960 text_renderer = gtk_cell_renderer_text_new();
961 column = gtk_tree_view_column_new_with_attributes(
962 _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
963 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
965 swin_available = gtk_scrolled_window_new(NULL, NULL);
966 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available),
967 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
968 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_ETCHED_IN);
969 gtk_container_add(GTK_CONTAINER(swin_available), tree_available);
971 tree_used = gtk_tree_view_new();
972 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used));
973 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE);
974 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE);
976 icon_renderer = gtk_cell_renderer_pixbuf_new();
977 column = gtk_tree_view_column_new_with_attributes(
978 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
979 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
981 text_renderer = gtk_cell_renderer_text_new();
982 column = gtk_tree_view_column_new_with_attributes(
983 _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
984 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
986 swin_used = gtk_scrolled_window_new(NULL, NULL);
987 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used),
988 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
989 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_ETCHED_IN);
990 gtk_container_add(GTK_CONTAINER(swin_used), tree_used);
992 /* drag'n'drop */
993 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK,
994 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
995 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available),
996 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
997 g_signal_connect(tree_available, "drag-data-get",
998 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
999 g_signal_connect(tree_available, "drag-data-received",
1000 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1001 g_signal_connect(tree_available, "drag-motion",
1002 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1004 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK,
1005 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1006 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used),
1007 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1008 g_signal_connect(tree_used, "drag-data-get",
1009 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1010 g_signal_connect(tree_used, "drag-data-received",
1011 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1012 g_signal_connect(tree_used, "drag-motion",
1013 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1016 button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL);
1017 button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL);
1018 g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw);
1019 g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw);
1021 vbox_buttons = gtk_vbox_new(FALSE, 6);
1022 /* FIXME this is a little hack'ish, any better ideas? */
1023 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1024 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0);
1025 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0);
1026 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1028 hbox = gtk_hbox_new(FALSE, 6);
1029 gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0);
1030 gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
1031 gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0);
1033 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
1034 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1036 gtk_widget_show_all(vbox);
1038 g_object_unref(tbw->store_available);
1039 g_object_unref(tbw->store_used);
1041 tbw->dialog = dialog;
1042 tbw->tree_available = GTK_TREE_VIEW(tree_available);
1043 tbw->tree_used = GTK_TREE_VIEW(tree_used);
1045 tbw->last_drag_path = NULL;
1047 return tbw;
1051 void toolbar_configure(GtkWindow *parent)
1053 gchar *markup;
1054 const gchar *name;
1055 GSList *sl, *used_items;
1056 GList *l, *all_items;
1057 GtkTreeIter iter;
1058 GtkTreePath *path;
1059 TBEditorWidget *tbw;
1061 /* read the current active toolbar items */
1062 markup = gtk_ui_manager_get_ui(uim);
1063 used_items = tb_editor_parse_ui(markup, strlen(markup), NULL);
1064 g_free(markup);
1066 /* get all available actions */
1067 all_items = gtk_action_group_list_actions(group);
1069 /* create the GUI */
1070 tbw = tb_editor_create_dialog(parent);
1072 /* fill the stores */
1073 gtk_list_store_insert_with_values(tbw->store_available, NULL, -1,
1074 TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR,
1075 TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL,
1076 -1);
1077 foreach_list(l, all_items)
1079 name = gtk_action_get_name(l->data);
1080 if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL)
1082 gtk_list_store_append(tbw->store_available, &iter);
1083 tb_editor_set_item_values(name, tbw->store_available, &iter);
1086 foreach_slist(sl, used_items)
1088 gtk_list_store_append(tbw->store_used, &iter);
1089 tb_editor_set_item_values(sl->data, tbw->store_used, &iter);
1091 /* select first item */
1092 path = gtk_tree_path_new_from_string("0");
1093 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path);
1094 gtk_tree_path_free(path);
1096 /* connect the changed signals after populating the store */
1097 g_signal_connect(tbw->store_used, "row-changed",
1098 G_CALLBACK(tb_editor_available_items_changed_cb), tbw);
1099 g_signal_connect(tbw->store_used, "row-deleted",
1100 G_CALLBACK(tb_editor_available_items_deleted_cb), tbw);
1102 /* run it */
1103 gtk_dialog_run(GTK_DIALOG(tbw->dialog));
1105 gtk_widget_destroy(tbw->dialog);
1107 g_slist_foreach(used_items, (GFunc) g_free, NULL);
1108 g_slist_free(used_items);
1109 g_list_free(all_items);
1110 tb_editor_free_path(tbw);
1111 g_free(tbw);