2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2009-2012 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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 /* Utility functions to create the toolbar */
36 #include "callbacks.h"
38 #include "geanyentryaction.h"
39 #include "geanymenubuttonaction.h"
46 #include <glib/gstdio.h>
49 GeanyToolbarPrefs toolbar_prefs
;
50 static GtkUIManager
*uim
;
51 static GtkActionGroup
*group
;
52 static GSList
*plugin_items
= NULL
;
54 /* Available toolbar actions
55 * Fields: name, stock_id, label, accelerator, tooltip, callback */
56 static const GtkActionEntry ui_entries
[] = {
57 /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */
58 { "Save", GTK_STOCK_SAVE
, NULL
, NULL
, N_("Save the current file"), G_CALLBACK(on_save1_activate
) },
59 { "SaveAs", GTK_STOCK_SAVE_AS
, NULL
, NULL
, N_("Save as"), G_CALLBACK(on_save_as1_activate
) },
60 { "SaveAll", GEANY_STOCK_SAVE_ALL
, NULL
, NULL
, N_("Save all open files"), G_CALLBACK(on_save_all1_activate
) },
61 { "Reload", GTK_STOCK_REVERT_TO_SAVED
, NULL
, NULL
, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked
) },
62 { "Close", GTK_STOCK_CLOSE
, NULL
, NULL
, N_("Close the current file"), G_CALLBACK(on_close1_activate
) },
63 { "CloseAll", GEANY_STOCK_CLOSE_ALL
, NULL
, NULL
, N_("Close all open files"), G_CALLBACK(on_close_all1_activate
) },
64 { "Cut", GTK_STOCK_CUT
, NULL
, NULL
, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate
) },
65 { "Copy", GTK_STOCK_COPY
, NULL
, NULL
, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate
) },
66 { "Paste", GTK_STOCK_PASTE
, NULL
, NULL
, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate
) },
67 { "Delete", GTK_STOCK_DELETE
, NULL
, NULL
, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate
) },
68 { "Undo", GTK_STOCK_UNDO
, NULL
, NULL
, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate
) },
69 { "Redo", GTK_STOCK_REDO
, NULL
, NULL
, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate
) },
70 { "NavBack", GTK_STOCK_GO_BACK
, NULL
, NULL
, N_("Navigate back a location"), G_CALLBACK(on_toolbutton_back_activate
) },
71 { "NavFor", GTK_STOCK_GO_FORWARD
, NULL
, NULL
, N_("Navigate forward a location"), G_CALLBACK(on_toolbutton_forward_activate
) },
72 { "Compile", GTK_STOCK_CONVERT
, N_("Compile"), NULL
, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked
) },
73 { "Run", GTK_STOCK_EXECUTE
, NULL
, NULL
, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked
) },
74 { "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
) },
75 { "ZoomIn", GTK_STOCK_ZOOM_IN
, NULL
, NULL
, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate
) },
76 { "ZoomOut", GTK_STOCK_ZOOM_OUT
, NULL
, NULL
, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate
) },
77 { "UnIndent", GTK_STOCK_UNINDENT
, NULL
, NULL
, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate
) },
78 { "Indent", GTK_STOCK_INDENT
, NULL
, NULL
, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate
) },
79 { "Search", GTK_STOCK_FIND
, NULL
, NULL
, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked
) },
80 { "Goto", GTK_STOCK_JUMP_TO
, NULL
, NULL
, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked
) },
81 { "Preferences", GTK_STOCK_PREFERENCES
, NULL
, NULL
, N_("Show the preferences dialog"), G_CALLBACK(on_preferences1_activate
) },
82 { "Quit", GTK_STOCK_QUIT
, NULL
, NULL
, N_("Quit Geany"), G_CALLBACK(on_quit1_activate
) },
83 { "Print", GTK_STOCK_PRINT
, NULL
, NULL
, N_("Print document"), G_CALLBACK(on_print1_activate
) },
84 { "Replace", GTK_STOCK_FIND_AND_REPLACE
, NULL
, NULL
, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate
) }
86 static const guint ui_entries_n
= G_N_ELEMENTS(ui_entries
);
89 /* fallback UI definition */
90 static const gchar
*toolbar_markup
=
92 "<toolbar name='GeanyToolbar'>"
93 "<toolitem action='New'/>"
94 "<toolitem action='Open'/>"
95 "<toolitem action='Save'/>"
96 "<toolitem action='SaveAll'/>"
98 "<toolitem action='Reload'/>"
99 "<toolitem action='Close'/>"
101 "<toolitem action='NavBack'/>"
102 "<toolitem action='NavFor'/>"
104 "<toolitem action='Compile'/>"
105 "<toolitem action='Build'/>"
106 "<toolitem action='Run'/>"
108 "<toolitem action='Color'/>"
110 "<toolitem action='SearchEntry'/>"
111 "<toolitem action='Search'/>"
113 "<toolitem action='GotoEntry'/>"
114 "<toolitem action='Goto'/>"
116 "<toolitem action='Quit'/>"
121 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
122 * update the widget pointer in this case (i.e. request it again) or better use
123 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
124 * toolbar is reloaded. */
125 GtkWidget
*toolbar_get_widget_by_name(const gchar
*name
)
130 g_return_val_if_fail(name
!= NULL
, NULL
);
132 path
= g_strconcat("/ui/GeanyToolbar/", name
, NULL
);
133 widget
= gtk_ui_manager_get_widget(uim
, path
);
140 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
141 * toolbar_get_widget_by_name for details(). */
142 GtkWidget
*toolbar_get_widget_child_by_name(const gchar
*name
)
144 GtkWidget
*widget
= toolbar_get_widget_by_name(name
);
146 if (G_LIKELY(widget
!= NULL
))
147 return gtk_bin_get_child(GTK_BIN(widget
));
153 GtkAction
*toolbar_get_action_by_name(const gchar
*name
)
155 g_return_val_if_fail(name
!= NULL
, NULL
);
157 return gtk_action_group_get_action(group
, name
);
161 static void toolbar_item_destroy_cb(GtkWidget
*widget
, G_GNUC_UNUSED gpointer data
)
163 plugin_items
= g_slist_remove(plugin_items
, widget
);
167 void toolbar_item_ref(GtkToolItem
*item
)
169 g_return_if_fail(item
!= NULL
);
171 plugin_items
= g_slist_append(plugin_items
, item
);
172 g_signal_connect(item
, "destroy", G_CALLBACK(toolbar_item_destroy_cb
), NULL
);
176 static GtkWidget
*toolbar_reload(const gchar
*markup
)
181 GError
*error
= NULL
;
183 static guint merge_id
= 0;
184 GtkWidget
*toolbar_new_file_menu
= NULL
;
185 GtkWidget
*toolbar_recent_files_menu
= NULL
;
186 GtkWidget
*toolbar_build_menu
= NULL
;
188 /* Cleanup old toolbar */
191 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
192 foreach_slist(l
, plugin_items
)
194 g_object_ref(l
->data
);
195 gtk_container_remove(GTK_CONTAINER(main_widgets
.toolbar
), GTK_WIDGET(l
->data
));
197 /* ref and hold the submenus of the New, Open and Build toolbar items */
198 toolbar_new_file_menu
= geany_menu_button_action_get_menu(
199 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "New")));
200 g_object_ref(toolbar_new_file_menu
);
201 toolbar_recent_files_menu
= geany_menu_button_action_get_menu(
202 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Open")));
203 g_object_ref(toolbar_recent_files_menu
);
204 toolbar_build_menu
= geany_menu_button_action_get_menu(
205 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Build")));
206 g_object_ref(toolbar_build_menu
);
209 gtk_widget_destroy(main_widgets
.toolbar
);
211 gtk_ui_manager_remove_ui(uim
, merge_id
);
212 gtk_ui_manager_ensure_update(uim
);
217 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, markup
, -1, &error
);
221 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
222 filename
= g_build_filename(app
->configdir
, "ui_toolbar.xml", NULL
);
223 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
226 if (! g_error_matches(error
, G_FILE_ERROR
, G_FILE_ERROR_NOENT
))
227 geany_debug("Loading user toolbar UI definition failed (%s).", error
->message
);
231 SETPTR(filename
, g_build_filename(app
->datadir
, "ui_toolbar.xml", NULL
));
232 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
238 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
241 /* finally load the internally defined markup as fallback */
242 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, toolbar_markup
, -1, NULL
);
244 main_widgets
.toolbar
= gtk_ui_manager_get_widget(uim
, "/ui/GeanyToolbar");
245 ui_init_toolbar_widgets();
247 /* add the toolbar again to the main window */
248 if (toolbar_prefs
.append_to_menu
)
250 GtkWidget
*hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
251 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
252 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
256 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
258 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
259 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
261 gtk_widget_show(main_widgets
.toolbar
);
263 /* re-add und unref the plugin toolbar items */
264 i
= toolbar_get_insert_position();
265 foreach_slist(l
, plugin_items
)
267 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
), l
->data
, i
);
268 g_object_unref(l
->data
);
271 /* re-add und unref the submenus of menu toolbar items */
272 if (toolbar_new_file_menu
!= NULL
)
274 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
275 gtk_action_group_get_action(group
, "New")), toolbar_new_file_menu
);
276 g_object_unref(toolbar_new_file_menu
);
278 if (toolbar_recent_files_menu
!= NULL
)
280 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
281 gtk_action_group_get_action(group
, "Open")), toolbar_recent_files_menu
);
282 g_object_unref(toolbar_recent_files_menu
);
284 if (toolbar_build_menu
!= NULL
)
286 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
287 gtk_action_group_get_action(group
, "Build")), toolbar_build_menu
);
288 g_object_unref(toolbar_build_menu
);
291 /* update button states */
292 if (main_status
.main_window_realized
)
294 GeanyDocument
*doc
= document_get_current();
295 gboolean doc_changed
= (doc
!= NULL
) ? doc
->changed
: FALSE
;
297 ui_document_buttons_update();
298 ui_save_buttons_toggle(doc_changed
); /* update save all */
299 ui_update_popup_reundo_items(doc
);
301 toolbar_apply_settings();
305 g_signal_connect(main_widgets
.toolbar
, "button-press-event",
306 G_CALLBACK(toolbar_popup_menu
), NULL
);
307 g_signal_connect(main_widgets
.toolbar
, "key-press-event",
308 G_CALLBACK(on_escape_key_press_event
), NULL
);
310 /* We don't need to disconnect those signals as this is done automatically when the entry
311 * widgets are destroyed, happens when the toolbar itself is destroyed. */
312 entry
= toolbar_get_widget_child_by_name("SearchEntry");
314 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
315 entry
= toolbar_get_widget_child_by_name("GotoEntry");
317 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
319 return main_widgets
.toolbar
;
323 static void toolbar_notify_style_cb(GObject
*object
, GParamSpec
*arg1
, gpointer data
)
325 const gchar
*arg_name
= g_param_spec_get_name(arg1
);
328 if (toolbar_prefs
.use_gtk_default_style
&& utils_str_equal(arg_name
, "gtk-toolbar-style"))
330 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_style
);
331 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
333 else if (toolbar_prefs
.use_gtk_default_icon
&& utils_str_equal(arg_name
, "gtk-toolbar-size"))
335 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_size
);
336 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
341 GtkWidget
*toolbar_init(void)
344 GtkAction
*action_new
;
345 GtkAction
*action_open
;
346 GtkAction
*action_build
;
347 GtkAction
*action_searchentry
;
348 GtkAction
*action_gotoentry
;
349 GtkSettings
*gtk_settings
;
351 uim
= gtk_ui_manager_new();
352 group
= gtk_action_group_new("GeanyToolbar");
354 gtk_action_group_set_translation_domain(group
, GETTEXT_PACKAGE
);
355 gtk_action_group_add_actions(group
, ui_entries
, ui_entries_n
, NULL
);
357 /* Create our custom actions */
358 action_new
= geany_menu_button_action_new(
360 _("Create a new file"),
361 _("Create a new file from a template"),
363 g_signal_connect(action_new
, "button-clicked", G_CALLBACK(on_new1_activate
), NULL
);
364 gtk_action_group_add_action(group
, action_new
);
366 action_open
= geany_menu_button_action_new(
368 _("Open an existing file"),
369 _("Open a recent file"),
371 g_signal_connect(action_open
, "button-clicked", G_CALLBACK(on_open1_activate
), NULL
);
372 gtk_action_group_add_action(group
, action_open
);
374 action_build
= geany_menu_button_action_new(
376 _("Build the current file"),
377 _("Choose more build actions"),
379 g_signal_connect(action_build
, "button-clicked",
380 G_CALLBACK(build_toolbutton_build_clicked
), NULL
);
381 gtk_action_group_add_action(group
, action_build
);
383 action_searchentry
= geany_entry_action_new(
384 "SearchEntry", _("Search Field"), _("Find the entered text in the current file"), FALSE
);
385 g_signal_connect(action_searchentry
, "entry-activate",
386 G_CALLBACK(on_toolbar_search_entry_activate
), GINT_TO_POINTER(FALSE
));
387 g_signal_connect(action_searchentry
, "entry-activate-backward",
388 G_CALLBACK(on_toolbar_search_entry_activate
), GINT_TO_POINTER(TRUE
));
389 g_signal_connect(action_searchentry
, "entry-changed",
390 G_CALLBACK(on_toolbar_search_entry_changed
), NULL
);
391 gtk_action_group_add_action(group
, action_searchentry
);
393 action_gotoentry
= geany_entry_action_new(
394 "GotoEntry", _("Goto Field"), _("Jump to the entered line number"), TRUE
);
395 g_signal_connect(action_gotoentry
, "entry-activate",
396 G_CALLBACK(on_toolbutton_goto_entry_activate
), NULL
);
397 gtk_action_group_add_action(group
, action_gotoentry
);
399 gtk_ui_manager_insert_action_group(uim
, group
, 0);
401 toolbar
= toolbar_reload(NULL
);
402 #if GTK_CHECK_VERSION(3, 0, 0)
403 gtk_style_context_add_class(gtk_widget_get_style_context(toolbar
), "primary-toolbar");
406 gtk_settings
= gtk_widget_get_settings(GTK_WIDGET(toolbar
));
407 if (gtk_settings
!= NULL
)
409 g_signal_connect(gtk_settings
, "notify::gtk-toolbar-style",
410 G_CALLBACK(toolbar_notify_style_cb
), NULL
);
417 void toolbar_update_ui(void)
419 static GtkWidget
*hbox_menubar
= NULL
;
420 static GtkWidget
*menubar
= NULL
;
422 GtkToolItem
*first_item
;
425 { /* cache widget pointers */
426 hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
427 menubar
= ui_lookup_widget(main_widgets
.window
, "menubar1");
429 /* the separator between the menubar and the toolbar */
430 first_item
= gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets
.toolbar
), 0);
431 if (first_item
!= NULL
&& GTK_IS_SEPARATOR_TOOL_ITEM(first_item
))
433 gtk_widget_destroy(GTK_WIDGET(first_item
));
436 parent
= gtk_widget_get_parent(main_widgets
.toolbar
);
438 if (toolbar_prefs
.append_to_menu
)
440 GtkWidget
*menubar_toolbar_separator
;
444 if (parent
!= hbox_menubar
)
445 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
447 g_object_ref(main_widgets
.toolbar
);
449 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
450 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
451 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
453 g_object_unref(main_widgets
.toolbar
);
457 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
459 /* the separator between the menubar and the toolbar */
460 menubar_toolbar_separator
= GTK_WIDGET(gtk_separator_tool_item_new());
461 gtk_widget_show(menubar_toolbar_separator
);
462 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
),
463 GTK_TOOL_ITEM(menubar_toolbar_separator
), 0);
467 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
473 g_object_ref(main_widgets
.toolbar
);
475 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
476 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
477 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
479 g_object_unref(main_widgets
.toolbar
);
484 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
485 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
488 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
489 * hbox and not expand it if the toolbar is appended */
490 gtk_box_set_child_packing(GTK_BOX(hbox_menubar
), menubar
,
491 ! (toolbar_prefs
.visible
&& toolbar_prefs
.append_to_menu
), TRUE
, 0, GTK_PACK_START
);
495 /* Returns the position for adding new toolbar items. The returned position can be used
496 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
497 * with @a geany->main_widgets->toolbar.
498 * The position is always the last one before the Quit button or the very last position if the
499 * Quit button is not the last toolbar item.
501 * @return The position for new toolbar items.
503 gint
toolbar_get_insert_position(void)
505 GtkWidget
*quit
= toolbar_get_widget_by_name("Quit");
506 gint quit_pos
= -1, pos
;
509 quit_pos
= gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets
.toolbar
), GTK_TOOL_ITEM(quit
));
511 pos
= gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets
.toolbar
));
512 if (quit_pos
== (pos
- 1))
514 /* if the toolbar item before the quit button is a separator, insert new items before */
515 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
516 GTK_TOOLBAR(main_widgets
.toolbar
), quit_pos
- 1)))
520 /* else return the position of the quit button to insert new items before */
528 void toolbar_finalize(void)
530 GeanyMenubuttonAction
*open_action
= GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"));
531 g_object_unref(geany_menu_button_action_get_menu(open_action
));
532 geany_menu_button_action_set_menu(open_action
, NULL
);
534 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
536 g_object_unref(group
);
538 g_slist_free(plugin_items
);
542 void toolbar_show_hide(void)
544 ignore_callback
= TRUE
;
545 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
546 ui_lookup_widget(main_widgets
.window
, "menu_show_toolbar1")), toolbar_prefs
.visible
);
547 ui_widget_show_hide(main_widgets
.toolbar
, toolbar_prefs
.visible
);
548 ignore_callback
= FALSE
;
552 /* sets the icon style of the toolbar */
553 static void toolbar_set_icon_style(void)
557 icon_style
= toolbar_prefs
.icon_style
;
559 if (toolbar_prefs
.use_gtk_default_style
)
560 icon_style
= ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs
.icon_style
);
562 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), icon_style
);
566 /* sets the icon size of the toolbar */
567 static void toolbar_set_icon_size(void)
571 icon_size
= toolbar_prefs
.icon_size
;
573 if (toolbar_prefs
.use_gtk_default_icon
)
574 icon_size
= ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs
.icon_size
);
576 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), icon_size
);
580 void toolbar_apply_settings(void)
582 toolbar_set_icon_style();
583 toolbar_set_icon_size();
587 #define TB_EDITOR_SEPARATOR _("Separator")
588 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
593 GtkTreeView
*tree_available
;
594 GtkTreeView
*tree_used
;
596 GtkListStore
*store_available
;
597 GtkListStore
*store_used
;
599 GtkTreePath
*last_drag_path
;
600 GtkTreeViewDropPosition last_drag_pos
;
602 GtkWidget
*drag_source
;
605 static const GtkTargetEntry tb_editor_dnd_targets
[] =
607 { "GEANY_TB_EDITOR_ROW", 0, 0 }
609 static const gint tb_editor_dnd_targets_len
= G_N_ELEMENTS(tb_editor_dnd_targets
);
613 TB_EDITOR_COL_ACTION
,
619 static void tb_editor_handler_start_element(GMarkupParseContext
*context
, const gchar
*element_name
,
620 const gchar
**attribute_names
,
621 const gchar
**attribute_values
, gpointer data
,
625 GSList
**actions
= data
;
627 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
628 if (utils_str_equal(element_name
, "separator"))
629 *actions
= g_slist_append(*actions
, g_strdup(TB_EDITOR_SEPARATOR
));
631 for (i
= 0; attribute_names
[i
] != NULL
; i
++)
633 if (utils_str_equal(attribute_names
[i
], "action"))
635 *actions
= g_slist_append(*actions
, g_strdup(attribute_values
[i
]));
641 static const GMarkupParser tb_editor_xml_parser
=
643 tb_editor_handler_start_element
, NULL
, NULL
, NULL
, NULL
647 static GSList
*tb_editor_parse_ui(const gchar
*buffer
, gssize length
, GError
**error
)
649 GMarkupParseContext
*context
;
652 context
= g_markup_parse_context_new(&tb_editor_xml_parser
, 0, &list
, NULL
);
653 g_markup_parse_context_parse(context
, buffer
, length
, error
);
654 g_markup_parse_context_free(context
);
660 static void tb_editor_set_item_values(const gchar
*name
, GtkListStore
*store
, GtkTreeIter
*iter
)
664 gchar
*label_clean
= NULL
;
667 action
= gtk_action_group_get_action(group
, name
);
670 if (utils_str_equal(name
, TB_EDITOR_SEPARATOR
))
671 label_clean
= g_strdup(TB_EDITOR_SEPARATOR_LABEL
);
677 g_object_get(action
, "icon-name", &icon
, NULL
);
679 g_object_get(action
, "stock-id", &icon
, NULL
);
681 g_object_get(action
, "label", &label
, NULL
);
683 label_clean
= utils_str_remove_chars(g_strdup(label
), "_");
686 gtk_list_store_set(store
, iter
,
687 TB_EDITOR_COL_ACTION
, name
,
688 TB_EDITOR_COL_LABEL
, label_clean
,
689 TB_EDITOR_COL_ICON
, icon
,
698 static void tb_editor_scroll_to_iter(GtkTreeView
*treeview
, GtkTreeIter
*iter
)
700 GtkTreePath
*path
= gtk_tree_model_get_path(gtk_tree_view_get_model(treeview
), iter
);
701 gtk_tree_view_scroll_to_cell(treeview
, path
, NULL
, TRUE
, 0.5, 0.0);
702 gtk_tree_path_free(path
);
706 static void tb_editor_free_path(TBEditorWidget
*tbw
)
708 if (tbw
->last_drag_path
!= NULL
)
710 gtk_tree_path_free(tbw
->last_drag_path
);
711 tbw
->last_drag_path
= NULL
;
716 static void tb_editor_btn_remove_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
718 GtkTreeModel
*model_used
;
719 GtkTreeSelection
*selection_used
;
720 GtkTreeIter iter_used
, iter_new
;
723 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
724 if (gtk_tree_selection_get_selected(selection_used
, &model_used
, &iter_used
))
726 gtk_tree_model_get(model_used
, &iter_used
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
727 if (gtk_list_store_remove(tbw
->store_used
, &iter_used
))
728 gtk_tree_selection_select_iter(selection_used
, &iter_used
);
730 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
732 gtk_list_store_append(tbw
->store_available
, &iter_new
);
733 tb_editor_set_item_values(action_name
, tbw
->store_available
, &iter_new
);
734 tb_editor_scroll_to_iter(tbw
->tree_available
, &iter_new
);
742 static void tb_editor_btn_add_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
744 GtkTreeModel
*model_available
;
745 GtkTreeSelection
*selection_available
, *selection_used
;
746 GtkTreeIter iter_available
, iter_new
, iter_selected
;
749 selection_available
= gtk_tree_view_get_selection(tbw
->tree_available
);
750 if (gtk_tree_selection_get_selected(selection_available
, &model_available
, &iter_available
))
752 gtk_tree_model_get(model_available
, &iter_available
,
753 TB_EDITOR_COL_ACTION
, &action_name
, -1);
754 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
756 if (gtk_list_store_remove(tbw
->store_available
, &iter_available
))
757 gtk_tree_selection_select_iter(selection_available
, &iter_available
);
760 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
761 if (gtk_tree_selection_get_selected(selection_used
, NULL
, &iter_selected
))
762 gtk_list_store_insert_before(tbw
->store_used
, &iter_new
, &iter_selected
);
764 gtk_list_store_append(tbw
->store_used
, &iter_new
);
766 tb_editor_set_item_values(action_name
, tbw
->store_used
, &iter_new
);
767 tb_editor_scroll_to_iter(tbw
->tree_used
, &iter_new
);
774 static gboolean
tb_editor_drag_motion_cb(GtkWidget
*widget
, GdkDragContext
*drag_context
,
775 gint x
, gint y
, guint ltime
, TBEditorWidget
*tbw
)
777 if (tbw
->last_drag_path
!= NULL
)
778 gtk_tree_path_free(tbw
->last_drag_path
);
779 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget
),
780 &(tbw
->last_drag_path
), &(tbw
->last_drag_pos
));
786 static void tb_editor_drag_data_get_cb(GtkWidget
*widget
, GdkDragContext
*context
,
787 GtkSelectionData
*data
, guint info
, guint ltime
,
791 GtkTreeSelection
*selection
;
796 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
797 if (! gtk_tree_selection_get_selected(selection
, &model
, &iter
))
800 gtk_tree_model_get(model
, &iter
, TB_EDITOR_COL_ACTION
, &name
, -1);
801 if (G_UNLIKELY(EMPTY(name
)))
807 atom
= gdk_atom_intern(tb_editor_dnd_targets
[0].target
, FALSE
);
808 gtk_selection_data_set(data
, atom
, 8, (guchar
*) name
, strlen(name
));
812 tbw
->drag_source
= widget
;
816 static void tb_editor_drag_data_rcvd_cb(GtkWidget
*widget
, GdkDragContext
*context
,
817 gint x
, gint y
, GtkSelectionData
*data
, guint info
,
818 guint ltime
, TBEditorWidget
*tbw
)
820 GtkTreeView
*tree
= GTK_TREE_VIEW(widget
);
821 gboolean del
= FALSE
;
823 if (gtk_selection_data_get_length(data
) >= 0 && gtk_selection_data_get_format(data
) == 8)
828 text
= (gchar
*) gtk_selection_data_get_data(data
);
829 is_sep
= utils_str_equal(text
, TB_EDITOR_SEPARATOR
);
830 /* If the source of the action is equal to the target, we do just re-order and so need
831 * to delete the separator to get it moved, not just copied. */
832 if (is_sep
&& widget
== tbw
->drag_source
)
835 if (tree
!= tbw
->tree_available
|| ! is_sep
)
837 GtkTreeIter iter
, iter_before
, *iter_before_ptr
;
838 GtkListStore
*store
= GTK_LIST_STORE(gtk_tree_view_get_model(tree
));
840 if (tbw
->last_drag_path
!= NULL
)
842 gtk_tree_model_get_iter(GTK_TREE_MODEL(store
), &iter_before
, tbw
->last_drag_path
);
844 if (gtk_list_store_iter_is_valid(store
, &iter_before
))
845 iter_before_ptr
= &iter_before
;
847 iter_before_ptr
= NULL
;
849 if (tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_BEFORE
||
850 tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
)
851 gtk_list_store_insert_before(store
, &iter
, iter_before_ptr
);
853 gtk_list_store_insert_after(store
, &iter
, iter_before_ptr
);
856 gtk_list_store_append(store
, &iter
);
858 tb_editor_set_item_values(text
, store
, &iter
);
859 tb_editor_scroll_to_iter(tree
, &iter
);
861 if (tree
!= tbw
->tree_used
|| ! is_sep
)
865 tbw
->drag_source
= NULL
; /* reset the value just to be sure */
866 tb_editor_free_path(tbw
);
867 gtk_drag_finish(context
, TRUE
, del
, ltime
);
871 static gboolean
tb_editor_foreach_used(GtkTreeModel
*model
, GtkTreePath
*path
,
872 GtkTreeIter
*iter
, gpointer data
)
876 gtk_tree_model_get(model
, iter
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
878 if (utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
879 g_string_append_printf(data
, "\t\t<separator/>\n");
880 else if (G_LIKELY(!EMPTY(action_name
)))
881 g_string_append_printf(data
, "\t\t<toolitem action='%s' />\n", action_name
);
888 static void tb_editor_write_markup(TBEditorWidget
*tbw
)
890 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
891 const gchar
*template = "<ui>\n<!--\n\
892 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
893 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
894 You can re-order all items and freely add and remove available actions.\n\
895 You cannot add new actions which are not listed in the documentation.\n\
896 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
897 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
898 editor in Geany.\n\n\
899 A list of available actions can be found in the documentation included with Geany or\n\
900 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
901 \t<toolbar name='GeanyToolbar'>\n";
903 GString
*str
= g_string_new(template);
905 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw
->store_used
), tb_editor_foreach_used
, str
);
907 g_string_append(str
, "\n\t</toolbar>\n</ui>\n");
909 toolbar_reload(str
->str
);
911 filename
= g_build_filename(app
->configdir
, "ui_toolbar.xml", NULL
);
912 utils_write_file(filename
, str
->str
);
915 g_string_free(str
, TRUE
);
919 static void tb_editor_available_items_changed_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
920 GtkTreeIter
*arg2
, TBEditorWidget
*tbw
)
922 tb_editor_write_markup(tbw
);
926 static void tb_editor_available_items_deleted_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
929 tb_editor_write_markup(tbw
);
933 static TBEditorWidget
*tb_editor_create_dialog(GtkWindow
*parent
)
935 GtkWidget
*dialog
, *vbox
, *hbox
, *vbox_buttons
, *button_add
, *button_remove
;
936 GtkWidget
*swin_available
, *swin_used
, *tree_available
, *tree_used
, *label
;
937 GtkCellRenderer
*text_renderer
, *icon_renderer
;
938 GtkTreeViewColumn
*column
;
939 TBEditorWidget
*tbw
= g_new(TBEditorWidget
, 1);
942 parent
= GTK_WINDOW(main_widgets
.window
);
944 dialog
= gtk_dialog_new_with_buttons(_("Customize Toolbar"),
946 GTK_DIALOG_DESTROY_WITH_PARENT
,
947 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
, NULL
);
948 vbox
= ui_dialog_vbox_new(GTK_DIALOG(dialog
));
949 gtk_box_set_spacing(GTK_BOX(vbox
), 6);
950 gtk_widget_set_name(dialog
, "GeanyDialog");
951 gtk_window_set_default_size(GTK_WINDOW(dialog
), -1, 400);
952 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_CLOSE
);
954 tbw
->store_available
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
955 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
956 tbw
->store_used
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
957 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
959 label
= gtk_label_new(
960 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
961 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
963 tree_available
= gtk_tree_view_new();
964 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available
), GTK_TREE_MODEL(tbw
->store_available
));
965 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available
), TRUE
);
966 gtk_tree_sortable_set_sort_column_id(
967 GTK_TREE_SORTABLE(tbw
->store_available
), TB_EDITOR_COL_LABEL
, GTK_SORT_ASCENDING
);
969 icon_renderer
= gtk_cell_renderer_pixbuf_new();
970 column
= gtk_tree_view_column_new_with_attributes(
971 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
972 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
974 text_renderer
= gtk_cell_renderer_text_new();
975 column
= gtk_tree_view_column_new_with_attributes(
976 _("Available Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
977 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
979 swin_available
= gtk_scrolled_window_new(NULL
, NULL
);
980 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available
),
981 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
982 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available
), GTK_SHADOW_IN
);
983 gtk_container_add(GTK_CONTAINER(swin_available
), tree_available
);
985 tree_used
= gtk_tree_view_new();
986 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used
), GTK_TREE_MODEL(tbw
->store_used
));
987 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used
), TRUE
);
988 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used
), TRUE
);
990 icon_renderer
= gtk_cell_renderer_pixbuf_new();
991 column
= gtk_tree_view_column_new_with_attributes(
992 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
993 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
995 text_renderer
= gtk_cell_renderer_text_new();
996 column
= gtk_tree_view_column_new_with_attributes(
997 _("Displayed Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
998 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
1000 swin_used
= gtk_scrolled_window_new(NULL
, NULL
);
1001 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used
),
1002 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1003 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used
), GTK_SHADOW_IN
);
1004 gtk_container_add(GTK_CONTAINER(swin_used
), tree_used
);
1007 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available
), GDK_BUTTON1_MASK
,
1008 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1009 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available
),
1010 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1011 g_signal_connect(tree_available
, "drag-data-get",
1012 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
1013 g_signal_connect(tree_available
, "drag-data-received",
1014 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
1015 g_signal_connect(tree_available
, "drag-motion",
1016 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
1018 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used
), GDK_BUTTON1_MASK
,
1019 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1020 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used
),
1021 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1022 g_signal_connect(tree_used
, "drag-data-get",
1023 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
1024 g_signal_connect(tree_used
, "drag-data-received",
1025 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
1026 g_signal_connect(tree_used
, "drag-motion",
1027 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
1030 button_add
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, NULL
);
1031 button_remove
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, NULL
);
1032 g_signal_connect(button_add
, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb
), tbw
);
1033 g_signal_connect(button_remove
, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb
), tbw
);
1035 vbox_buttons
= gtk_vbox_new(FALSE
, 6);
1036 /* FIXME this is a little hack'ish, any better ideas? */
1037 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1038 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_add
, FALSE
, FALSE
, 0);
1039 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_remove
, FALSE
, FALSE
, 0);
1040 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1042 hbox
= gtk_hbox_new(FALSE
, 6);
1043 gtk_box_pack_start(GTK_BOX(hbox
), swin_available
, TRUE
, TRUE
, 0);
1044 gtk_box_pack_start(GTK_BOX(hbox
), vbox_buttons
, FALSE
, FALSE
, 0);
1045 gtk_box_pack_start(GTK_BOX(hbox
), swin_used
, TRUE
, TRUE
, 0);
1047 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 6);
1048 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, TRUE
, 0);
1050 gtk_widget_show_all(vbox
);
1052 g_object_unref(tbw
->store_available
);
1053 g_object_unref(tbw
->store_used
);
1055 tbw
->dialog
= dialog
;
1056 tbw
->tree_available
= GTK_TREE_VIEW(tree_available
);
1057 tbw
->tree_used
= GTK_TREE_VIEW(tree_used
);
1059 tbw
->last_drag_path
= NULL
;
1065 void toolbar_configure(GtkWindow
*parent
)
1068 GSList
*sl
, *used_items
;
1069 GList
*l
, *all_items
;
1071 TBEditorWidget
*tbw
;
1073 /* read the current active toolbar items */
1074 markup
= gtk_ui_manager_get_ui(uim
);
1075 used_items
= tb_editor_parse_ui(markup
, -1, NULL
);
1078 /* get all available actions */
1079 all_items
= gtk_action_group_list_actions(group
);
1081 /* create the GUI */
1082 tbw
= tb_editor_create_dialog(parent
);
1084 /* fill the stores */
1085 gtk_list_store_insert_with_values(tbw
->store_available
, NULL
, -1,
1086 TB_EDITOR_COL_ACTION
, TB_EDITOR_SEPARATOR
,
1087 TB_EDITOR_COL_LABEL
, TB_EDITOR_SEPARATOR_LABEL
,
1089 foreach_list(l
, all_items
)
1091 const gchar
*name
= gtk_action_get_name(l
->data
);
1093 if (g_slist_find_custom(used_items
, name
, (GCompareFunc
) strcmp
) == NULL
)
1097 gtk_list_store_append(tbw
->store_available
, &iter
);
1098 tb_editor_set_item_values(name
, tbw
->store_available
, &iter
);
1101 foreach_slist(sl
, used_items
)
1105 gtk_list_store_append(tbw
->store_used
, &iter
);
1106 tb_editor_set_item_values(sl
->data
, tbw
->store_used
, &iter
);
1108 /* select first item */
1109 path
= gtk_tree_path_new_from_string("0");
1110 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw
->tree_used
), path
);
1111 gtk_tree_path_free(path
);
1113 /* connect the changed signals after populating the store */
1114 g_signal_connect(tbw
->store_used
, "row-changed",
1115 G_CALLBACK(tb_editor_available_items_changed_cb
), tbw
);
1116 g_signal_connect(tbw
->store_used
, "row-deleted",
1117 G_CALLBACK(tb_editor_available_items_deleted_cb
), tbw
);
1120 gtk_dialog_run(GTK_DIALOG(tbw
->dialog
));
1122 gtk_widget_destroy(tbw
->dialog
);
1124 g_slist_foreach(used_items
, (GFunc
) g_free
, NULL
);
1125 g_slist_free(used_items
);
1126 g_list_free(all_items
);
1127 tb_editor_free_path(tbw
);