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
;
421 GtkWidget
*menubar_toolbar_separator
= NULL
;
423 GtkToolItem
*first_item
;
426 { /* cache widget pointers */
427 hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
428 menubar
= ui_lookup_widget(main_widgets
.window
, "menubar1");
430 /* the separator between the menubar and the toolbar */
431 first_item
= gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets
.toolbar
), 0);
432 if (first_item
!= NULL
&& GTK_IS_SEPARATOR_TOOL_ITEM(first_item
))
434 gtk_widget_destroy(GTK_WIDGET(first_item
));
437 parent
= gtk_widget_get_parent(main_widgets
.toolbar
);
439 if (toolbar_prefs
.append_to_menu
)
443 if (parent
!= hbox_menubar
)
444 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
446 g_object_ref(main_widgets
.toolbar
);
448 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
449 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
450 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
452 g_object_unref(main_widgets
.toolbar
);
456 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
458 /* the separator between the menubar and the toolbar */
459 menubar_toolbar_separator
= GTK_WIDGET(gtk_separator_tool_item_new());
460 gtk_widget_show(menubar_toolbar_separator
);
461 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
),
462 GTK_TOOL_ITEM(menubar_toolbar_separator
), 0);
466 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
472 g_object_ref(main_widgets
.toolbar
);
474 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
475 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
476 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
478 g_object_unref(main_widgets
.toolbar
);
483 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
484 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
487 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
488 * hbox and not expand it if the toolbar is appended */
489 gtk_box_set_child_packing(GTK_BOX(hbox_menubar
), menubar
,
490 ! (toolbar_prefs
.visible
&& toolbar_prefs
.append_to_menu
), TRUE
, 0, GTK_PACK_START
);
494 /* Returns the position for adding new toolbar items. The returned position can be used
495 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
496 * with @a geany->main_widgets->toolbar.
497 * The position is always the last one before the Quit button or the very last position if the
498 * Quit button is not the last toolbar item.
500 * @return The position for new toolbar items.
502 gint
toolbar_get_insert_position(void)
504 GtkWidget
*quit
= toolbar_get_widget_by_name("Quit");
505 gint quit_pos
= -1, pos
;
508 quit_pos
= gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets
.toolbar
), GTK_TOOL_ITEM(quit
));
510 pos
= gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets
.toolbar
));
511 if (quit_pos
== (pos
- 1))
513 /* if the toolbar item before the quit button is a separator, insert new items before */
514 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
515 GTK_TOOLBAR(main_widgets
.toolbar
), quit_pos
- 1)))
519 /* else return the position of the quit button to insert new items before */
527 void toolbar_finalize(void)
529 GeanyMenubuttonAction
*open_action
= GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"));
530 g_object_unref(geany_menu_button_action_get_menu(open_action
));
531 geany_menu_button_action_set_menu(open_action
, NULL
);
533 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
535 g_object_unref(group
);
537 g_slist_free(plugin_items
);
541 void toolbar_show_hide(void)
543 ignore_callback
= TRUE
;
544 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
545 ui_lookup_widget(main_widgets
.window
, "menu_show_toolbar1")), toolbar_prefs
.visible
);
546 ui_widget_show_hide(main_widgets
.toolbar
, toolbar_prefs
.visible
);
547 ignore_callback
= FALSE
;
551 /* sets the icon style of the toolbar */
552 static void toolbar_set_icon_style(void)
556 icon_style
= toolbar_prefs
.icon_style
;
558 if (toolbar_prefs
.use_gtk_default_style
)
559 icon_style
= ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs
.icon_style
);
561 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), icon_style
);
565 /* sets the icon size of the toolbar */
566 static void toolbar_set_icon_size(void)
570 icon_size
= toolbar_prefs
.icon_size
;
572 if (toolbar_prefs
.use_gtk_default_icon
)
573 icon_size
= ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs
.icon_size
);
575 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), icon_size
);
579 void toolbar_apply_settings(void)
581 toolbar_set_icon_style();
582 toolbar_set_icon_size();
586 #define TB_EDITOR_SEPARATOR _("Separator")
587 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
592 GtkTreeView
*tree_available
;
593 GtkTreeView
*tree_used
;
595 GtkListStore
*store_available
;
596 GtkListStore
*store_used
;
598 GtkTreePath
*last_drag_path
;
599 GtkTreeViewDropPosition last_drag_pos
;
601 GtkWidget
*drag_source
;
604 static const GtkTargetEntry tb_editor_dnd_targets
[] =
606 { "GEANY_TB_EDITOR_ROW", 0, 0 }
608 static const gint tb_editor_dnd_targets_len
= G_N_ELEMENTS(tb_editor_dnd_targets
);
612 TB_EDITOR_COL_ACTION
,
618 static void tb_editor_handler_start_element(GMarkupParseContext
*context
, const gchar
*element_name
,
619 const gchar
**attribute_names
,
620 const gchar
**attribute_values
, gpointer data
,
624 GSList
**actions
= data
;
626 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
627 if (utils_str_equal(element_name
, "separator"))
628 *actions
= g_slist_append(*actions
, g_strdup(TB_EDITOR_SEPARATOR
));
630 for (i
= 0; attribute_names
[i
] != NULL
; i
++)
632 if (utils_str_equal(attribute_names
[i
], "action"))
634 *actions
= g_slist_append(*actions
, g_strdup(attribute_values
[i
]));
640 static const GMarkupParser tb_editor_xml_parser
=
642 tb_editor_handler_start_element
, NULL
, NULL
, NULL
, NULL
646 static GSList
*tb_editor_parse_ui(const gchar
*buffer
, gssize length
, GError
**error
)
648 GMarkupParseContext
*context
;
651 context
= g_markup_parse_context_new(&tb_editor_xml_parser
, 0, &list
, NULL
);
652 g_markup_parse_context_parse(context
, buffer
, length
, error
);
653 g_markup_parse_context_free(context
);
659 static void tb_editor_set_item_values(const gchar
*name
, GtkListStore
*store
, GtkTreeIter
*iter
)
663 gchar
*label_clean
= NULL
;
666 action
= gtk_action_group_get_action(group
, name
);
669 if (utils_str_equal(name
, TB_EDITOR_SEPARATOR
))
670 label_clean
= g_strdup(TB_EDITOR_SEPARATOR_LABEL
);
676 g_object_get(action
, "icon-name", &icon
, NULL
);
678 g_object_get(action
, "stock-id", &icon
, NULL
);
680 g_object_get(action
, "label", &label
, NULL
);
682 label_clean
= utils_str_remove_chars(g_strdup(label
), "_");
685 gtk_list_store_set(store
, iter
,
686 TB_EDITOR_COL_ACTION
, name
,
687 TB_EDITOR_COL_LABEL
, label_clean
,
688 TB_EDITOR_COL_ICON
, icon
,
697 static void tb_editor_scroll_to_iter(GtkTreeView
*treeview
, GtkTreeIter
*iter
)
699 GtkTreePath
*path
= gtk_tree_model_get_path(gtk_tree_view_get_model(treeview
), iter
);
700 gtk_tree_view_scroll_to_cell(treeview
, path
, NULL
, TRUE
, 0.5, 0.0);
701 gtk_tree_path_free(path
);
705 static void tb_editor_free_path(TBEditorWidget
*tbw
)
707 if (tbw
->last_drag_path
!= NULL
)
709 gtk_tree_path_free(tbw
->last_drag_path
);
710 tbw
->last_drag_path
= NULL
;
715 static void tb_editor_btn_remove_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
717 GtkTreeModel
*model_used
;
718 GtkTreeSelection
*selection_used
;
719 GtkTreeIter iter_used
, iter_new
;
722 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
723 if (gtk_tree_selection_get_selected(selection_used
, &model_used
, &iter_used
))
725 gtk_tree_model_get(model_used
, &iter_used
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
726 if (gtk_list_store_remove(tbw
->store_used
, &iter_used
))
727 gtk_tree_selection_select_iter(selection_used
, &iter_used
);
729 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
731 gtk_list_store_append(tbw
->store_available
, &iter_new
);
732 tb_editor_set_item_values(action_name
, tbw
->store_available
, &iter_new
);
733 tb_editor_scroll_to_iter(tbw
->tree_available
, &iter_new
);
741 static void tb_editor_btn_add_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
743 GtkTreeModel
*model_available
;
744 GtkTreeSelection
*selection_available
, *selection_used
;
745 GtkTreeIter iter_available
, iter_new
, iter_selected
;
748 selection_available
= gtk_tree_view_get_selection(tbw
->tree_available
);
749 if (gtk_tree_selection_get_selected(selection_available
, &model_available
, &iter_available
))
751 gtk_tree_model_get(model_available
, &iter_available
,
752 TB_EDITOR_COL_ACTION
, &action_name
, -1);
753 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
755 if (gtk_list_store_remove(tbw
->store_available
, &iter_available
))
756 gtk_tree_selection_select_iter(selection_available
, &iter_available
);
759 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
760 if (gtk_tree_selection_get_selected(selection_used
, NULL
, &iter_selected
))
761 gtk_list_store_insert_before(tbw
->store_used
, &iter_new
, &iter_selected
);
763 gtk_list_store_append(tbw
->store_used
, &iter_new
);
765 tb_editor_set_item_values(action_name
, tbw
->store_used
, &iter_new
);
766 tb_editor_scroll_to_iter(tbw
->tree_used
, &iter_new
);
773 static gboolean
tb_editor_drag_motion_cb(GtkWidget
*widget
, GdkDragContext
*drag_context
,
774 gint x
, gint y
, guint ltime
, TBEditorWidget
*tbw
)
776 if (tbw
->last_drag_path
!= NULL
)
777 gtk_tree_path_free(tbw
->last_drag_path
);
778 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget
),
779 &(tbw
->last_drag_path
), &(tbw
->last_drag_pos
));
785 static void tb_editor_drag_data_get_cb(GtkWidget
*widget
, GdkDragContext
*context
,
786 GtkSelectionData
*data
, guint info
, guint ltime
,
790 GtkTreeSelection
*selection
;
795 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
796 if (! gtk_tree_selection_get_selected(selection
, &model
, &iter
))
799 gtk_tree_model_get(model
, &iter
, TB_EDITOR_COL_ACTION
, &name
, -1);
800 if (G_UNLIKELY(EMPTY(name
)))
806 atom
= gdk_atom_intern(tb_editor_dnd_targets
[0].target
, FALSE
);
807 gtk_selection_data_set(data
, atom
, 8, (guchar
*) name
, strlen(name
));
811 tbw
->drag_source
= widget
;
815 static void tb_editor_drag_data_rcvd_cb(GtkWidget
*widget
, GdkDragContext
*context
,
816 gint x
, gint y
, GtkSelectionData
*data
, guint info
,
817 guint ltime
, TBEditorWidget
*tbw
)
819 GtkTreeView
*tree
= GTK_TREE_VIEW(widget
);
820 gboolean del
= FALSE
;
822 if (gtk_selection_data_get_length(data
) >= 0 && gtk_selection_data_get_format(data
) == 8)
827 text
= (gchar
*) gtk_selection_data_get_data(data
);
828 is_sep
= utils_str_equal(text
, TB_EDITOR_SEPARATOR
);
829 /* If the source of the action is equal to the target, we do just re-order and so need
830 * to delete the separator to get it moved, not just copied. */
831 if (is_sep
&& widget
== tbw
->drag_source
)
834 if (tree
!= tbw
->tree_available
|| ! is_sep
)
836 GtkTreeIter iter
, iter_before
, *iter_before_ptr
;
837 GtkListStore
*store
= GTK_LIST_STORE(gtk_tree_view_get_model(tree
));
839 if (tbw
->last_drag_path
!= NULL
)
841 gtk_tree_model_get_iter(GTK_TREE_MODEL(store
), &iter_before
, tbw
->last_drag_path
);
843 if (gtk_list_store_iter_is_valid(store
, &iter_before
))
844 iter_before_ptr
= &iter_before
;
846 iter_before_ptr
= NULL
;
848 if (tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_BEFORE
||
849 tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
)
850 gtk_list_store_insert_before(store
, &iter
, iter_before_ptr
);
852 gtk_list_store_insert_after(store
, &iter
, iter_before_ptr
);
855 gtk_list_store_append(store
, &iter
);
857 tb_editor_set_item_values(text
, store
, &iter
);
858 tb_editor_scroll_to_iter(tree
, &iter
);
860 if (tree
!= tbw
->tree_used
|| ! is_sep
)
864 tbw
->drag_source
= NULL
; /* reset the value just to be sure */
865 tb_editor_free_path(tbw
);
866 gtk_drag_finish(context
, TRUE
, del
, ltime
);
870 static gboolean
tb_editor_foreach_used(GtkTreeModel
*model
, GtkTreePath
*path
,
871 GtkTreeIter
*iter
, gpointer data
)
875 gtk_tree_model_get(model
, iter
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
877 if (utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
878 g_string_append_printf(data
, "\t\t<separator/>\n");
879 else if (G_LIKELY(!EMPTY(action_name
)))
880 g_string_append_printf(data
, "\t\t<toolitem action='%s' />\n", action_name
);
887 static void tb_editor_write_markup(TBEditorWidget
*tbw
)
889 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
890 const gchar
*template = "<ui>\n<!--\n\
891 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
892 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
893 You can re-order all items and freely add and remove available actions.\n\
894 You cannot add new actions which are not listed in the documentation.\n\
895 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
896 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
897 editor in Geany.\n\n\
898 A list of available actions can be found in the documentation included with Geany or\n\
899 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
900 \t<toolbar name='GeanyToolbar'>\n";
902 GString
*str
= g_string_new(template);
904 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw
->store_used
), tb_editor_foreach_used
, str
);
906 g_string_append(str
, "\n\t</toolbar>\n</ui>\n");
908 toolbar_reload(str
->str
);
910 filename
= g_build_filename(app
->configdir
, "ui_toolbar.xml", NULL
);
911 utils_write_file(filename
, str
->str
);
914 g_string_free(str
, TRUE
);
918 static void tb_editor_available_items_changed_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
919 GtkTreeIter
*arg2
, TBEditorWidget
*tbw
)
921 tb_editor_write_markup(tbw
);
925 static void tb_editor_available_items_deleted_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
928 tb_editor_write_markup(tbw
);
932 static TBEditorWidget
*tb_editor_create_dialog(GtkWindow
*parent
)
934 GtkWidget
*dialog
, *vbox
, *hbox
, *vbox_buttons
, *button_add
, *button_remove
;
935 GtkWidget
*swin_available
, *swin_used
, *tree_available
, *tree_used
, *label
;
936 GtkCellRenderer
*text_renderer
, *icon_renderer
;
937 GtkTreeViewColumn
*column
;
938 TBEditorWidget
*tbw
= g_new(TBEditorWidget
, 1);
941 parent
= GTK_WINDOW(main_widgets
.window
);
943 dialog
= gtk_dialog_new_with_buttons(_("Customize Toolbar"),
945 GTK_DIALOG_DESTROY_WITH_PARENT
,
946 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
, NULL
);
947 vbox
= ui_dialog_vbox_new(GTK_DIALOG(dialog
));
948 gtk_box_set_spacing(GTK_BOX(vbox
), 6);
949 gtk_widget_set_name(dialog
, "GeanyDialog");
950 gtk_window_set_default_size(GTK_WINDOW(dialog
), -1, 400);
951 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_CLOSE
);
953 tbw
->store_available
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
954 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
955 tbw
->store_used
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
956 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
958 label
= gtk_label_new(
959 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
960 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
962 tree_available
= gtk_tree_view_new();
963 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available
), GTK_TREE_MODEL(tbw
->store_available
));
964 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available
), TRUE
);
965 gtk_tree_sortable_set_sort_column_id(
966 GTK_TREE_SORTABLE(tbw
->store_available
), TB_EDITOR_COL_LABEL
, GTK_SORT_ASCENDING
);
968 icon_renderer
= gtk_cell_renderer_pixbuf_new();
969 column
= gtk_tree_view_column_new_with_attributes(
970 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
971 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
973 text_renderer
= gtk_cell_renderer_text_new();
974 column
= gtk_tree_view_column_new_with_attributes(
975 _("Available Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
976 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
978 swin_available
= gtk_scrolled_window_new(NULL
, NULL
);
979 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available
),
980 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
981 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available
), GTK_SHADOW_IN
);
982 gtk_container_add(GTK_CONTAINER(swin_available
), tree_available
);
984 tree_used
= gtk_tree_view_new();
985 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used
), GTK_TREE_MODEL(tbw
->store_used
));
986 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used
), TRUE
);
987 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used
), TRUE
);
989 icon_renderer
= gtk_cell_renderer_pixbuf_new();
990 column
= gtk_tree_view_column_new_with_attributes(
991 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
992 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
994 text_renderer
= gtk_cell_renderer_text_new();
995 column
= gtk_tree_view_column_new_with_attributes(
996 _("Displayed Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
997 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
999 swin_used
= gtk_scrolled_window_new(NULL
, NULL
);
1000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used
),
1001 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
1002 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used
), GTK_SHADOW_IN
);
1003 gtk_container_add(GTK_CONTAINER(swin_used
), tree_used
);
1006 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available
), GDK_BUTTON1_MASK
,
1007 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1008 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available
),
1009 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1010 g_signal_connect(tree_available
, "drag-data-get",
1011 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
1012 g_signal_connect(tree_available
, "drag-data-received",
1013 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
1014 g_signal_connect(tree_available
, "drag-motion",
1015 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
1017 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used
), GDK_BUTTON1_MASK
,
1018 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1019 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used
),
1020 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
1021 g_signal_connect(tree_used
, "drag-data-get",
1022 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
1023 g_signal_connect(tree_used
, "drag-data-received",
1024 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
1025 g_signal_connect(tree_used
, "drag-motion",
1026 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
1029 button_add
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, NULL
);
1030 button_remove
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, NULL
);
1031 g_signal_connect(button_add
, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb
), tbw
);
1032 g_signal_connect(button_remove
, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb
), tbw
);
1034 vbox_buttons
= gtk_vbox_new(FALSE
, 6);
1035 /* FIXME this is a little hack'ish, any better ideas? */
1036 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1037 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_add
, FALSE
, FALSE
, 0);
1038 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_remove
, FALSE
, FALSE
, 0);
1039 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1041 hbox
= gtk_hbox_new(FALSE
, 6);
1042 gtk_box_pack_start(GTK_BOX(hbox
), swin_available
, TRUE
, TRUE
, 0);
1043 gtk_box_pack_start(GTK_BOX(hbox
), vbox_buttons
, FALSE
, FALSE
, 0);
1044 gtk_box_pack_start(GTK_BOX(hbox
), swin_used
, TRUE
, TRUE
, 0);
1046 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 6);
1047 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, TRUE
, 0);
1049 gtk_widget_show_all(vbox
);
1051 g_object_unref(tbw
->store_available
);
1052 g_object_unref(tbw
->store_used
);
1054 tbw
->dialog
= dialog
;
1055 tbw
->tree_available
= GTK_TREE_VIEW(tree_available
);
1056 tbw
->tree_used
= GTK_TREE_VIEW(tree_used
);
1058 tbw
->last_drag_path
= NULL
;
1064 void toolbar_configure(GtkWindow
*parent
)
1068 GSList
*sl
, *used_items
;
1069 GList
*l
, *all_items
;
1072 TBEditorWidget
*tbw
;
1074 /* read the current active toolbar items */
1075 markup
= gtk_ui_manager_get_ui(uim
);
1076 used_items
= tb_editor_parse_ui(markup
, -1, NULL
);
1079 /* get all available actions */
1080 all_items
= gtk_action_group_list_actions(group
);
1082 /* create the GUI */
1083 tbw
= tb_editor_create_dialog(parent
);
1085 /* fill the stores */
1086 gtk_list_store_insert_with_values(tbw
->store_available
, NULL
, -1,
1087 TB_EDITOR_COL_ACTION
, TB_EDITOR_SEPARATOR
,
1088 TB_EDITOR_COL_LABEL
, TB_EDITOR_SEPARATOR_LABEL
,
1090 foreach_list(l
, all_items
)
1092 name
= gtk_action_get_name(l
->data
);
1093 if (g_slist_find_custom(used_items
, name
, (GCompareFunc
) strcmp
) == NULL
)
1095 gtk_list_store_append(tbw
->store_available
, &iter
);
1096 tb_editor_set_item_values(name
, tbw
->store_available
, &iter
);
1099 foreach_slist(sl
, used_items
)
1101 gtk_list_store_append(tbw
->store_used
, &iter
);
1102 tb_editor_set_item_values(sl
->data
, tbw
->store_used
, &iter
);
1104 /* select first item */
1105 path
= gtk_tree_path_new_from_string("0");
1106 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw
->tree_used
), path
);
1107 gtk_tree_path_free(path
);
1109 /* connect the changed signals after populating the store */
1110 g_signal_connect(tbw
->store_used
, "row-changed",
1111 G_CALLBACK(tb_editor_available_items_changed_cb
), tbw
);
1112 g_signal_connect(tbw
->store_used
, "row-deleted",
1113 G_CALLBACK(tb_editor_available_items_deleted_cb
), tbw
);
1116 gtk_dialog_run(GTK_DIALOG(tbw
->dialog
));
1118 gtk_widget_destroy(tbw
->dialog
);
1120 g_slist_foreach(used_items
, (GFunc
) g_free
, NULL
);
1121 g_slist_free(used_items
);
1122 g_list_free(all_items
);
1123 tb_editor_free_path(tbw
);