/* * toolbar.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2009-2012 Enrico Tröger * Copyright 2009-2012 Nick Treleaven * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * @file toolbar.h * Toolbar (prefs). */ /* Utility functions to create the toolbar */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "toolbar.h" #include "app.h" #include "build.h" #include "callbacks.h" #include "document.h" #include "geanyentryaction.h" #include "geanymenubuttonaction.h" #include "main.h" #include "support.h" #include "ui_utils.h" #include "utils.h" #include #include GeanyToolbarPrefs toolbar_prefs; static GtkUIManager *uim; static GtkActionGroup *group; static GSList *plugin_items = NULL; /* Available toolbar actions * Fields: name, stock_id, label, accelerator, tooltip, callback */ static const GtkActionEntry ui_entries[] = { /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */ { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Save the current file"), G_CALLBACK(on_save1_activate) }, { "SaveAs", GTK_STOCK_SAVE_AS, NULL, NULL, N_("Save as"), G_CALLBACK(on_save_as1_activate) }, { "SaveAll", GEANY_STOCK_SAVE_ALL, NULL, NULL, N_("Save all open files"), G_CALLBACK(on_save_all1_activate) }, { "Reload", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked) }, { "Close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current file"), G_CALLBACK(on_close1_activate) }, { "CloseAll", GEANY_STOCK_CLOSE_ALL, NULL, NULL, N_("Close all open files"), G_CALLBACK(on_close_all1_activate) }, { "Cut", GTK_STOCK_CUT, NULL, NULL, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate) }, { "Copy", GTK_STOCK_COPY, NULL, NULL, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate) }, { "Paste", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate) }, { "Delete", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate) }, { "Undo", GTK_STOCK_UNDO, NULL, NULL, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate) }, { "Redo", GTK_STOCK_REDO, NULL, NULL, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate) }, { "NavBack", GTK_STOCK_GO_BACK, NULL, NULL, N_("Navigate back a location"), G_CALLBACK(on_toolbutton_back_activate) }, { "NavFor", GTK_STOCK_GO_FORWARD, NULL, NULL, N_("Navigate forward a location"), G_CALLBACK(on_toolbutton_forward_activate) }, { "Compile", GTK_STOCK_CONVERT, N_("Compile"), NULL, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked) }, { "Run", GTK_STOCK_EXECUTE, NULL, NULL, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked) }, { "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) }, { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, NULL, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate) }, { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, NULL, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate) }, { "UnIndent", GTK_STOCK_UNINDENT, NULL, NULL, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate) }, { "Indent", GTK_STOCK_INDENT, NULL, NULL, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate) }, { "Search", GTK_STOCK_FIND, NULL, NULL, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked) }, { "Goto", GTK_STOCK_JUMP_TO, NULL, NULL, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked) }, { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, N_("Show the preferences dialog"), G_CALLBACK(on_preferences1_activate) }, { "Quit", GTK_STOCK_QUIT, NULL, NULL, N_("Quit Geany"), G_CALLBACK(on_quit1_activate) }, { "Print", GTK_STOCK_PRINT, NULL, NULL, N_("Print document"), G_CALLBACK(on_print1_activate) }, { "Replace", GTK_STOCK_FIND_AND_REPLACE, NULL, NULL, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate) } }; static const guint ui_entries_n = G_N_ELEMENTS(ui_entries); /* fallback UI definition */ static const gchar *toolbar_markup = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either * update the widget pointer in this case (i.e. request it again) or better use * toolbar_get_action_by_name() instead. The action objects will remain the same even when the * toolbar is reloaded. */ GtkWidget *toolbar_get_widget_by_name(const gchar *name) { GtkWidget *widget; gchar *path; g_return_val_if_fail(name != NULL, NULL); path = g_strconcat("/ui/GeanyToolbar/", name, NULL); widget = gtk_ui_manager_get_widget(uim, path); g_free(path); return widget; } /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See * toolbar_get_widget_by_name for details(). */ GtkWidget *toolbar_get_widget_child_by_name(const gchar *name) { GtkWidget *widget = toolbar_get_widget_by_name(name); if (G_LIKELY(widget != NULL)) return gtk_bin_get_child(GTK_BIN(widget)); else return NULL; } GtkAction *toolbar_get_action_by_name(const gchar *name) { g_return_val_if_fail(name != NULL, NULL); return gtk_action_group_get_action(group, name); } static void toolbar_item_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data) { plugin_items = g_slist_remove(plugin_items, widget); } void toolbar_item_ref(GtkToolItem *item) { g_return_if_fail(item != NULL); plugin_items = g_slist_append(plugin_items, item); g_signal_connect(item, "destroy", G_CALLBACK(toolbar_item_destroy_cb), NULL); } static GtkWidget *toolbar_reload(const gchar *markup) { gint i; GSList *l; GtkWidget *entry; GError *error = NULL; gchar *filename; static guint merge_id = 0; GtkWidget *toolbar_new_file_menu = NULL; GtkWidget *toolbar_recent_files_menu = NULL; GtkWidget *toolbar_build_menu = NULL; /* Cleanup old toolbar */ if (merge_id > 0) { /* ref plugins toolbar items to keep them after we destroyed the toolbar */ foreach_slist(l, plugin_items) { g_object_ref(l->data); gtk_container_remove(GTK_CONTAINER(main_widgets.toolbar), GTK_WIDGET(l->data)); } /* ref and hold the submenus of the New, Open and Build toolbar items */ toolbar_new_file_menu = geany_menu_button_action_get_menu( GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "New"))); g_object_ref(toolbar_new_file_menu); toolbar_recent_files_menu = geany_menu_button_action_get_menu( GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Open"))); g_object_ref(toolbar_recent_files_menu); toolbar_build_menu = geany_menu_button_action_get_menu( GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Build"))); g_object_ref(toolbar_build_menu); /* Get rid of it! */ gtk_widget_destroy(main_widgets.toolbar); gtk_ui_manager_remove_ui(uim, merge_id); gtk_ui_manager_ensure_update(uim); } if (markup != NULL) { merge_id = gtk_ui_manager_add_ui_from_string(uim, markup, -1, &error); } else { /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */ filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL); merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error); if (merge_id == 0) { if (! g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) geany_debug("Loading user toolbar UI definition failed (%s).", error->message); g_error_free(error); error = NULL; SETPTR(filename, g_build_filename(app->datadir, "ui_toolbar.xml", NULL)); merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error); } g_free(filename); } if (error != NULL) { geany_debug("UI creation failed, using internal fallback definition. Error message: %s", error->message); g_error_free(error); /* finally load the internally defined markup as fallback */ merge_id = gtk_ui_manager_add_ui_from_string(uim, toolbar_markup, -1, NULL); } main_widgets.toolbar = gtk_ui_manager_get_widget(uim, "/ui/GeanyToolbar"); ui_init_toolbar_widgets(); /* add the toolbar again to the main window */ if (toolbar_prefs.append_to_menu) { GtkWidget *hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar"); gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0); gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1); } else { GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1"); gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0); gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1); } gtk_widget_show(main_widgets.toolbar); /* re-add und unref the plugin toolbar items */ i = toolbar_get_insert_position(); foreach_slist(l, plugin_items) { gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), l->data, i); g_object_unref(l->data); i++; } /* re-add und unref the submenus of menu toolbar items */ if (toolbar_new_file_menu != NULL) { geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION( gtk_action_group_get_action(group, "New")), toolbar_new_file_menu); g_object_unref(toolbar_new_file_menu); } if (toolbar_recent_files_menu != NULL) { geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION( gtk_action_group_get_action(group, "Open")), toolbar_recent_files_menu); g_object_unref(toolbar_recent_files_menu); } if (toolbar_build_menu != NULL) { geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION( gtk_action_group_get_action(group, "Build")), toolbar_build_menu); g_object_unref(toolbar_build_menu); } /* update button states */ if (main_status.main_window_realized) { GeanyDocument *doc = document_get_current(); gboolean doc_changed = (doc != NULL) ? doc->changed : FALSE; ui_document_buttons_update(); ui_save_buttons_toggle(doc_changed); /* update save all */ ui_update_popup_reundo_items(doc); toolbar_apply_settings(); } /* Signals */ g_signal_connect(main_widgets.toolbar, "button-press-event", G_CALLBACK(toolbar_popup_menu), NULL); g_signal_connect(main_widgets.toolbar, "key-press-event", G_CALLBACK(on_escape_key_press_event), NULL); /* We don't need to disconnect those signals as this is done automatically when the entry * widgets are destroyed, happens when the toolbar itself is destroyed. */ entry = toolbar_get_widget_child_by_name("SearchEntry"); if (entry != NULL) g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL); entry = toolbar_get_widget_child_by_name("GotoEntry"); if (entry != NULL) g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL); return main_widgets.toolbar; } static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data) { const gchar *arg_name = g_param_spec_get_name(arg1); gint value; if (toolbar_prefs.use_gtk_default_style && utils_str_equal(arg_name, "gtk-toolbar-style")) { value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_style); gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), value); } else if (toolbar_prefs.use_gtk_default_icon && utils_str_equal(arg_name, "gtk-toolbar-size")) { value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_size); gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), value); } } GtkWidget *toolbar_init(void) { GtkWidget *toolbar; GtkAction *action_new; GtkAction *action_open; GtkAction *action_build; GtkAction *action_searchentry; GtkAction *action_gotoentry; GtkSettings *gtk_settings; uim = gtk_ui_manager_new(); group = gtk_action_group_new("GeanyToolbar"); gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE); gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL); /* Create our custom actions */ action_new = geany_menu_button_action_new( "New", NULL, _("Create a new file"), _("Create a new file from a template"), GTK_STOCK_NEW); g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_new1_activate), NULL); gtk_action_group_add_action(group, action_new); action_open = geany_menu_button_action_new( "Open", NULL, _("Open an existing file"), _("Open a recent file"), GTK_STOCK_OPEN); g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_open1_activate), NULL); gtk_action_group_add_action(group, action_open); action_build = geany_menu_button_action_new( "Build", NULL, _("Build the current file"), _("Choose more build actions"), GEANY_STOCK_BUILD); g_signal_connect(action_build, "button-clicked", G_CALLBACK(build_toolbutton_build_clicked), NULL); gtk_action_group_add_action(group, action_build); action_searchentry = geany_entry_action_new( "SearchEntry", _("Search Field"), _("Find the entered text in the current file"), FALSE); g_signal_connect(action_searchentry, "entry-activate", G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(FALSE)); g_signal_connect(action_searchentry, "entry-activate-backward", G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(TRUE)); g_signal_connect(action_searchentry, "entry-changed", G_CALLBACK(on_toolbar_search_entry_changed), NULL); gtk_action_group_add_action(group, action_searchentry); action_gotoentry = geany_entry_action_new( "GotoEntry", _("Goto Field"), _("Jump to the entered line number"), TRUE); g_signal_connect(action_gotoentry, "entry-activate", G_CALLBACK(on_toolbutton_goto_entry_activate), NULL); gtk_action_group_add_action(group, action_gotoentry); gtk_ui_manager_insert_action_group(uim, group, 0); toolbar = toolbar_reload(NULL); #if GTK_CHECK_VERSION(3, 0, 0) gtk_style_context_add_class(gtk_widget_get_style_context(toolbar), "primary-toolbar"); #endif gtk_settings = gtk_widget_get_settings(GTK_WIDGET(toolbar)); if (gtk_settings != NULL) { g_signal_connect(gtk_settings, "notify::gtk-toolbar-style", G_CALLBACK(toolbar_notify_style_cb), NULL); } return toolbar; } void toolbar_update_ui(void) { static GtkWidget *hbox_menubar = NULL; static GtkWidget *menubar = NULL; GtkWidget *parent; GtkToolItem *first_item; if (menubar == NULL) { /* cache widget pointers */ hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar"); menubar = ui_lookup_widget(main_widgets.window, "menubar1"); } /* the separator between the menubar and the toolbar */ first_item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets.toolbar), 0); if (first_item != NULL && GTK_IS_SEPARATOR_TOOL_ITEM(first_item)) { gtk_widget_destroy(GTK_WIDGET(first_item)); } parent = gtk_widget_get_parent(main_widgets.toolbar); if (toolbar_prefs.append_to_menu) { GtkWidget *menubar_toolbar_separator; if (parent != NULL) { if (parent != hbox_menubar) { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't * like to do it */ g_object_ref(main_widgets.toolbar); gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar); gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0); gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1); g_object_unref(main_widgets.toolbar); } } else gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0); /* the separator between the menubar and the toolbar */ menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new()); gtk_widget_show(menubar_toolbar_separator); gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(menubar_toolbar_separator), 0); } else { GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1"); if (parent != NULL) { if (parent != box) { g_object_ref(main_widgets.toolbar); gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar); gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0); gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1); g_object_unref(main_widgets.toolbar); } } else { gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0); gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1); } } /* we need to adjust the packing flags for the menubar to expand it if it is alone in the * hbox and not expand it if the toolbar is appended */ gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar, ! (toolbar_prefs.visible && toolbar_prefs.append_to_menu), TRUE, 0, GTK_PACK_START); } /* Returns the position for adding new toolbar items. The returned position can be used * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed * with @a geany->main_widgets->toolbar. * The position is always the last one before the Quit button or the very last position if the * Quit button is not the last toolbar item. * * @return The position for new toolbar items. */ gint toolbar_get_insert_position(void) { GtkWidget *quit = toolbar_get_widget_by_name("Quit"); gint quit_pos = -1, pos; if (quit != NULL) quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit)); pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar)); if (quit_pos == (pos - 1)) { /* if the toolbar item before the quit button is a separator, insert new items before */ if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item( GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1))) { return quit_pos - 1; } /* else return the position of the quit button to insert new items before */ return quit_pos; } return pos; } void toolbar_finalize(void) { GeanyMenubuttonAction *open_action = GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open")); g_object_unref(geany_menu_button_action_get_menu(open_action)); geany_menu_button_action_set_menu(open_action, NULL); /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */ g_object_unref(uim); g_object_unref(group); g_slist_free(plugin_items); } void toolbar_show_hide(void) { ignore_callback = TRUE; gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM( ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible); ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible); ignore_callback = FALSE; } /* sets the icon style of the toolbar */ static void toolbar_set_icon_style(void) { gint icon_style; icon_style = toolbar_prefs.icon_style; if (toolbar_prefs.use_gtk_default_style) icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs.icon_style); gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), icon_style); } /* sets the icon size of the toolbar */ static void toolbar_set_icon_size(void) { gint icon_size; icon_size = toolbar_prefs.icon_size; if (toolbar_prefs.use_gtk_default_icon) icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs.icon_size); gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), icon_size); } void toolbar_apply_settings(void) { toolbar_set_icon_style(); toolbar_set_icon_size(); } #define TB_EDITOR_SEPARATOR _("Separator") #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---") typedef struct { GtkWidget *dialog; GtkTreeView *tree_available; GtkTreeView *tree_used; GtkListStore *store_available; GtkListStore *store_used; GtkTreePath *last_drag_path; GtkTreeViewDropPosition last_drag_pos; GtkWidget *drag_source; } TBEditorWidget; static const GtkTargetEntry tb_editor_dnd_targets[] = { { "GEANY_TB_EDITOR_ROW", 0, 0 } }; static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets); enum { TB_EDITOR_COL_ACTION, TB_EDITOR_COL_LABEL, TB_EDITOR_COL_ICON, TB_EDITOR_COLS_MAX }; static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer data, GError **error) { gint i; GSList **actions = data; /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */ if (utils_str_equal(element_name, "separator")) *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR)); for (i = 0; attribute_names[i] != NULL; i++) { if (utils_str_equal(attribute_names[i], "action")) { *actions = g_slist_append(*actions, g_strdup(attribute_values[i])); } } } static const GMarkupParser tb_editor_xml_parser = { tb_editor_handler_start_element, NULL, NULL, NULL, NULL }; static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error) { GMarkupParseContext *context; GSList *list = NULL; context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL); g_markup_parse_context_parse(context, buffer, length, error); g_markup_parse_context_free(context); return list; } static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter) { gchar *icon = NULL; gchar *label = NULL; gchar *label_clean = NULL; GtkAction *action; action = gtk_action_group_get_action(group, name); if (action == NULL) { if (utils_str_equal(name, TB_EDITOR_SEPARATOR)) label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL); else return; } else { g_object_get(action, "icon-name", &icon, NULL); if (icon == NULL) g_object_get(action, "stock-id", &icon, NULL); g_object_get(action, "label", &label, NULL); if (label != NULL) label_clean = utils_str_remove_chars(g_strdup(label), "_"); } gtk_list_store_set(store, iter, TB_EDITOR_COL_ACTION, name, TB_EDITOR_COL_LABEL, label_clean, TB_EDITOR_COL_ICON, icon, -1); g_free(icon); g_free(label); g_free(label_clean); } static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter) { GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter); gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0); gtk_tree_path_free(path); } static void tb_editor_free_path(TBEditorWidget *tbw) { if (tbw->last_drag_path != NULL) { gtk_tree_path_free(tbw->last_drag_path); tbw->last_drag_path = NULL; } } static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw) { GtkTreeModel *model_used; GtkTreeSelection *selection_used; GtkTreeIter iter_used, iter_new; gchar *action_name; selection_used = gtk_tree_view_get_selection(tbw->tree_used); if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used)) { gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1); if (gtk_list_store_remove(tbw->store_used, &iter_used)) gtk_tree_selection_select_iter(selection_used, &iter_used); if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) { gtk_list_store_append(tbw->store_available, &iter_new); tb_editor_set_item_values(action_name, tbw->store_available, &iter_new); tb_editor_scroll_to_iter(tbw->tree_available, &iter_new); } g_free(action_name); } } static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw) { GtkTreeModel *model_available; GtkTreeSelection *selection_available, *selection_used; GtkTreeIter iter_available, iter_new, iter_selected; gchar *action_name; selection_available = gtk_tree_view_get_selection(tbw->tree_available); if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available)) { gtk_tree_model_get(model_available, &iter_available, TB_EDITOR_COL_ACTION, &action_name, -1); if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) { if (gtk_list_store_remove(tbw->store_available, &iter_available)) gtk_tree_selection_select_iter(selection_available, &iter_available); } selection_used = gtk_tree_view_get_selection(tbw->tree_used); if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected)) gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected); else gtk_list_store_append(tbw->store_used, &iter_new); tb_editor_set_item_values(action_name, tbw->store_used, &iter_new); tb_editor_scroll_to_iter(tbw->tree_used, &iter_new); g_free(action_name); } } static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint ltime, TBEditorWidget *tbw) { if (tbw->last_drag_path != NULL) gtk_tree_path_free(tbw->last_drag_path); gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget), &(tbw->last_drag_path), &(tbw->last_drag_pos)); return FALSE; } static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint ltime, TBEditorWidget *tbw) { GtkTreeIter iter; GtkTreeSelection *selection; GtkTreeModel *model; GdkAtom atom; gchar *name; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); if (! gtk_tree_selection_get_selected(selection, &model, &iter)) return; gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1); if (G_UNLIKELY(EMPTY(name))) { g_free(name); return; } atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE); gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name)); g_free(name); tbw->drag_source = widget; } static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint ltime, TBEditorWidget *tbw) { GtkTreeView *tree = GTK_TREE_VIEW(widget); gboolean del = FALSE; if (gtk_selection_data_get_length(data) >= 0 && gtk_selection_data_get_format(data) == 8) { gboolean is_sep; gchar *text = NULL; text = (gchar*) gtk_selection_data_get_data(data); is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR); /* If the source of the action is equal to the target, we do just re-order and so need * to delete the separator to get it moved, not just copied. */ if (is_sep && widget == tbw->drag_source) is_sep = FALSE; if (tree != tbw->tree_available || ! is_sep) { GtkTreeIter iter, iter_before, *iter_before_ptr; GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree)); if (tbw->last_drag_path != NULL) { gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path); if (gtk_list_store_iter_is_valid(store, &iter_before)) iter_before_ptr = &iter_before; else iter_before_ptr = NULL; if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE || tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) gtk_list_store_insert_before(store, &iter, iter_before_ptr); else gtk_list_store_insert_after(store, &iter, iter_before_ptr); } else gtk_list_store_append(store, &iter); tb_editor_set_item_values(text, store, &iter); tb_editor_scroll_to_iter(tree, &iter); } if (tree != tbw->tree_used || ! is_sep) del = TRUE; } tbw->drag_source = NULL; /* reset the value just to be sure */ tb_editor_free_path(tbw); gtk_drag_finish(context, TRUE, del, ltime); } static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { gchar *action_name; gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1); if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) g_string_append_printf(data, "\t\t\n"); else if (G_LIKELY(!EMPTY(action_name))) g_string_append_printf(data, "\t\t\n", action_name); g_free(action_name); return FALSE; } static void tb_editor_write_markup(TBEditorWidget *tbw) { /* must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */ const gchar *template = "\n\n\ \t\n"; gchar *filename; GString *str = g_string_new(template); gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str); g_string_append(str, "\n\t\n\n"); toolbar_reload(str->str); filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL); utils_write_file(filename, str->str); g_free(filename); g_string_free(str, TRUE); } static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1, GtkTreeIter *arg2, TBEditorWidget *tbw) { tb_editor_write_markup(tbw); } static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1, TBEditorWidget *tbw) { tb_editor_write_markup(tbw); } static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent) { GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove; GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label; GtkCellRenderer *text_renderer, *icon_renderer; GtkTreeViewColumn *column; TBEditorWidget *tbw = g_new(TBEditorWidget, 1); if (parent == NULL) parent = GTK_WINDOW(main_widgets.window); dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"), parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); gtk_box_set_spacing(GTK_BOX(vbox), 6); gtk_widget_set_name(dialog, "GeanyDialog"); gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE); tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); label = gtk_label_new( _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop.")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); tree_available = gtk_tree_view_new(); gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available)); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE); gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING); icon_renderer = gtk_cell_renderer_pixbuf_new(); column = gtk_tree_view_column_new_with_attributes( NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column); text_renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column); swin_available = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(swin_available), tree_available); tree_used = gtk_tree_view_new(); gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used)); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE); gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE); icon_renderer = gtk_cell_renderer_pixbuf_new(); column = gtk_tree_view_column_new_with_attributes( NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column); text_renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column); swin_used = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(swin_used), tree_used); /* drag'n'drop */ gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK, tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available), tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); g_signal_connect(tree_available, "drag-data-get", G_CALLBACK(tb_editor_drag_data_get_cb), tbw); g_signal_connect(tree_available, "drag-data-received", G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw); g_signal_connect(tree_available, "drag-motion", G_CALLBACK(tb_editor_drag_motion_cb), tbw); gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK, tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used), tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); g_signal_connect(tree_used, "drag-data-get", G_CALLBACK(tb_editor_drag_data_get_cb), tbw); g_signal_connect(tree_used, "drag-data-received", G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw); g_signal_connect(tree_used, "drag-motion", G_CALLBACK(tb_editor_drag_motion_cb), tbw); button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL); button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL); g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw); g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw); vbox_buttons = gtk_vbox_new(FALSE, 6); /* FIXME this is a little hack'ish, any better ideas? */ gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0); hbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); gtk_widget_show_all(vbox); g_object_unref(tbw->store_available); g_object_unref(tbw->store_used); tbw->dialog = dialog; tbw->tree_available = GTK_TREE_VIEW(tree_available); tbw->tree_used = GTK_TREE_VIEW(tree_used); tbw->last_drag_path = NULL; return tbw; } void toolbar_configure(GtkWindow *parent) { gchar *markup; GSList *sl, *used_items; GList *l, *all_items; GtkTreePath *path; TBEditorWidget *tbw; /* read the current active toolbar items */ markup = gtk_ui_manager_get_ui(uim); used_items = tb_editor_parse_ui(markup, -1, NULL); g_free(markup); /* get all available actions */ all_items = gtk_action_group_list_actions(group); /* create the GUI */ tbw = tb_editor_create_dialog(parent); /* fill the stores */ gtk_list_store_insert_with_values(tbw->store_available, NULL, -1, TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR, TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL, -1); foreach_list(l, all_items) { const gchar *name = gtk_action_get_name(l->data); if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL) { GtkTreeIter iter; gtk_list_store_append(tbw->store_available, &iter); tb_editor_set_item_values(name, tbw->store_available, &iter); } } foreach_slist(sl, used_items) { GtkTreeIter iter; gtk_list_store_append(tbw->store_used, &iter); tb_editor_set_item_values(sl->data, tbw->store_used, &iter); } /* select first item */ path = gtk_tree_path_new_from_string("0"); gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path); gtk_tree_path_free(path); /* connect the changed signals after populating the store */ g_signal_connect(tbw->store_used, "row-changed", G_CALLBACK(tb_editor_available_items_changed_cb), tbw); g_signal_connect(tbw->store_used, "row-deleted", G_CALLBACK(tb_editor_available_items_deleted_cb), tbw); /* run it */ gtk_dialog_run(GTK_DIALOG(tbw->dialog)); gtk_widget_destroy(tbw->dialog); g_slist_foreach(used_items, (GFunc) g_free, NULL); g_slist_free(used_items); g_list_free(all_items); tb_editor_free_path(tbw); g_free(tbw); }