2 * toolbar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2009-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2009-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 /* Utility functions to create the toolbar */
34 #include "callbacks.h"
40 #include "geanymenubuttonaction.h"
41 #include "geanyentryaction.h"
44 #include <glib/gstdio.h>
47 GeanyToolbarPrefs toolbar_prefs
;
48 static GtkUIManager
*uim
;
49 static GtkActionGroup
*group
;
50 static GSList
*plugin_items
= NULL
;
52 /* Available toolbar actions
53 * Fields: name, stock_id, label, accelerator, tooltip, callback */
54 const GtkActionEntry ui_entries
[] = {
55 /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */
56 { "Save", GTK_STOCK_SAVE
, NULL
, NULL
, N_("Save the current file"), G_CALLBACK(on_toolbutton_save_clicked
) },
57 { "SaveAll", GEANY_STOCK_SAVE_ALL
, NULL
, NULL
, N_("Save all open files"), G_CALLBACK(on_save_all1_activate
) },
58 { "Reload", GTK_STOCK_REVERT_TO_SAVED
, NULL
, NULL
, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked
) },
59 { "Close", GTK_STOCK_CLOSE
, NULL
, NULL
, N_("Close the current file"), G_CALLBACK(on_toolbutton_close_clicked
) },
60 { "CloseAll", GEANY_STOCK_CLOSE_ALL
, NULL
, NULL
, N_("Close all open files"), G_CALLBACK(on_toolbutton_close_all_clicked
) },
61 { "Cut", GTK_STOCK_CUT
, NULL
, NULL
, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate
) },
62 { "Copy", GTK_STOCK_COPY
, NULL
, NULL
, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate
) },
63 { "Paste", GTK_STOCK_PASTE
, NULL
, NULL
, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate
) },
64 { "Delete", GTK_STOCK_DELETE
, NULL
, NULL
, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate
) },
65 { "Undo", GTK_STOCK_UNDO
, NULL
, NULL
, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate
) },
66 { "Redo", GTK_STOCK_REDO
, NULL
, NULL
, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate
) },
67 { "NavBack", GTK_STOCK_GO_BACK
, NULL
, NULL
, N_("Navigate back a location"), G_CALLBACK(on_back_activate
) },
68 { "NavFor", GTK_STOCK_GO_FORWARD
, NULL
, NULL
, N_("Navigate forward a location"), G_CALLBACK(on_forward_activate
) },
69 { "Compile", GTK_STOCK_CONVERT
, N_("Compile"), NULL
, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked
) },
70 { "Run", GTK_STOCK_EXECUTE
, NULL
, NULL
, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked
) },
71 { "Color", GTK_STOCK_SELECT_COLOR
, N_("Color Chooser"), NULL
, N_("Open a color chooser dialog, to interactively pick colors from a palette"), G_CALLBACK(on_show_color_chooser1_activate
) },
72 { "ZoomIn", GTK_STOCK_ZOOM_IN
, NULL
, NULL
, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate
) },
73 { "ZoomOut", GTK_STOCK_ZOOM_OUT
, NULL
, NULL
, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate
) },
74 { "UnIndent", GTK_STOCK_UNINDENT
, NULL
, NULL
, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate
) },
75 { "Indent", GTK_STOCK_INDENT
, NULL
, NULL
, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate
) },
76 { "Search", GTK_STOCK_FIND
, NULL
, NULL
, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked
) },
77 { "Goto", GTK_STOCK_JUMP_TO
, NULL
, NULL
, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked
) },
78 { "Preferences", GTK_STOCK_PREFERENCES
, NULL
, NULL
, N_("Show the preferences dialog"), G_CALLBACK(on_toolbutton_preferences_clicked
) },
79 { "Quit", GTK_STOCK_QUIT
, NULL
, NULL
, N_("Quit Geany"), G_CALLBACK(on_toolbutton_quit_clicked
) },
80 { "Print", GTK_STOCK_PRINT
, NULL
, NULL
, N_("Print document"), G_CALLBACK(on_print1_activate
) },
81 { "Replace", GTK_STOCK_FIND_AND_REPLACE
, NULL
, NULL
, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate
) }
83 const guint ui_entries_n
= G_N_ELEMENTS(ui_entries
);
86 /* fallback UI definition */
87 const gchar
*toolbar_markup
=
89 "<toolbar name='GeanyToolbar'>"
90 "<toolitem action='New'/>"
91 "<toolitem action='Open'/>"
92 "<toolitem action='Save'/>"
93 "<toolitem action='SaveAll'/>"
95 "<toolitem action='Reload'/>"
96 "<toolitem action='Close'/>"
98 "<toolitem action='NavBack'/>"
99 "<toolitem action='NavFor'/>"
101 "<toolitem action='Compile'/>"
102 "<toolitem action='Build'/>"
103 "<toolitem action='Run'/>"
105 "<toolitem action='Color'/>"
107 "<toolitem action='SearchEntry'/>"
108 "<toolitem action='Search'/>"
110 "<toolitem action='GotoEntry'/>"
111 "<toolitem action='Goto'/>"
113 "<toolitem action='Quit'/>"
118 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
119 * update the widget pointer in this case (i.e. request it again) or better use
120 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
121 * toolbar is reloaded. */
122 GtkWidget
*toolbar_get_widget_by_name(const gchar
*name
)
127 g_return_val_if_fail(name
!= NULL
, NULL
);
129 path
= g_strconcat("/ui/GeanyToolbar/", name
, NULL
);
130 widget
= gtk_ui_manager_get_widget(uim
, path
);
137 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See
138 * toolbar_get_widget_by_name for details(). */
139 GtkWidget
*toolbar_get_widget_child_by_name(const gchar
*name
)
141 GtkWidget
*widget
= toolbar_get_widget_by_name(name
);
143 if (G_LIKELY(widget
!= NULL
))
144 return gtk_bin_get_child(GTK_BIN(widget
));
150 GtkAction
*toolbar_get_action_by_name(const gchar
*name
)
152 g_return_val_if_fail(name
!= NULL
, NULL
);
154 return gtk_action_group_get_action(group
, name
);
158 static void toolbar_item_destroy_cb(GtkWidget
*widget
, G_GNUC_UNUSED gpointer data
)
160 plugin_items
= g_slist_remove(plugin_items
, widget
);
164 void toolbar_item_ref(GtkToolItem
*item
)
166 g_return_if_fail(item
!= NULL
);
168 plugin_items
= g_slist_append(plugin_items
, item
);
169 g_signal_connect(item
, "destroy", G_CALLBACK(toolbar_item_destroy_cb
), NULL
);
173 static GtkWidget
*toolbar_reload(const gchar
*markup
)
178 GError
*error
= NULL
;
179 const gchar
*filename
;
180 static guint merge_id
= 0;
181 GtkWidget
*toolbar_new_file_menu
= NULL
;
182 GtkWidget
*toolbar_recent_files_menu
= NULL
;
183 GtkWidget
*toolbar_build_menu
= NULL
;
185 /* Cleanup old toolbar */
188 /* ref plugins toolbar items to keep them after we destroyed the toolbar */
189 foreach_slist(l
, plugin_items
)
191 g_object_ref(l
->data
);
192 gtk_container_remove(GTK_CONTAINER(main_widgets
.toolbar
), GTK_WIDGET(l
->data
));
194 /* ref and hold the submenus of the New, Open and Build toolbar items */
195 toolbar_new_file_menu
= geany_menu_button_action_get_menu(
196 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "New")));
197 g_object_ref(toolbar_new_file_menu
);
198 toolbar_recent_files_menu
= geany_menu_button_action_get_menu(
199 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Open")));
200 g_object_ref(toolbar_recent_files_menu
);
201 toolbar_build_menu
= geany_menu_button_action_get_menu(
202 GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group
, "Build")));
203 g_object_ref(toolbar_build_menu
);
206 gtk_widget_destroy(main_widgets
.toolbar
);
208 gtk_ui_manager_remove_ui(uim
, merge_id
);
209 gtk_ui_manager_ensure_update(uim
);
214 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, markup
, -1, &error
);
218 /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */
219 filename
= utils_build_path(app
->configdir
, "ui_toolbar.xml", NULL
);
220 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
223 if (! g_error_matches(error
, G_FILE_ERROR
, G_FILE_ERROR_NOENT
))
224 geany_debug("Loading user toolbar UI definition failed (%s).", error
->message
);
228 filename
= utils_build_path(app
->datadir
, "ui_toolbar.xml", NULL
);
229 merge_id
= gtk_ui_manager_add_ui_from_file(uim
, filename
, &error
);
234 geany_debug("UI creation failed, using internal fallback definition. Error message: %s",
237 /* finally load the internally defined markup as fallback */
238 merge_id
= gtk_ui_manager_add_ui_from_string(uim
, toolbar_markup
, -1, NULL
);
240 main_widgets
.toolbar
= gtk_ui_manager_get_widget(uim
, "/ui/GeanyToolbar");
241 ui_init_toolbar_widgets();
243 /* add the toolbar again to the main window */
244 if (toolbar_prefs
.append_to_menu
)
246 GtkWidget
*hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
247 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
248 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
252 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
254 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
255 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
257 gtk_widget_show(main_widgets
.toolbar
);
259 /* re-add und unref the plugin toolbar items */
260 i
= toolbar_get_insert_position();
261 foreach_slist(l
, plugin_items
)
263 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
), l
->data
, i
);
264 g_object_unref(l
->data
);
267 /* re-add und unref the submenus of menu toolbar items */
268 if (toolbar_new_file_menu
!= NULL
)
270 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
271 gtk_action_group_get_action(group
, "New")), toolbar_new_file_menu
);
272 g_object_unref(toolbar_new_file_menu
);
274 if (toolbar_recent_files_menu
!= NULL
)
276 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
277 gtk_action_group_get_action(group
, "Open")), toolbar_recent_files_menu
);
278 g_object_unref(toolbar_recent_files_menu
);
280 if (toolbar_build_menu
!= NULL
)
282 geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(
283 gtk_action_group_get_action(group
, "Build")), toolbar_build_menu
);
284 g_object_unref(toolbar_build_menu
);
287 /* update button states */
288 if (main_status
.main_window_realized
)
290 GeanyDocument
*doc
= document_get_current();
292 ui_document_buttons_update();
293 ui_save_buttons_toggle(doc
->changed
); /* update save all */
294 ui_update_popup_reundo_items(doc
);
296 toolbar_apply_settings();
300 g_signal_connect(main_widgets
.toolbar
, "button-press-event",
301 G_CALLBACK(toolbar_popup_menu
), NULL
);
302 g_signal_connect(main_widgets
.toolbar
, "key-press-event",
303 G_CALLBACK(on_escape_key_press_event
), NULL
);
305 /* We don't need to disconnect those signals as this is done automatically when the entry
306 * widgets are destroyed, happens when the toolbar itself is destroyed. */
307 entry
= toolbar_get_widget_child_by_name("SearchEntry");
309 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
310 entry
= toolbar_get_widget_child_by_name("GotoEntry");
312 g_signal_connect(entry
, "motion-notify-event", G_CALLBACK(on_motion_event
), NULL
);
315 return main_widgets
.toolbar
;
319 static void toolbar_notify_style_cb(GObject
*object
, GParamSpec
*arg1
, gpointer data
)
321 const gchar
*arg_name
= g_param_spec_get_name(arg1
);
324 if (toolbar_prefs
.use_gtk_default_style
&& utils_str_equal(arg_name
, "gtk-toolbar-style"))
326 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_style
);
327 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
329 else if (toolbar_prefs
.use_gtk_default_icon
&& utils_str_equal(arg_name
, "gtk-toolbar-size"))
331 value
= ui_get_gtk_settings_integer(arg_name
, toolbar_prefs
.icon_size
);
332 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), value
);
337 GtkWidget
*toolbar_init(void)
340 GtkAction
*action_new
;
341 GtkAction
*action_open
;
342 GtkAction
*action_build
;
343 GtkAction
*action_searchentry
;
344 GtkAction
*action_gotoentry
;
345 GtkSettings
*gtk_settings
;
347 uim
= gtk_ui_manager_new();
348 group
= gtk_action_group_new("GeanyToolbar");
350 gtk_action_group_set_translation_domain(group
, GETTEXT_PACKAGE
);
351 gtk_action_group_add_actions(group
, ui_entries
, ui_entries_n
, NULL
);
353 /* Create our custom actions */
354 action_new
= geany_menu_button_action_new(
356 _("Create a new file"),
357 _("Create a new file from a template"),
359 g_signal_connect(action_new
, "button-clicked", G_CALLBACK(on_toolbutton_new_clicked
), NULL
);
360 gtk_action_group_add_action(group
, action_new
);
362 action_open
= geany_menu_button_action_new(
364 _("Open an existing file"),
365 _("Open a recent file"),
367 g_signal_connect(action_open
, "button-clicked", G_CALLBACK(on_toolbutton_open_clicked
), NULL
);
368 gtk_action_group_add_action(group
, action_open
);
370 action_build
= geany_menu_button_action_new(
372 _("Build the current file"),
373 _("Choose more build actions"),
375 g_signal_connect(action_build
, "button-clicked",
376 G_CALLBACK(build_toolbutton_build_clicked
), NULL
);
377 gtk_action_group_add_action(group
, action_build
);
379 action_searchentry
= geany_entry_action_new(
380 "SearchEntry", _("Search"), _("Find the entered text in the current file"), FALSE
);
381 g_signal_connect(action_searchentry
, "entry-activate",
382 G_CALLBACK(on_toolbar_search_entry_changed
), GINT_TO_POINTER(FALSE
));
383 g_signal_connect(action_searchentry
, "entry-changed",
384 G_CALLBACK(on_toolbar_search_entry_changed
), GINT_TO_POINTER(TRUE
));
385 gtk_action_group_add_action(group
, action_searchentry
);
387 action_gotoentry
= geany_entry_action_new(
388 "GotoEntry", _("Goto"), _("Jump to the entered line number"), TRUE
);
389 g_signal_connect(action_gotoentry
, "entry-activate",
390 G_CALLBACK(on_toolbutton_goto_entry_activate
), NULL
);
391 gtk_action_group_add_action(group
, action_gotoentry
);
393 gtk_ui_manager_insert_action_group(uim
, group
, 0);
395 toolbar
= toolbar_reload(NULL
);
397 gtk_settings
= gtk_widget_get_settings(GTK_WIDGET(toolbar
));
398 if (gtk_settings
!= NULL
)
400 g_signal_connect(gtk_settings
, "notify::gtk-toolbar-style",
401 G_CALLBACK(toolbar_notify_style_cb
), NULL
);
408 void toolbar_update_ui(void)
410 static GtkWidget
*hbox_menubar
= NULL
;
411 static GtkWidget
*menubar
= NULL
;
412 static GtkWidget
*menubar_toolbar_separator
= NULL
;
416 { /* cache widget pointers */
417 hbox_menubar
= ui_lookup_widget(main_widgets
.window
, "hbox_menubar");
418 menubar
= ui_lookup_widget(main_widgets
.window
, "menubar1");
420 menubar_toolbar_separator
= GTK_WIDGET(gtk_separator_tool_item_new());
421 gtk_toolbar_insert(GTK_TOOLBAR(main_widgets
.toolbar
),
422 GTK_TOOL_ITEM(menubar_toolbar_separator
), 0);
425 parent
= gtk_widget_get_parent(main_widgets
.toolbar
);
427 if (toolbar_prefs
.append_to_menu
)
431 if (parent
!= hbox_menubar
)
432 { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't
434 g_object_ref(main_widgets
.toolbar
);
436 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
437 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
438 gtk_box_reorder_child(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, 1);
440 g_object_unref(main_widgets
.toolbar
);
444 gtk_box_pack_start(GTK_BOX(hbox_menubar
), main_widgets
.toolbar
, TRUE
, TRUE
, 0);
448 GtkWidget
*box
= ui_lookup_widget(main_widgets
.window
, "vbox1");
454 g_object_ref(main_widgets
.toolbar
);
456 gtk_container_remove(GTK_CONTAINER(parent
), main_widgets
.toolbar
);
457 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
458 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
460 g_object_unref(main_widgets
.toolbar
);
465 gtk_box_pack_start(GTK_BOX(box
), main_widgets
.toolbar
, FALSE
, FALSE
, 0);
466 gtk_box_reorder_child(GTK_BOX(box
), main_widgets
.toolbar
, 1);
469 /* the separator between the menubar and the toolbar */
470 ui_widget_show_hide(menubar_toolbar_separator
, toolbar_prefs
.append_to_menu
);
471 /* we need to adjust the packing flags for the menubar to expand it if it is alone in the
472 * hbox and not expand it if the toolbar is appended */
473 gtk_box_set_child_packing(GTK_BOX(hbox_menubar
), menubar
,
474 ! toolbar_prefs
.append_to_menu
, ! toolbar_prefs
.append_to_menu
, 0, GTK_PACK_START
);
478 /* Returns the position for adding new toolbar items. The returned position can be used
479 * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed
480 * with @a geany->main_widgets->toolbar.
481 * The position is always the last one before the Quit button or the very last position if the
482 * Quit button is not the last toolbar item.
484 * @return The position for new toolbar items.
486 gint
toolbar_get_insert_position(void)
488 GtkWidget
*quit
= toolbar_get_widget_by_name("Quit");
489 gint quit_pos
= -1, pos
;
492 quit_pos
= gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets
.toolbar
), GTK_TOOL_ITEM(quit
));
494 pos
= gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets
.toolbar
));
495 if (quit_pos
== (pos
- 1))
497 /* if the toolbar item before the quit button is a separator, insert new items before */
498 if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item(
499 GTK_TOOLBAR(main_widgets
.toolbar
), quit_pos
- 1)))
503 /* else return the position of the quit button to insert new items before */
511 void toolbar_finalize(void)
513 g_object_unref(geany_menu_button_action_get_menu(
514 GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open"))));
516 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
518 g_object_unref(group
);
520 g_slist_free(plugin_items
);
524 void toolbar_show_hide(void)
526 ignore_callback
= TRUE
;
527 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
528 ui_lookup_widget(main_widgets
.window
, "menu_show_toolbar1")), toolbar_prefs
.visible
);
529 ui_widget_show_hide(main_widgets
.toolbar
, toolbar_prefs
.visible
);
530 ignore_callback
= FALSE
;
534 /* sets the icon style of the toolbar */
535 static void toolbar_set_icon_style(void)
539 icon_style
= toolbar_prefs
.icon_style
;
541 if (toolbar_prefs
.use_gtk_default_style
)
542 icon_style
= ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs
.icon_style
);
544 gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets
.toolbar
), icon_style
);
548 /* sets the icon size of the toolbar */
549 static void toolbar_set_icon_size(void)
553 icon_size
= toolbar_prefs
.icon_size
;
555 if (toolbar_prefs
.use_gtk_default_icon
)
556 icon_size
= ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs
.icon_size
);
558 gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets
.toolbar
), icon_size
);
562 void toolbar_apply_settings(void)
564 toolbar_set_icon_style();
565 toolbar_set_icon_size();
569 #define TB_EDITOR_SEPARATOR _("Separator")
570 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
575 GtkTreeView
*tree_available
;
576 GtkTreeView
*tree_used
;
578 GtkListStore
*store_available
;
579 GtkListStore
*store_used
;
581 GtkTreePath
*last_drag_path
;
582 GtkTreeViewDropPosition last_drag_pos
;
584 GtkWidget
*drag_source
;
587 static const GtkTargetEntry tb_editor_dnd_targets
[] =
589 { "GEANY_TB_EDITOR_ROW", 0, 0 }
591 static const gint tb_editor_dnd_targets_len
= G_N_ELEMENTS(tb_editor_dnd_targets
);
595 TB_EDITOR_COL_ACTION
,
601 static void tb_editor_handler_start_element(GMarkupParseContext
*context
, const gchar
*element_name
,
602 const gchar
**attribute_names
,
603 const gchar
**attribute_values
, gpointer data
,
607 GSList
**actions
= data
;
609 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
610 if (utils_str_equal(element_name
, "separator"))
611 *actions
= g_slist_append(*actions
, g_strdup(TB_EDITOR_SEPARATOR
));
613 for (i
= 0; attribute_names
[i
] != NULL
; i
++)
615 if (utils_str_equal(attribute_names
[i
], "action"))
617 *actions
= g_slist_append(*actions
, g_strdup(attribute_values
[i
]));
623 static const GMarkupParser tb_editor_xml_parser
=
625 tb_editor_handler_start_element
, NULL
, NULL
, NULL
, NULL
629 static GSList
*tb_editor_parse_ui(const gchar
*buffer
, gssize length
, GError
**error
)
631 GMarkupParseContext
*context
;
634 context
= g_markup_parse_context_new(&tb_editor_xml_parser
, 0, &list
, NULL
);
635 g_markup_parse_context_parse(context
, buffer
, length
, error
);
636 g_markup_parse_context_free(context
);
642 static void tb_editor_set_item_values(const gchar
*name
, GtkListStore
*store
, GtkTreeIter
*iter
)
646 gchar
*label_clean
= NULL
;
649 action
= gtk_action_group_get_action(group
, name
);
652 if (utils_str_equal(name
, TB_EDITOR_SEPARATOR
))
653 label_clean
= g_strdup(TB_EDITOR_SEPARATOR_LABEL
);
659 g_object_get(action
, "icon-name", &icon
, NULL
);
661 g_object_get(action
, "stock-id", &icon
, NULL
);
663 g_object_get(action
, "label", &label
, NULL
);
665 label_clean
= utils_str_remove_chars(g_strdup(label
), "_");
668 gtk_list_store_set(store
, iter
,
669 TB_EDITOR_COL_ACTION
, name
,
670 TB_EDITOR_COL_LABEL
, label_clean
,
671 TB_EDITOR_COL_ICON
, icon
,
680 static void tb_editor_scroll_to_iter(GtkTreeView
*treeview
, GtkTreeIter
*iter
)
682 GtkTreePath
*path
= gtk_tree_model_get_path(gtk_tree_view_get_model(treeview
), iter
);
683 gtk_tree_view_scroll_to_cell(treeview
, path
, NULL
, TRUE
, 0.5, 0.0);
684 gtk_tree_path_free(path
);
688 static void tb_editor_free_path(TBEditorWidget
*tbw
)
690 if (tbw
->last_drag_path
!= NULL
)
692 gtk_tree_path_free(tbw
->last_drag_path
);
693 tbw
->last_drag_path
= NULL
;
698 static void tb_editor_btn_remove_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
700 GtkTreeModel
*model_used
;
701 GtkTreeSelection
*selection_used
;
702 GtkTreeIter iter_used
, iter_new
;
705 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
706 if (gtk_tree_selection_get_selected(selection_used
, &model_used
, &iter_used
))
708 gtk_tree_model_get(model_used
, &iter_used
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
709 if (gtk_list_store_remove(tbw
->store_used
, &iter_used
))
710 gtk_tree_selection_select_iter(selection_used
, &iter_used
);
712 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
714 gtk_list_store_append(tbw
->store_available
, &iter_new
);
715 tb_editor_set_item_values(action_name
, tbw
->store_available
, &iter_new
);
716 tb_editor_scroll_to_iter(tbw
->tree_available
, &iter_new
);
724 static void tb_editor_btn_add_clicked_cb(GtkWidget
*button
, TBEditorWidget
*tbw
)
726 GtkTreeModel
*model_available
;
727 GtkTreeSelection
*selection_available
, *selection_used
;
728 GtkTreeIter iter_available
, iter_new
, iter_selected
;
731 selection_available
= gtk_tree_view_get_selection(tbw
->tree_available
);
732 if (gtk_tree_selection_get_selected(selection_available
, &model_available
, &iter_available
))
734 gtk_tree_model_get(model_available
, &iter_available
,
735 TB_EDITOR_COL_ACTION
, &action_name
, -1);
736 if (! utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
738 if (gtk_list_store_remove(tbw
->store_available
, &iter_available
))
739 gtk_tree_selection_select_iter(selection_available
, &iter_available
);
742 selection_used
= gtk_tree_view_get_selection(tbw
->tree_used
);
743 if (gtk_tree_selection_get_selected(selection_used
, NULL
, &iter_selected
))
744 gtk_list_store_insert_before(tbw
->store_used
, &iter_new
, &iter_selected
);
746 gtk_list_store_append(tbw
->store_used
, &iter_new
);
748 tb_editor_set_item_values(action_name
, tbw
->store_used
, &iter_new
);
749 tb_editor_scroll_to_iter(tbw
->tree_used
, &iter_new
);
756 static gboolean
tb_editor_drag_motion_cb(GtkWidget
*widget
, GdkDragContext
*drag_context
,
757 gint x
, gint y
, guint ltime
, TBEditorWidget
*tbw
)
759 if (tbw
->last_drag_path
!= NULL
)
760 gtk_tree_path_free(tbw
->last_drag_path
);
761 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget
),
762 &(tbw
->last_drag_path
), &(tbw
->last_drag_pos
));
768 static void tb_editor_drag_data_get_cb(GtkWidget
*widget
, GdkDragContext
*context
,
769 GtkSelectionData
*data
, guint info
, guint ltime
,
773 GtkTreeSelection
*selection
;
778 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
779 if (! gtk_tree_selection_get_selected(selection
, &model
, &iter
))
782 gtk_tree_model_get(model
, &iter
, TB_EDITOR_COL_ACTION
, &name
, -1);
786 atom
= gdk_atom_intern(tb_editor_dnd_targets
[0].target
, FALSE
);
787 gtk_selection_data_set(data
, atom
, 8, (guchar
*) name
, strlen(name
));
791 tbw
->drag_source
= widget
;
795 static void tb_editor_drag_data_rcvd_cb(GtkWidget
*widget
, GdkDragContext
*context
,
796 gint x
, gint y
, GtkSelectionData
*data
, guint info
,
797 guint ltime
, TBEditorWidget
*tbw
)
799 GtkTreeView
*tree
= GTK_TREE_VIEW(widget
);
800 gboolean del
= FALSE
;
802 if (data
->length
>= 0 && data
->format
== 8)
807 text
= (gchar
*) data
->data
;
808 is_sep
= utils_str_equal(text
, TB_EDITOR_SEPARATOR
);
809 /* If the source of the action is equal to the target, we do just re-order and so need
810 * to delete the separator to get it moved, not just copied. */
811 if (is_sep
&& widget
== tbw
->drag_source
)
814 if (tree
!= tbw
->tree_available
|| ! is_sep
)
816 GtkTreeIter iter
, iter_before
, *iter_before_ptr
;
817 GtkListStore
*store
= GTK_LIST_STORE(gtk_tree_view_get_model(tree
));
819 if (tbw
->last_drag_path
!= NULL
)
821 gtk_tree_model_get_iter(GTK_TREE_MODEL(store
), &iter_before
, tbw
->last_drag_path
);
823 if (gtk_list_store_iter_is_valid(store
, &iter_before
))
824 iter_before_ptr
= &iter_before
;
826 iter_before_ptr
= NULL
;
828 if (tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_BEFORE
||
829 tbw
->last_drag_pos
== GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
)
830 gtk_list_store_insert_before(store
, &iter
, iter_before_ptr
);
832 gtk_list_store_insert_after(store
, &iter
, iter_before_ptr
);
835 gtk_list_store_append(store
, &iter
);
837 tb_editor_set_item_values(text
, store
, &iter
);
838 tb_editor_scroll_to_iter(tree
, &iter
);
840 if (tree
!= tbw
->tree_used
|| ! is_sep
)
844 tbw
->drag_source
= NULL
; /* reset the value just to be sure */
845 tb_editor_free_path(tbw
);
846 gtk_drag_finish(context
, TRUE
, del
, ltime
);
850 static gboolean
tb_editor_foreach_used(GtkTreeModel
*model
, GtkTreePath
*path
,
851 GtkTreeIter
*iter
, gpointer data
)
855 gtk_tree_model_get(model
, iter
, TB_EDITOR_COL_ACTION
, &action_name
, -1);
857 if (utils_str_equal(action_name
, TB_EDITOR_SEPARATOR
))
858 g_string_append_printf(data
, "\t\t<separator/>\n");
859 else if (NZV(action_name
))
860 g_string_append_printf(data
, "\t\t<toolitem action='%s' />\n", action_name
);
867 static void tb_editor_write_markup(TBEditorWidget
*tbw
)
869 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
870 const gchar
*template = "<ui>\n<!--\n\
871 This is Geany's toolbar UI definition.\nThe DTD can be found at \n\
872 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
873 You can re-order all items and freely add and remove available actions.\n\
874 You cannot add new actions which are not listed in the documentation.\n\
875 Everything you add or change must be inside the /ui/toolbar/ path.\n\n\
876 For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\
877 editor in Geany.\n\n\
878 A list of available actions can be found in the documentation included with Geany or\n\
879 at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\
880 \t<toolbar name='GeanyToolbar'>\n";
881 const gchar
*filename
= utils_build_path(app
->configdir
, "ui_toolbar.xml", NULL
);
882 GString
*str
= g_string_new(template);
884 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw
->store_used
), tb_editor_foreach_used
, str
);
886 g_string_append(str
, "\n\t</toolbar>\n</ui>\n");
888 toolbar_reload(str
->str
);
890 utils_write_file(filename
, str
->str
);
892 g_string_free(str
, TRUE
);
896 static void tb_editor_available_items_changed_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
897 GtkTreeIter
*arg2
, TBEditorWidget
*tbw
)
899 tb_editor_write_markup(tbw
);
903 static void tb_editor_available_items_deleted_cb(GtkTreeModel
*model
, GtkTreePath
*arg1
,
906 tb_editor_write_markup(tbw
);
910 static TBEditorWidget
*tb_editor_create_dialog(GtkWindow
*parent
)
912 GtkWidget
*dialog
, *vbox
, *hbox
, *vbox_buttons
, *button_add
, *button_remove
;
913 GtkWidget
*swin_available
, *swin_used
, *tree_available
, *tree_used
, *label
;
914 GtkCellRenderer
*text_renderer
, *icon_renderer
;
915 GtkTreeViewColumn
*column
;
916 TBEditorWidget
*tbw
= g_new(TBEditorWidget
, 1);
919 parent
= GTK_WINDOW(main_widgets
.window
);
921 dialog
= gtk_dialog_new_with_buttons(_("Customize Toolbar"),
923 GTK_DIALOG_DESTROY_WITH_PARENT
,
924 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
, NULL
);
925 vbox
= ui_dialog_vbox_new(GTK_DIALOG(dialog
));
926 gtk_box_set_spacing(GTK_BOX(vbox
), 6);
927 gtk_widget_set_name(dialog
, "GeanyDialog");
928 gtk_window_set_default_size(GTK_WINDOW(dialog
), -1, 400);
929 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_CLOSE
);
931 tbw
->store_available
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
932 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
933 tbw
->store_used
= gtk_list_store_new(TB_EDITOR_COLS_MAX
,
934 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
936 label
= gtk_label_new(
937 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
938 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
940 tree_available
= gtk_tree_view_new();
941 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available
), GTK_TREE_MODEL(tbw
->store_available
));
942 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available
), TRUE
);
943 gtk_tree_sortable_set_sort_column_id(
944 GTK_TREE_SORTABLE(tbw
->store_available
), TB_EDITOR_COL_LABEL
, GTK_SORT_ASCENDING
);
946 icon_renderer
= gtk_cell_renderer_pixbuf_new();
947 column
= gtk_tree_view_column_new_with_attributes(
948 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
949 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
951 text_renderer
= gtk_cell_renderer_text_new();
952 column
= gtk_tree_view_column_new_with_attributes(
953 _("Available Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
954 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available
), column
);
956 swin_available
= gtk_scrolled_window_new(NULL
, NULL
);
957 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available
),
958 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
959 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available
), GTK_SHADOW_ETCHED_IN
);
960 gtk_container_add(GTK_CONTAINER(swin_available
), tree_available
);
962 tree_used
= gtk_tree_view_new();
963 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used
), GTK_TREE_MODEL(tbw
->store_used
));
964 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used
), TRUE
);
965 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used
), TRUE
);
967 icon_renderer
= gtk_cell_renderer_pixbuf_new();
968 column
= gtk_tree_view_column_new_with_attributes(
969 NULL
, icon_renderer
, "stock-id", TB_EDITOR_COL_ICON
, NULL
);
970 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
972 text_renderer
= gtk_cell_renderer_text_new();
973 column
= gtk_tree_view_column_new_with_attributes(
974 _("Displayed Items"), text_renderer
, "text", TB_EDITOR_COL_LABEL
, NULL
);
975 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used
), column
);
977 swin_used
= gtk_scrolled_window_new(NULL
, NULL
);
978 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used
),
979 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
980 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used
), GTK_SHADOW_ETCHED_IN
);
981 gtk_container_add(GTK_CONTAINER(swin_used
), tree_used
);
984 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available
), GDK_BUTTON1_MASK
,
985 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
986 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available
),
987 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
988 g_signal_connect(tree_available
, "drag-data-get",
989 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
990 g_signal_connect(tree_available
, "drag-data-received",
991 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
992 g_signal_connect(tree_available
, "drag-motion",
993 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
995 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used
), GDK_BUTTON1_MASK
,
996 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
997 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used
),
998 tb_editor_dnd_targets
, tb_editor_dnd_targets_len
, GDK_ACTION_MOVE
);
999 g_signal_connect(tree_used
, "drag-data-get",
1000 G_CALLBACK(tb_editor_drag_data_get_cb
), tbw
);
1001 g_signal_connect(tree_used
, "drag-data-received",
1002 G_CALLBACK(tb_editor_drag_data_rcvd_cb
), tbw
);
1003 g_signal_connect(tree_used
, "drag-motion",
1004 G_CALLBACK(tb_editor_drag_motion_cb
), tbw
);
1007 button_add
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, NULL
);
1008 button_remove
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, NULL
);
1009 g_signal_connect(button_add
, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb
), tbw
);
1010 g_signal_connect(button_remove
, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb
), tbw
);
1012 vbox_buttons
= gtk_vbox_new(FALSE
, 6);
1013 /* FIXME this is a little hack'ish, any better ideas? */
1014 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1015 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_add
, FALSE
, FALSE
, 0);
1016 gtk_box_pack_start(GTK_BOX(vbox_buttons
), button_remove
, FALSE
, FALSE
, 0);
1017 gtk_box_pack_start(GTK_BOX(vbox_buttons
), gtk_label_new(""), TRUE
, TRUE
, 0);
1019 hbox
= gtk_hbox_new(FALSE
, 6);
1020 gtk_box_pack_start(GTK_BOX(hbox
), swin_available
, TRUE
, TRUE
, 0);
1021 gtk_box_pack_start(GTK_BOX(hbox
), vbox_buttons
, FALSE
, FALSE
, 0);
1022 gtk_box_pack_start(GTK_BOX(hbox
), swin_used
, TRUE
, TRUE
, 0);
1024 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 6);
1025 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, TRUE
, 0);
1027 gtk_widget_show_all(vbox
);
1029 g_object_unref(tbw
->store_available
);
1030 g_object_unref(tbw
->store_used
);
1032 tbw
->dialog
= dialog
;
1033 tbw
->tree_available
= GTK_TREE_VIEW(tree_available
);
1034 tbw
->tree_used
= GTK_TREE_VIEW(tree_used
);
1036 tbw
->last_drag_path
= NULL
;
1042 void toolbar_configure(GtkWindow
*parent
)
1046 GSList
*sl
, *used_items
;
1047 GList
*l
, *all_items
;
1050 TBEditorWidget
*tbw
;
1052 /* read the current active toolbar items */
1053 markup
= gtk_ui_manager_get_ui(uim
);
1054 used_items
= tb_editor_parse_ui(markup
, strlen(markup
), NULL
);
1057 /* get all available actions */
1058 all_items
= gtk_action_group_list_actions(group
);
1060 /* create the GUI */
1061 tbw
= tb_editor_create_dialog(parent
);
1063 /* fill the stores */
1064 gtk_list_store_insert_with_values(tbw
->store_available
, NULL
, -1,
1065 TB_EDITOR_COL_ACTION
, TB_EDITOR_SEPARATOR
,
1066 TB_EDITOR_COL_LABEL
, TB_EDITOR_SEPARATOR_LABEL
,
1068 foreach_list(l
, all_items
)
1070 name
= gtk_action_get_name(l
->data
);
1071 if (g_slist_find_custom(used_items
, name
, (GCompareFunc
) strcmp
) == NULL
)
1073 gtk_list_store_append(tbw
->store_available
, &iter
);
1074 tb_editor_set_item_values(name
, tbw
->store_available
, &iter
);
1077 foreach_slist(sl
, used_items
)
1079 gtk_list_store_append(tbw
->store_used
, &iter
);
1080 tb_editor_set_item_values(sl
->data
, tbw
->store_used
, &iter
);
1082 /* select first item */
1083 path
= gtk_tree_path_new_from_string("0");
1084 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw
->tree_used
), path
);
1085 gtk_tree_path_free(path
);
1087 /* connect the changed signals after populating the store */
1088 g_signal_connect(tbw
->store_used
, "row-changed",
1089 G_CALLBACK(tb_editor_available_items_changed_cb
), tbw
);
1090 g_signal_connect(tbw
->store_used
, "row-deleted",
1091 G_CALLBACK(tb_editor_available_items_deleted_cb
), tbw
);
1094 gtk_dialog_run(GTK_DIALOG(tbw
->dialog
));
1096 gtk_widget_destroy(tbw
->dialog
);
1098 g_slist_foreach(used_items
, (GFunc
) g_free
, NULL
);
1099 g_slist_free(used_items
);
1100 g_list_free(all_items
);
1101 tb_editor_free_path(tbw
);