1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 * toolbar.c - this file was part of Geany (v1.24.1), a fast and lightweight IDE
5 * Copyright 2009-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
6 * Copyright 2009-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * Copyright 2014 Rob Norris <rw_norris@hotmail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 /* Utility functions to create the toolbar */
39 #include <glib/gi18n.h>
40 #include <glib/gstdio.h>
41 #include <gdk/gdkkeysyms.h>
43 #include "preferences.h"
46 struct _VikToolbarClass
48 GObjectClass object_class;
56 GtkActionGroup *group_actions;
57 GtkActionGroup *group_toggles;
58 GtkActionGroup *group_tools;
59 GtkActionGroup *group_modes;
60 GSList *list_of_actions;
61 GSList *list_of_toggles;
62 GSList *list_of_tools;
63 GSList *list_of_modes;
66 G_DEFINE_TYPE (VikToolbar, vik_toolbar, G_TYPE_OBJECT)
68 static void vik_toolbar_class_init (VikToolbarClass *klass)
72 static void vik_toolbar_init (VikToolbar *vtb)
76 vtb->list_of_actions = NULL;
77 vtb->list_of_toggles = NULL;
78 vtb->list_of_tools = NULL;
79 vtb->list_of_modes = NULL;
82 VikToolbar *vik_toolbar_new ()
84 VikToolbar *vtb = (VikToolbar *)g_object_new(vik_toolbar_get_type(), NULL);
88 #define TOOLBAR_PARAMS_GROUP_KEY "toolbar"
89 #define TOOLBAR_PARAMS_NAMESPACE "toolbar."
91 static gchar *params_icon_size[] = { N_("System Default"), N_("Small"), N_("Medium"), N_("Large"), NULL };
92 static gchar *params_icon_style[] = { N_("System Default"), N_("Icons Only"), N_("Text Only"), N_("Icons and Text"), NULL };
103 static config_t extra_widget_data;
105 static VikLayerParam prefs[] = {
106 { VIK_LAYER_NUM_TYPES, TOOLBAR_PARAMS_NAMESPACE "append_to_menu", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Append to Menu:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL,
107 N_("Pack the toolbar to the main menu to save vertical space"), NULL, NULL, NULL },
108 { VIK_LAYER_NUM_TYPES, TOOLBAR_PARAMS_NAMESPACE "icon_size", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Icon Size:"), VIK_LAYER_WIDGET_COMBOBOX, params_icon_size, NULL,
109 NULL, NULL, NULL, NULL },
110 { VIK_LAYER_NUM_TYPES, TOOLBAR_PARAMS_NAMESPACE "icon_style", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Icon Style:"), VIK_LAYER_WIDGET_COMBOBOX, params_icon_style, NULL,
111 NULL, NULL, NULL, NULL },
112 { VIK_LAYER_NUM_TYPES, TOOLBAR_PARAMS_NAMESPACE "NOTSAVED1", VIK_LAYER_PARAM_PTR, VIK_LAYER_GROUP_NONE, N_("Customize:"), VIK_LAYER_WIDGET_BUTTON, N_("Customize Buttons"), NULL,
113 NULL, NULL, NULL, NULL },
116 // Global storage to enable freeing upon closure
117 static GHashTable *signal_data;
118 static GSList *toggle_overrides = NULL;
120 // Forward declaration
121 void toolbar_configure (VikToolbar *vtb, GtkWidget *toolbar, GtkWindow *parent, GtkWidget *vbox, GtkWidget *hbox, ReloadCB reload_cb, gpointer user_data);
123 void toolbar_configure_cb(void)
125 // Values not known at prefs initialization.
126 // So trying to pass these values via the UI builder is not possible currently.
127 // ATM cheat via internal values - although this doesn't work properly for multiple Windows...
128 toolbar_configure ( extra_widget_data.vtb,
129 extra_widget_data.vtb->widget,
130 extra_widget_data.parent,
131 extra_widget_data.vbox,
132 extra_widget_data.hbox,
133 extra_widget_data.reload_cb,
134 extra_widget_data.user_data );
140 * Initialize stuff for the toolbar.
142 void a_toolbar_init (void)
145 a_preferences_register_group ( TOOLBAR_PARAMS_GROUP_KEY, _("Toolbar") );
148 VikLayerParamData tmp;
150 a_preferences_register (&prefs[i++], tmp, TOOLBAR_PARAMS_GROUP_KEY);
152 a_preferences_register (&prefs[i++], tmp, TOOLBAR_PARAMS_GROUP_KEY);
154 a_preferences_register (&prefs[i++], tmp, TOOLBAR_PARAMS_GROUP_KEY);
155 tmp.ptr = toolbar_configure_cb;
156 a_preferences_register (&prefs[i++], tmp, TOOLBAR_PARAMS_GROUP_KEY);
159 signal_data = g_hash_table_new_full ( g_direct_hash, g_direct_equal, NULL, g_free );
165 * Uninitialize toolbar related stuff.
167 void a_toolbar_uninit ( void )
169 g_hash_table_destroy ( signal_data );
170 g_slist_foreach ( toggle_overrides, (GFunc)g_free, NULL );
171 g_slist_free ( toggle_overrides );
174 static gboolean prefs_get_append_to_menu (void)
176 return a_preferences_get(TOOLBAR_PARAMS_NAMESPACE "append_to_menu")->b;
179 static guint prefs_get_icon_size (void)
181 return a_preferences_get(TOOLBAR_PARAMS_NAMESPACE "icon_size")->b;
184 static guint prefs_get_icon_style (void)
186 return a_preferences_get(TOOLBAR_PARAMS_NAMESPACE "icon_style")->b;
189 /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either
190 * update the widget pointer in this case (i.e. request it again) or better use
191 * toolbar_get_action_by_name() instead. The action objects will remain the same even when the
192 * toolbar is reloaded. */
193 GtkWidget *toolbar_get_widget_by_name(VikToolbar *vtb, const gchar *name)
198 g_return_val_if_fail(name != NULL, NULL);
199 g_return_val_if_fail(VIK_IS_TOOLBAR(vtb), NULL);
201 path = g_strconcat("/ui/MainToolbar/", name, NULL);
202 widget = gtk_ui_manager_get_widget(vtb->uim, path);
208 static GtkAction *get_action ( VikToolbar *vtb, const gchar *name )
211 GtkAction *action = gtk_action_group_get_action (vtb->group_actions, name);
213 action = gtk_action_group_get_action (vtb->group_tools, name);
215 action = gtk_action_group_get_action (vtb->group_toggles, name);
217 action = gtk_action_group_get_action (vtb->group_modes, name);
222 * toolbar_get_action_by_name:
224 * Find an action in the specified toolbar via the action name
226 GtkAction *toolbar_get_action_by_name(VikToolbar *vtb, const gchar *name)
228 g_return_val_if_fail(name != NULL, NULL);
229 g_return_val_if_fail(VIK_IS_TOOLBAR(vtb), NULL);
231 return get_action(vtb,name);
235 * toolbar_action_tool_entry_register:
237 * Register a tool button in the specified toolbar
238 * Only one of these tools can be active at a time (hence it is a GtkRadioActionEntry)
240 void toolbar_action_tool_entry_register(VikToolbar *vtb, GtkRadioActionEntry *action)
242 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
243 g_return_if_fail(action != NULL);
244 vtb->list_of_tools = g_slist_append(vtb->list_of_tools, action);
248 * toolbar_action_mode_entry_register:
250 * Register a drawing projection mode button in the specified toolbar
251 * Only one of these modes can be active at a time (hence it is a GtkRadioActionEntry)
253 void toolbar_action_mode_entry_register(VikToolbar *vtb, GtkRadioActionEntry *action)
255 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
256 g_return_if_fail(action != NULL);
257 vtb->list_of_modes = g_slist_append(vtb->list_of_modes, action);
261 * toolbar_action_toggle_entry_register:
263 * Register a toggle button in the specified toolbar with the specified callback
264 * Used in preventing circluar callbacks of a toolbar toggle event calling the menu toggle event
265 * (that then calls toolbar callback and so on and so on...)
266 * The toggle action must be given a pointer to a function that is used on the callback for toolbar only
267 * (that must offer a way to have a finite call chain!)
269 void toolbar_action_toggle_entry_register(VikToolbar *vtb, GtkToggleActionEntry *action, gpointer callback)
271 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
272 g_return_if_fail(action != NULL);
274 GtkToggleActionEntry *myaction = g_malloc (sizeof (GtkToggleActionEntry) );
275 memcpy ( myaction, action, sizeof (GtkToggleActionEntry) );
276 // Overwrite with specific callback
277 myaction->callback = callback;
278 vtb->list_of_toggles = g_slist_append(vtb->list_of_toggles, myaction);
280 // Store override so it can be freed upon toolbar destruction
281 toggle_overrides = g_slist_append ( toggle_overrides, myaction );
285 * toolbar_action_entry_register:
287 * Register a standard action button in the specified toolbar
289 void toolbar_action_entry_register(VikToolbar *vtb, GtkActionEntry *action)
291 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
292 g_return_if_fail(action != NULL);
293 vtb->list_of_actions = g_slist_append(vtb->list_of_actions, action);
296 static void configure_cb (GtkWidget *widget, gpointer user_data)
298 config_t *data = (config_t*)user_data;
299 toolbar_configure ( data->vtb, data->vtb->widget, data->parent, data->vbox, data->hbox, data->reload_cb, data->user_data);
302 static gboolean toolbar_popup_menu (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
304 // Only display menu on right button clicks
305 if (event->button == 3) {
307 tmenu = gtk_menu_new();
308 GtkWidget *item = gtk_menu_item_new_with_mnemonic ( _("_Customize") );
309 g_signal_connect ( G_OBJECT(item), "activate", G_CALLBACK(configure_cb), user_data );
310 gtk_menu_shell_append ( GTK_MENU_SHELL(tmenu), item );
311 gtk_menu_popup ( GTK_MENU(tmenu), NULL, NULL, NULL, NULL, event->button, event->time );
312 gtk_widget_show_all ( GTK_WIDGET(tmenu) );
313 g_object_ref_sink (tmenu);
319 /* sets the icon style of the toolbar */
320 static void toolbar_set_icon_style (GtkWidget *toolbar)
322 gint icon_style = prefs_get_icon_style();
325 icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", GTK_TOOLBAR_ICONS);
327 // Adjust to enum GtkToolbarStyle
330 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), icon_style);
334 /* sets the icon size of the toolbar */
335 static void toolbar_set_icon_size (GtkWidget *toolbar)
337 gint icon_size = prefs_get_icon_size();
339 if ( icon_size == 0 )
340 icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", GTK_ICON_SIZE_SMALL_TOOLBAR);
342 // Adjust to enum GtkIconSize
343 if ( icon_size == 1 )
344 icon_size = GTK_ICON_SIZE_SMALL_TOOLBAR;
345 else if ( icon_size == 2 )
346 icon_size = GTK_ICON_SIZE_LARGE_TOOLBAR;
347 else if ( icon_size == 3 )
348 icon_size = GTK_ICON_SIZE_DND;
351 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), icon_size);
355 * toolbar_apply_settings:
356 * @vbox: Potential vertical container for the specified toolbar
357 * @hbox: Potential horizontal container for the specified toolbar
358 * @Reset: Specify if the toolbar should be reparented
359 * (when called externally this should always be TRUE)
361 * Updates the specified toolbar with current setting values
363 void toolbar_apply_settings(VikToolbar *vtb,
368 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
371 g_object_ref (vtb->widget); // ensure not deleted when removed
372 // Try both places it could be
373 if ( gtk_widget_get_parent (vtb->widget) == hbox )
374 gtk_container_remove(GTK_CONTAINER(hbox), vtb->widget );
375 if ( gtk_widget_get_parent (vtb->widget) == vbox )
376 gtk_container_remove(GTK_CONTAINER(vbox), vtb->widget );
379 toolbar_set_icon_style(vtb->widget);
380 toolbar_set_icon_size(vtb->widget);
382 /* add the toolbar again to the main window */
383 // Use reorder to ensure toolbar always comes after the menu
384 if (prefs_get_append_to_menu())
387 gtk_box_pack_start(GTK_BOX(hbox), vtb->widget, TRUE, TRUE, 0);
388 gtk_box_reorder_child(GTK_BOX(hbox), vtb->widget, 1);
394 gtk_box_pack_start(GTK_BOX(vbox), vtb->widget, FALSE, TRUE, 0);
395 gtk_box_reorder_child(GTK_BOX(vbox), vtb->widget, 1);
401 * toolbar_get_widget:
404 GtkWidget* toolbar_get_widget(VikToolbar *vtb)
406 g_return_val_if_fail(VIK_IS_TOOLBAR(vtb), NULL);
410 #include "toolbar.xml.h"
411 static void toolbar_reload ( VikToolbar *vtb,
419 GError *error = NULL;
420 g_debug ( "%s: %d", __FUNCTION__, g_hash_table_size(signal_data) );
422 /* Cleanup old toolbar */
423 if (vtb->merge_id > 0)
426 gtk_widget_destroy(vtb->widget);
428 gtk_ui_manager_remove_ui(vtb->uim, vtb->merge_id);
429 gtk_ui_manager_ensure_update(vtb->uim);
431 g_hash_table_remove ( signal_data, vtb );
436 vtb->merge_id = gtk_ui_manager_add_ui_from_string(vtb->uim, markup, -1, &error);
440 gchar *filename = NULL;
441 /* Load the toolbar UI XML file from disk */
442 // Consider using a_get_viking_data_path() first
443 filename = g_build_filename (a_get_viking_dir(), "ui_toolbar.xml", NULL);
444 vtb->merge_id = gtk_ui_manager_add_ui_from_file(vtb->uim, filename, &error);
449 g_debug("UI creation failed, using internal fallback definition. Error message: %s", error->message);
453 /* finally load the internally defined markup as fallback */
454 vtb->merge_id = gtk_ui_manager_add_ui_from_string(vtb->uim, toolbar_xml, -1, &error);
456 // Abort - this should only happen if you're missing around with the code
457 g_error("Internal UI creation failed. Error message: %s", error->message);
461 vtb->widget = gtk_ui_manager_get_widget(vtb->uim, "/ui/MainToolbar");
463 /* update button states */
464 reload_cb ( vtb->group_actions, user_data );
466 toolbar_apply_settings(vtb, vbox, hbox, FALSE);
468 gtk_widget_show(vtb->widget);
471 config_t *data = g_malloc(sizeof(config_t));
473 data->parent = parent;
476 data->reload_cb = reload_cb;
477 data->user_data = user_data;
479 // Store data in a hash so it can be freed when the toolbar is reconfigured
480 g_hash_table_insert (signal_data, vtb, data);
482 g_signal_connect(vtb->widget, "button-press-event", G_CALLBACK(toolbar_popup_menu), data);
484 /* We don't need to disconnect those signals as this is done automatically when the entry
485 * widgets are destroyed, happens when the toolbar itself is destroyed. */
488 static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data)
490 const gchar *arg_name = g_param_spec_get_name(arg1);
493 if (prefs_get_icon_style() == 0 && !g_strcmp0(arg_name, "gtk-toolbar-style"))
495 value = ui_get_gtk_settings_integer(arg_name, GTK_TOOLBAR_ICONS);
496 if ( GTK_IS_TOOLBAR (data) )
497 gtk_toolbar_set_style(GTK_TOOLBAR(data), value);
499 else if (prefs_get_icon_size() == 0 && !g_strcmp0(arg_name, "gtk-toolbar-size"))
501 value = ui_get_gtk_settings_integer(arg_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
502 if ( GTK_IS_TOOLBAR (data) )
503 gtk_toolbar_set_icon_size(GTK_TOOLBAR(data), value);
510 * Initialize the specified toolbar using the given values
512 void toolbar_init (VikToolbar *vtb,
520 vtb->uim = gtk_ui_manager_new();
522 vtb->group_actions = gtk_action_group_new("MainToolbar");
523 gtk_action_group_set_translation_domain(vtb->group_actions, GETTEXT_PACKAGE);
524 GtkActionEntry *actions = NULL;
527 foreach_slist(gl, vtb->list_of_actions) {
528 GtkActionEntry *action = gl->data;
529 actions = g_renew(GtkActionEntry, actions, nn+1);
530 actions[nn] = *action;
533 gtk_action_group_add_actions(vtb->group_actions, actions, nn, user_data);
534 gtk_ui_manager_insert_action_group(vtb->uim, vtb->group_actions, 0);
536 vtb->group_toggles = gtk_action_group_new("UIItems");
537 gtk_action_group_set_translation_domain(vtb->group_toggles, GETTEXT_PACKAGE);
538 GtkToggleActionEntry *toggle_actions = NULL;
540 foreach_slist(gl, vtb->list_of_toggles) {
541 GtkToggleActionEntry *action = gl->data;
542 toggle_actions = g_renew(GtkToggleActionEntry, toggle_actions, nn+1);
543 toggle_actions[nn] = *action;
546 gtk_action_group_add_toggle_actions(vtb->group_toggles, toggle_actions, nn, user_data);
547 gtk_ui_manager_insert_action_group(vtb->uim, vtb->group_toggles, 0);
549 vtb->group_tools = gtk_action_group_new("ToolItems");
550 gtk_action_group_set_translation_domain(vtb->group_tools, GETTEXT_PACKAGE);
552 GtkRadioActionEntry *tool_actions = NULL;
554 foreach_slist(gl, vtb->list_of_tools) {
555 GtkRadioActionEntry *action = gl->data;
556 tool_actions = g_renew(GtkRadioActionEntry, tool_actions, nn+1);
557 tool_actions[nn] = *action;
558 tool_actions[nn].value = nn;
561 gtk_action_group_add_radio_actions(vtb->group_tools, tool_actions, nn, 0, G_CALLBACK(tool_cb), user_data);
562 gtk_ui_manager_insert_action_group(vtb->uim, vtb->group_tools, 0);
564 vtb->group_modes = gtk_action_group_new("ModeItems");
565 gtk_action_group_set_translation_domain(vtb->group_modes, GETTEXT_PACKAGE);
567 GtkRadioActionEntry *mode_actions = NULL;
569 foreach_slist(gl, vtb->list_of_modes) {
570 GtkRadioActionEntry *action = gl->data;
571 mode_actions = g_renew(GtkRadioActionEntry, mode_actions, nn+1);
572 mode_actions[nn] = *action;
573 mode_actions[nn].value = nn;
576 gtk_action_group_add_radio_actions(vtb->group_modes, mode_actions, nn, 0, G_CALLBACK(tool_cb), user_data);
577 gtk_ui_manager_insert_action_group(vtb->uim, vtb->group_modes, 0);
579 toolbar_reload(vtb, NULL, parent, vbox, hbox, reload_cb, user_data);
581 #if GTK_CHECK_VERSION(3, 0, 0)
582 gtk_style_context_add_class(gtk_widget_get_style_context(vtb->widget), "primary-toolbar");
585 GtkSettings *gtk_settings = gtk_widget_get_settings(vtb->widget);
586 if (gtk_settings != NULL)
588 g_signal_connect(gtk_settings, "notify::gtk-toolbar-style",
589 G_CALLBACK(toolbar_notify_style_cb), vtb->widget);
592 extra_widget_data.vtb = vtb;
593 extra_widget_data.parent = parent;
594 extra_widget_data.vbox = vbox;
595 extra_widget_data.reload_cb = reload_cb;
596 extra_widget_data.user_data = user_data;
600 * toolbar_action_set_sensitive:
602 * Set sensitivity of a particular action
604 void toolbar_action_set_sensitive (VikToolbar *vtb, const gchar *name, gboolean sensitive)
606 g_return_if_fail(VIK_IS_TOOLBAR(vtb));
607 g_return_if_fail(name != NULL);
609 GtkAction *action = get_action ( vtb, name );
611 g_object_set ( action, "sensitive", sensitive, NULL);
615 * vik_toolbar_finalize:
617 * Memory cleanups upon toolbar destruction
619 void vik_toolbar_finalize ( VikToolbar *vtb )
621 g_hash_table_remove ( signal_data, vtb );
623 /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */
624 g_object_unref(vtb->uim);
625 g_object_unref(vtb->group_actions);
626 g_object_unref(vtb->group_tools);
627 g_object_unref(vtb->group_toggles);
628 g_object_unref(vtb->group_modes);
630 g_slist_free(vtb->list_of_actions);
631 g_slist_free(vtb->list_of_tools);
632 g_slist_free(vtb->list_of_toggles);
633 g_slist_free(vtb->list_of_modes);
637 #define TB_EDITOR_SEPARATOR _("Separator")
638 #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---")
643 GtkTreeView *tree_available;
644 GtkTreeView *tree_used;
646 GtkListStore *store_available;
647 GtkListStore *store_used;
649 GtkTreePath *last_drag_path;
650 GtkTreeViewDropPosition last_drag_pos;
652 GtkWidget *drag_source;
657 static const GtkTargetEntry tb_editor_dnd_targets[] =
659 { "VIKING_TB_EDITOR_ROW", 0, 0 }
661 static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets);
665 TB_EDITOR_COL_ACTION,
671 static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name,
672 const gchar **attribute_names,
673 const gchar **attribute_values, gpointer data,
677 GSList **actions = data;
679 /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */
680 if (!g_strcmp0(element_name, "separator"))
681 *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR));
683 for (i = 0; attribute_names[i] != NULL; i++)
685 if (!g_strcmp0(attribute_names[i], "action"))
687 *actions = g_slist_append(*actions, g_strdup(attribute_values[i]));
693 static const GMarkupParser tb_editor_xml_parser =
695 tb_editor_handler_start_element, NULL, NULL, NULL, NULL
699 static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error)
701 GMarkupParseContext *context;
704 context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL);
705 g_markup_parse_context_parse(context, buffer, length, error);
706 g_markup_parse_context_free(context);
712 static void tb_editor_set_item_values(VikToolbar *vtb, const gchar *name, GtkListStore *store, GtkTreeIter *iter)
716 gchar *label_clean = NULL;
718 // Tries all action groups
719 GtkAction *action = get_action ( vtb, name );
721 if (action == NULL) {
722 if (!g_strcmp0(name, TB_EDITOR_SEPARATOR))
723 label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL);
727 if (action != NULL) {
728 g_object_get(action, "icon-name", &icon, NULL);
730 g_object_get(action, "stock-id", &icon, NULL);
732 g_object_get(action, "label", &label, NULL);
734 label_clean = util_str_remove_chars(g_strdup(label), "_");
737 gtk_list_store_set(store, iter,
738 TB_EDITOR_COL_ACTION, name,
739 TB_EDITOR_COL_LABEL, label_clean,
740 TB_EDITOR_COL_ICON, icon,
749 static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter)
751 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter);
752 gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0);
753 gtk_tree_path_free(path);
757 static void tb_editor_free_path(TBEditorWidget *tbw)
759 if (tbw->last_drag_path != NULL)
761 gtk_tree_path_free(tbw->last_drag_path);
762 tbw->last_drag_path = NULL;
767 static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
769 GtkTreeModel *model_used;
770 GtkTreeSelection *selection_used;
771 GtkTreeIter iter_used, iter_new;
774 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
775 if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used))
777 gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1);
778 if (gtk_list_store_remove(tbw->store_used, &iter_used))
779 gtk_tree_selection_select_iter(selection_used, &iter_used);
781 if (g_strcmp0(action_name, TB_EDITOR_SEPARATOR))
783 gtk_list_store_append(tbw->store_available, &iter_new);
784 tb_editor_set_item_values(tbw->config.vtb, action_name, tbw->store_available, &iter_new);
785 tb_editor_scroll_to_iter(tbw->tree_available, &iter_new);
793 static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw)
795 GtkTreeModel *model_available;
796 GtkTreeSelection *selection_available, *selection_used;
797 GtkTreeIter iter_available, iter_new, iter_selected;
800 selection_available = gtk_tree_view_get_selection(tbw->tree_available);
801 if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available))
803 gtk_tree_model_get(model_available, &iter_available,
804 TB_EDITOR_COL_ACTION, &action_name, -1);
805 if (g_strcmp0(action_name, TB_EDITOR_SEPARATOR))
807 if (gtk_list_store_remove(tbw->store_available, &iter_available))
808 gtk_tree_selection_select_iter(selection_available, &iter_available);
811 selection_used = gtk_tree_view_get_selection(tbw->tree_used);
812 if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected))
813 gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected);
815 gtk_list_store_append(tbw->store_used, &iter_new);
817 tb_editor_set_item_values(tbw->config.vtb, action_name, tbw->store_used, &iter_new);
818 tb_editor_scroll_to_iter(tbw->tree_used, &iter_new);
825 static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context,
826 gint x, gint y, guint ltime, TBEditorWidget *tbw)
828 if (tbw->last_drag_path != NULL)
829 gtk_tree_path_free(tbw->last_drag_path);
830 gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget),
831 &(tbw->last_drag_path), &(tbw->last_drag_pos));
837 static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
838 GtkSelectionData *data, guint info, guint ltime,
842 GtkTreeSelection *selection;
847 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
848 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
851 gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1);
852 if (G_UNLIKELY(EMPTY(name)))
858 atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE);
859 gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name));
863 tbw->drag_source = widget;
867 static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context,
868 gint x, gint y, GtkSelectionData *data, guint info,
869 guint ltime, TBEditorWidget *tbw)
871 GtkTreeView *tree = GTK_TREE_VIEW(widget);
872 gboolean del = FALSE;
874 if (gtk_selection_data_get_length(data) >= 0 && gtk_selection_data_get_format(data) == 8)
879 text = (gchar*) gtk_selection_data_get_data(data);
880 is_sep = !g_strcmp0(text, TB_EDITOR_SEPARATOR);
881 /* If the source of the action is equal to the target, we do just re-order and so need
882 * to delete the separator to get it moved, not just copied. */
883 if (is_sep && widget == tbw->drag_source)
886 if (tree != tbw->tree_available || ! is_sep)
888 GtkTreeIter iter, iter_before, *iter_before_ptr;
889 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
891 if (tbw->last_drag_path != NULL)
893 gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path);
895 if (gtk_list_store_iter_is_valid(store, &iter_before))
896 iter_before_ptr = &iter_before;
898 iter_before_ptr = NULL;
900 if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE ||
901 tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
902 gtk_list_store_insert_before(store, &iter, iter_before_ptr);
904 gtk_list_store_insert_after(store, &iter, iter_before_ptr);
907 gtk_list_store_append(store, &iter);
909 tb_editor_set_item_values(tbw->config.vtb, text, store, &iter);
910 tb_editor_scroll_to_iter(tree, &iter);
912 if (tree != tbw->tree_used || ! is_sep)
916 tbw->drag_source = NULL; /* reset the value just to be sure */
917 tb_editor_free_path(tbw);
918 gtk_drag_finish(context, TRUE, del, ltime);
922 static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path,
923 GtkTreeIter *iter, gpointer data)
927 gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1);
929 if (!g_strcmp0(action_name, TB_EDITOR_SEPARATOR))
930 g_string_append_printf(data, "\t\t<separator/>\n");
931 else if (G_LIKELY(!EMPTY(action_name)))
932 g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name);
939 static void tb_editor_write_markup(TBEditorWidget *tbw)
941 /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */
942 const gchar *template = "<ui>\n<!--\n\
943 This is Viking's toolbar UI definition.\nThe DTD can be found at \n\
944 http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\
945 Generally one should use the toolbar editor in Viking rather than editing this file.\n\n\
946 For manual changes to this file to take effect, you need to restart Viking.\n-->\n\
947 \t<toolbar name='MainToolbar'>\n";
948 GString *str = g_string_new(template);
950 gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str);
952 g_string_append(str, "\t</toolbar>\n</ui>\n");
954 toolbar_reload(tbw->config.vtb,
959 tbw->config.reload_cb,
960 tbw->config.user_data);
962 // ATM always save the toolbar when changed
963 gchar *filename = g_build_filename(a_get_viking_dir (), "ui_toolbar.xml", NULL);
964 GError *error = NULL;
965 if (! g_file_set_contents(filename, str->str, -1, &error)) {
966 g_warning ("%s: could not write to file %s (%s)", __FUNCTION__, filename, error->message);
971 g_string_free(str, TRUE);
975 static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1,
976 GtkTreeIter *arg2, TBEditorWidget *tbw)
978 tb_editor_write_markup(tbw);
982 static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1,
985 tb_editor_write_markup(tbw);
989 static TBEditorWidget *tb_editor_create_dialog(VikToolbar *vtb, GtkWindow *parent, GtkWidget *toolbar, GtkWidget *vbox, GtkWidget *menu_hbox, ReloadCB reload_cb, gpointer user_data)
991 GtkWidget *dialog, *hbox, *vbox_buttons, *button_add, *button_remove;
992 GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label;
993 GtkCellRenderer *text_renderer, *icon_renderer;
994 GtkTreeViewColumn *column;
996 if (parent == NULL) {
997 g_warning ( "No parent" );
1001 TBEditorWidget *tbw = g_new(TBEditorWidget, 1);
1003 dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"),
1005 GTK_DIALOG_DESTROY_WITH_PARENT,
1006 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
1007 gtk_widget_set_name(dialog, "VikingDialog");
1008 gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400);
1009 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
1011 tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX,
1012 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
1013 tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX,
1014 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
1016 tbw->config.vtb = vtb;
1017 tbw->config.parent = parent;
1018 tbw->config.vbox = vbox;
1019 tbw->config.hbox = menu_hbox;
1020 tbw->config.reload_cb = reload_cb;
1021 tbw->config.user_data = user_data;
1023 label = gtk_label_new(
1024 _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop."));
1025 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1027 tree_available = gtk_tree_view_new();
1028 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available));
1029 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE);
1030 gtk_tree_sortable_set_sort_column_id(
1031 GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING);
1033 icon_renderer = gtk_cell_renderer_pixbuf_new();
1034 column = gtk_tree_view_column_new_with_attributes(
1035 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
1036 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
1038 text_renderer = gtk_cell_renderer_text_new();
1039 column = gtk_tree_view_column_new_with_attributes(
1040 _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
1041 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column);
1043 swin_available = gtk_scrolled_window_new(NULL, NULL);
1044 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available),
1045 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1046 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_ETCHED_IN);
1047 gtk_container_add(GTK_CONTAINER(swin_available), tree_available);
1049 tree_used = gtk_tree_view_new();
1050 gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used));
1051 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE);
1052 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE);
1054 icon_renderer = gtk_cell_renderer_pixbuf_new();
1055 column = gtk_tree_view_column_new_with_attributes(
1056 NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL);
1057 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
1059 text_renderer = gtk_cell_renderer_text_new();
1060 column = gtk_tree_view_column_new_with_attributes(
1061 _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL);
1062 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column);
1064 swin_used = gtk_scrolled_window_new(NULL, NULL);
1065 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used),
1066 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1067 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_ETCHED_IN);
1068 gtk_container_add(GTK_CONTAINER(swin_used), tree_used);
1071 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK,
1072 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1073 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available),
1074 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1075 g_signal_connect(tree_available, "drag-data-get",
1076 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1077 g_signal_connect(tree_available, "drag-data-received",
1078 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1079 g_signal_connect(tree_available, "drag-motion",
1080 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1082 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK,
1083 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1084 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used),
1085 tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE);
1086 g_signal_connect(tree_used, "drag-data-get",
1087 G_CALLBACK(tb_editor_drag_data_get_cb), tbw);
1088 g_signal_connect(tree_used, "drag-data-received",
1089 G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw);
1090 g_signal_connect(tree_used, "drag-motion",
1091 G_CALLBACK(tb_editor_drag_motion_cb), tbw);
1094 button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL);
1095 button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL);
1096 g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw);
1097 g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw);
1099 vbox_buttons = gtk_vbox_new(FALSE, 6);
1100 /* FIXME this is a little hack'ish, any better ideas? */
1101 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1102 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0);
1103 gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0);
1104 gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0);
1106 hbox = gtk_hbox_new(FALSE, 6);
1107 gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0);
1108 gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0);
1109 gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0);
1111 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, FALSE, FALSE, 6);
1112 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 0);
1114 gtk_widget_show_all(dialog);
1116 g_object_unref(tbw->store_available);
1117 g_object_unref(tbw->store_used);
1119 tbw->dialog = dialog;
1120 tbw->tree_available = GTK_TREE_VIEW(tree_available);
1121 tbw->tree_used = GTK_TREE_VIEW(tree_used);
1123 tbw->last_drag_path = NULL;
1129 * toolbar_configure:
1132 void toolbar_configure (VikToolbar *vtb, GtkWidget *toolbar, GtkWindow *parent, GtkWidget *vbox, GtkWidget *hbox, ReloadCB reload_cb, gpointer user_data)
1136 GSList *sl, *used_items;
1137 GList *l, *all_items;
1140 TBEditorWidget *tbw;
1142 /* read the current active toolbar items */
1143 markup = gtk_ui_manager_get_ui(vtb->uim);
1144 used_items = tb_editor_parse_ui(markup, -1, NULL);
1147 /* get all available actions */
1148 all_items = gtk_action_group_list_actions(vtb->group_actions);
1149 all_items = g_list_concat ( all_items, gtk_action_group_list_actions(vtb->group_toggles) );
1150 all_items = g_list_concat ( all_items, gtk_action_group_list_actions(vtb->group_tools) );
1151 all_items = g_list_concat ( all_items, gtk_action_group_list_actions(vtb->group_modes) );
1153 /* create the GUI */
1154 tbw = tb_editor_create_dialog(vtb, parent, toolbar, vbox, hbox, reload_cb, user_data);
1156 /* fill the stores */
1157 gtk_list_store_insert_with_values(tbw->store_available, NULL, -1,
1158 TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR,
1159 TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL,
1161 foreach_list(l, all_items)
1163 name = gtk_action_get_name(l->data);
1164 if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL)
1166 gtk_list_store_append(tbw->store_available, &iter);
1167 tb_editor_set_item_values(vtb, name, tbw->store_available, &iter);
1170 foreach_slist(sl, used_items)
1172 gtk_list_store_append(tbw->store_used, &iter);
1173 tb_editor_set_item_values(vtb, sl->data, tbw->store_used, &iter);
1175 /* select first item */
1176 path = gtk_tree_path_new_from_string("0");
1177 gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path);
1178 gtk_tree_path_free(path);
1180 /* connect the changed signals after populating the store */
1181 g_signal_connect(tbw->store_used, "row-changed",
1182 G_CALLBACK(tb_editor_available_items_changed_cb), tbw);
1183 g_signal_connect(tbw->store_used, "row-deleted",
1184 G_CALLBACK(tb_editor_available_items_deleted_cb), tbw);
1187 gtk_dialog_run(GTK_DIALOG(tbw->dialog));
1189 gtk_widget_destroy(tbw->dialog);
1191 g_slist_foreach(used_items, (GFunc) g_free, NULL);
1192 g_slist_free(used_items);
1193 g_list_free(all_items);
1194 tb_editor_free_path(tbw);