2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 /* Utility functions to create the toolbar */
35 #include "callbacks.h"
37 #include "geanyentryaction.h"
38 #include "geanymenubuttonaction.h"
45 #include <glib/gstdio.h>
48 GeanyToolbarPrefs toolbar_prefs
;
49 static GtkUIManager
*uim
;
50 static GtkActionGroup
*group
;
51 static GSList
*plugin_items
= NULL
;
53 /* Available toolbar actions
54 * Fields: name, stock_id, label, accelerator, tooltip, callback */
55 static const GtkActionEntry ui_entries
[] = {
56 /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */
57 { "Save", GTK_STOCK_SAVE
, NULL
, NULL
, N_("Save the current file"), G_CALLBACK(on_save1_activate
) },
58 { "SaveAs", GTK_STOCK_SAVE_AS
, NULL
, NULL
, N_("Save as"), G_CALLBACK(on_save_as1_activate
) },
59 { "SaveAll", GEANY_STOCK_SAVE_ALL
, NULL
, NULL
, N_("Save all open files"), G_CALLBACK(on_save_all1_activate
) },
60 { "Reload", GTK_STOCK_REVERT_TO_SAVED
, NULL
, NULL
, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked
) },
61 { "Close", GTK_STOCK_CLOSE
, NULL
, NULL
, N_("Close the current file"), G_CALLBACK(on_close1_activate
) },
62 { "CloseAll", GEANY_STOCK_CLOSE_ALL
, NULL
, NULL
, N_("Close all open files"), G_CALLBACK(on_close_all1_activate
) },
63 { "Cut", GTK_STOCK_CUT
, NULL
, NULL
, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate
) },
64 { "Copy", GTK_STOCK_COPY
, NULL
, NULL
, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate
) },
65 { "Paste", GTK_STOCK_PASTE
, NULL
, NULL
, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate
) },
66 { "Delete", GTK_STOCK_DELETE
, NULL
, NULL
, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate
) },
67 { "Undo", GTK_STOCK_UNDO
, NULL
, NULL
, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate
) },
68 { "Redo", GTK_STOCK_REDO
, NULL
, NULL
, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate
) },
69 { "NavBack", GTK_STOCK_GO_BACK
, NULL
, NULL
, N_("Navigate back a location"), G_CALLBACK(on_toolbutton_back_activate
) },
70 { "NavFor", GTK_STOCK_GO_FORWARD
, NULL
, NULL
, N_("Navigate forward a location"), G_CALLBACK(on_toolbutton_forward_activate
) },
71 { "Compile", GTK_STOCK_CONVERT
, N_("Compile"), NULL
, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked
) },
72 { "Run", GTK_STOCK_EXECUTE
, NULL
, NULL
, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked
) },
73 { "Color", GTK_STOCK_SELECT_COLOR
, N_("Color Chooser"), NULL
, N_("Open a color chooser dialog, to interactively pick colors from a palette"), G_CALLBACK(on_show_color_chooser1_activate
) },
74 { "ZoomIn", GTK_STOCK_ZOOM_IN
, NULL
, NULL
, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate
) },
75 { "ZoomOut", GTK_STOCK_ZOOM_OUT
, NULL
, NULL
, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate
) },
76 { "UnIndent", GTK_STOCK_UNINDENT
, NULL
, NULL
, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate
) },
77 { "Indent", GTK_STOCK_INDENT
, NULL
, NULL
, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate
) },
78 { "Search", GTK_STOCK_FIND
, NULL
, NULL
, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked
) },
79 { "Goto", GTK_STOCK_JUMP_TO
, NULL
, NULL
, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked
) },
80 { "Preferences", GTK_STOCK_PREFERENCES
, NULL
, NULL
, N_("Show the preferences dialog"), G_CALLBACK(on_preferences1_activate
) },
81 { "Quit", GTK_STOCK_QUIT
, NULL
, NULL
, N_("Quit Geany"), G_CALLBACK(on_quit1_activate
) },
82 { "Print", GTK_STOCK_PRINT
, NULL
, NULL
, N_("Print document"), G_CALLBACK(on_print1_activate
) },
83 { "Replace", GTK_STOCK_FIND_AND_REPLACE
, NULL
, NULL
, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate
) }
85 static const guint ui_entries_n
= G_N_ELEMENTS(ui_entries
);
88 /* fallback UI definition */
89 static const gchar
*toolbar_markup
=
91 "<toolbar name='GeanyToolbar'>"
92 "<toolitem action='New'/>"
93 "<toolitem action='Open'/>"
94 "<toolitem action='Save'/>"
95 "<toolitem action='SaveAll'/>"
97 "<toolitem action='Reload'/>"
98 "<toolitem action='Close'/>"
100 "<toolitem action='NavBack'/>"
101 "<toolitem action='NavFor'/>"
103 "<toolitem action='Compile'/>"
104 "<toolitem action='Build'/>"
105 "<toolitem action='Run'/>"
107 "<toolitem action='Color'/>"
109 "<toolitem action='SearchEntry'/>"
110 "<toolitem action='Search'/>"
112 "<toolitem action='GotoEntry'/>"
113 "<toolitem action='Goto'/>"
115 "<toolitem action='Quit'/>"
120 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
121 * update the widget pointer in this case (i.e. request it again) or better use
122 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
123 * toolbar is reloaded. */
124 GtkWidget
*toolbar_get_widget_by_name(const gchar
*name
)
129 g_return_val_if_fail(name
!= NULL
, NULL
);
131 path
= g_strconcat("/ui/GeanyToolbar/", name
, NULL
);
132 widget
= gtk_ui_manager_get_widget(uim
, path
);
139 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
140 * toolbar_get_widget_by_name for details(). */
141 GtkWidget
*toolbar_get_widget_child_by_name(const gchar
*name
)
143 GtkWidget
*widget
= toolbar_get_widget_by_name(name
);
145 if (G_LIKELY(widget
!= NULL
))
146 return gtk_bin_get_child(GTK_BIN(widget
));
152 GtkAction
*toolbar_get_action_by_name(const gchar
*name
)
154 g_return_val_if_fail(name
!= NULL
, NULL
);
156 return gtk_action_group_get_action(group
, name
);
160 static void toolbar_item_destroy_cb(GtkWidget
*widget
, G_GNUC_UNUSED gpointer data
)
162 plugin_items
= g_slist_remove(plugin_items
, widget
);
166 void toolbar_item_ref(GtkToolItem
*item
)
168 g_return_if_fail(item
!= NULL
);
170 plugin_items
= g_slist_append(plugin_items
, item
);
171 g_signal_connect(item
, "destroy", G_CALLBACK(toolbar_item_destroy_cb
), NULL
);
175 static GtkWidget
*toolbar_reload(const gchar
*markup
)
180 GError
*error
= NULL
;
182 static guint merge_id
= 0;
183 GtkWidget
*toolbar_new_file_menu
= NULL
;
184 GtkWidget
*toolbar_recent_files_menu
= NULL
;
185 GtkWidget
*toolbar_build_menu
= NULL
;
187 /* Cleanup old toolbar */
190 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
191 foreach_slist(l
, plugin_items
)
193 g_object_ref(l
->data
);
194 gtk_container_remove(GTK_CONTAINER(main_widgets
.toolbar
), GTK_WIDGET(l
->data
));
196 /* ref and hold the submenus of the New, Open and Build toolbar items */
197 toolbar_new_file_menu
= geany_menu_button_action_get_menu(
198 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "New")));
199 g_object_ref(toolbar_new_file_menu
);
200 toolbar_recent_files_menu
= geany_menu_button_action_get_menu(
201 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Open")));
202 g_object_ref(toolbar_recent_files_menu
);
203 toolbar_build_menu
= geany_menu_button_action_get_menu(
204 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Build")));
205 g_object_ref(toolbar_build_menu
);
208 gtk_widget_destroy(main_widgets
.toolbar
);
210 gtk_ui_manager_remove_ui(uim
, merge_id
);
211 gtk_ui_manager_ensure_update(uim
);
216 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, markup
, -1, &error
);
220 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
221 filename
= g_build_filename(app
->configdir
, "ui_toolbar.xml", NULL
);
222 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
225 if (! g_error_matches(error
, G_FILE_ERROR
, G_FILE_ERROR_NOENT
))
226 geany_debug("Loading user toolbar UI definition failed (%s).", error
->message
);
230 SETPTR(filename
, g_build_filename(app
->datadir
, "ui_toolbar.xml", NULL
));
231 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
237 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
240 /* finally load the internally defined markup as fallback */
241 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, toolbar_markup
, -1, NULL
);
243 main_widgets
.toolbar
= gtk_ui_manager_get_widget(uim
, "/ui/GeanyToolbar");
244 ui_init_toolbar_widgets();
246 /* add the toolbar again to the main window */
247 if (toolbar_prefs
.append_to_menu
)
249 GtkWidget
*hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
250 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
251 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
255 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
257 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
258 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
260 gtk_widget_show(main_widgets
.toolbar
);
262 /* re-add und unref the plugin toolbar items */
263 i
= toolbar_get_insert_position();
264 foreach_slist(l
, plugin_items
)
266 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
), l
->data
, i
);
267 g_object_unref(l
->data
);
270 /* re-add und unref the submenus of menu toolbar items */
271 if (toolbar_new_file_menu
!= NULL
)
273 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
274 gtk_action_group_get_action(group
, "New")), toolbar_new_file_menu
);
275 g_object_unref(toolbar_new_file_menu
);
277 if (toolbar_recent_files_menu
!= NULL
)
279 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
280 gtk_action_group_get_action(group
, "Open")), toolbar_recent_files_menu
);
281 g_object_unref(toolbar_recent_files_menu
);
283 if (toolbar_build_menu
!= NULL
)
285 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
286 gtk_action_group_get_action(group
, "Build")), toolbar_build_menu
);
287 g_object_unref(toolbar_build_menu
);
290 /* update button states */
291 if (main_status
.main_window_realized
)
293 GeanyDocument
*doc
= document_get_current();
294 gboolean doc_changed
= (doc
!= NULL
) ? doc
->changed
: FALSE
;
296 ui_document_buttons_update();
297 ui_save_buttons_toggle(doc_changed
); /* update save all */
298 ui_update_popup_reundo_items(doc
);
300 toolbar_apply_settings();
304 g_signal_connect(main_widgets
.toolbar
, "button-press-event",
305 G_CALLBACK(toolbar_popup_menu
), NULL
);
306 g_signal_connect(main_widgets
.toolbar
, "key-press-event",
307 G_CALLBACK(on_escape_key_press_event
), NULL
);
309 /* We don't need to disconnect those signals as this is done automatically when the entry
310 * widgets are destroyed, happens when the toolbar itself is destroyed. */
311 entry
= toolbar_get_widget_child_by_name("SearchEntry");
313 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
314 entry
= toolbar_get_widget_child_by_name("GotoEntry");
316 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
318 return main_widgets
.toolbar
;
322 static void toolbar_notify_style_cb(GObject
*object
, GParamSpec
*arg1
, gpointer data
)
324 const gchar
*arg_name
= g_param_spec_get_name(arg1
);
327 if (toolbar_prefs
.use_gtk_default_style
&& utils_str_equal(arg_name
, "gtk-toolbar-style"))
329 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_style
);
330 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
332 else if (toolbar_prefs
.use_gtk_default_icon
&& utils_str_equal(arg_name
, "gtk-toolbar-size"))
334 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_size
);
335 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
340 GtkWidget
*toolbar_init(void)
343 GtkAction
*action_new
;
344 GtkAction
*action_open
;
345 GtkAction
*action_build
;
346 GtkAction
*action_searchentry
;
347 GtkAction
*action_gotoentry
;
348 GtkSettings
*gtk_settings
;
350 uim
= gtk_ui_manager_new();
351 group
= gtk_action_group_new("GeanyToolbar");
353 gtk_action_group_set_translation_domain(group
, GETTEXT_PACKAGE
);
354 gtk_action_group_add_actions(group
, ui_entries
, ui_entries_n
, NULL
);
356 /* Create our custom actions */
357 action_new
= geany_menu_button_action_new(
359 _("Create a new file"),
360 _("Create a new file from a template"),
362 g_signal_connect(action_new
, "button-clicked", G_CALLBACK(on_new1_activate
), NULL
);
363 gtk_action_group_add_action(group
, action_new
);
365 action_open
= geany_menu_button_action_new(
367 _("Open an existing file"),
368 _("Open a recent file"),
370 g_signal_connect(action_open
, "button-clicked", G_CALLBACK(on_open1_activate
), NULL
);
371 gtk_action_group_add_action(group
, action_open
);
373 action_build
= geany_menu_button_action_new(
375 _("Build the current file"),
376 _("Choose more build actions"),
378 g_signal_connect(action_build
, "button-clicked",
379 G_CALLBACK(build_toolbutton_build_clicked
), NULL
);
380 gtk_action_group_add_action(group
, action_build
);
382 action_searchentry
= geany_entry_action_new(
383 "SearchEntry", _("Search Field"), _("Find the entered text in the current file"), FALSE
);
384 g_signal_connect(action_searchentry
, "entry-activate",
385 G_CALLBACK(on_toolbar_search_entry_activate
), GINT_TO_POINTER(FALSE
));
386 g_signal_connect(action_searchentry
, "entry-activate-backward",
387 G_CALLBACK(on_toolbar_search_entry_activate
), GINT_TO_POINTER(TRUE
));
388 g_signal_connect(action_searchentry
, "entry-changed",
389 G_CALLBACK(on_toolbar_search_entry_changed
), NULL
);
390 gtk_action_group_add_action(group
, action_searchentry
);
392 action_gotoentry
= geany_entry_action_new(
393 "GotoEntry", _("Goto Field"), _("Jump to the entered line number"), TRUE
);
394 g_signal_connect(action_gotoentry
, "entry-activate",
395 G_CALLBACK(on_toolbutton_goto_entry_activate
), NULL
);
396 gtk_action_group_add_action(group
, action_gotoentry
);
398 gtk_ui_manager_insert_action_group(uim
, group
, 0);
400 toolbar
= toolbar_reload(NULL
);
401 #if GTK_CHECK_VERSION(3, 0, 0)
402 gtk_style_context_add_class(gtk_widget_get_style_context(toolbar
), "primary-toolbar");
405 gtk_settings
= gtk_widget_get_settings(GTK_WIDGET(toolbar
));
406 if (gtk_settings
!= NULL
)
408 g_signal_connect(gtk_settings
, "notify::gtk-toolbar-style",
409 G_CALLBACK(toolbar_notify_style_cb
), NULL
);
416 void toolbar_update_ui(void)
418 static GtkWidget
*hbox_menubar
= NULL
;
419 static GtkWidget
*menubar
= NULL
;
421 GtkToolItem
*first_item
;
424 { /* cache widget pointers */
425 hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
426 menubar
= ui_lookup_widget(main_widgets
.window
, "menubar1");
428 /* the separator between the menubar and the toolbar */
429 first_item
= gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets
.toolbar
), 0);
430 if (first_item
!= NULL
&& GTK_IS_SEPARATOR_TOOL_ITEM(first_item
))
432 gtk_widget_destroy(GTK_WIDGET(first_item
));
435 parent
= gtk_widget_get_parent(main_widgets
.toolbar
);
437 if (toolbar_prefs
.append_to_menu
)
439 GtkWidget
*menubar_toolbar_separator
;
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 https://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
)
1067 GSList
*sl
, *used_items
;
1068 GList
*l
, *all_items
;
1070 TBEditorWidget
*tbw
;
1072 /* read the current active toolbar items */
1073 markup
= gtk_ui_manager_get_ui(uim
);
1074 used_items
= tb_editor_parse_ui(markup
, -1, NULL
);
1077 /* get all available actions */
1078 all_items
= gtk_action_group_list_actions(group
);
1080 /* create the GUI */
1081 tbw
= tb_editor_create_dialog(parent
);
1083 /* fill the stores */
1084 gtk_list_store_insert_with_values(tbw
->store_available
, NULL
, -1,
1085 TB_EDITOR_COL_ACTION
, TB_EDITOR_SEPARATOR
,
1086 TB_EDITOR_COL_LABEL
, TB_EDITOR_SEPARATOR_LABEL
,
1088 foreach_list(l
, all_items
)
1090 const gchar
*name
= gtk_action_get_name(l
->data
);
1092 if (g_slist_find_custom(used_items
, name
, (GCompareFunc
) strcmp
) == NULL
)
1096 gtk_list_store_append(tbw
->store_available
, &iter
);
1097 tb_editor_set_item_values(name
, tbw
->store_available
, &iter
);
1100 foreach_slist(sl
, used_items
)
1104 gtk_list_store_append(tbw
->store_used
, &iter
);
1105 tb_editor_set_item_values(sl
->data
, tbw
->store_used
, &iter
);
1107 /* select first item */
1108 path
= gtk_tree_path_new_from_string("0");
1109 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw
->tree_used
), path
);
1110 gtk_tree_path_free(path
);
1112 /* connect the changed signals after populating the store */
1113 g_signal_connect(tbw
->store_used
, "row-changed",
1114 G_CALLBACK(tb_editor_available_items_changed_cb
), tbw
);
1115 g_signal_connect(tbw
->store_used
, "row-deleted",
1116 G_CALLBACK(tb_editor_available_items_deleted_cb
), tbw
);
1119 gtk_dialog_run(GTK_DIALOG(tbw
->dialog
));
1121 gtk_widget_destroy(tbw
->dialog
);
1123 g_slist_foreach(used_items
, (GFunc
) g_free
, NULL
);
1124 g_slist_free(used_items
);
1125 g_list_free(all_items
);
1126 tb_editor_free_path(tbw
);