]>
Commit | Line | Data |
---|---|---|
50b4f4de RN |
1 | /* |
2 | * toolbar.c - this file is part of Geany, a fast and lightweight IDE | |
3 | * | |
4 | * Copyright 2009-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de> | |
5 | * Copyright 2009-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com> | |
6 | * | |
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. | |
11 | * | |
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. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along | |
18 | * with this program; if not, write to the Free Software Foundation, Inc., | |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
20 | */ | |
21 | ||
22 | /** | |
23 | * @file toolbar.h | |
24 | * Toolbar (prefs). | |
25 | */ | |
26 | /* Utility functions to create the toolbar */ | |
27 | ||
28 | #include "geany.h" | |
29 | #include "support.h" | |
30 | #include "ui_utils.h" | |
31 | #include "toolbar.h" | |
32 | #include "callbacks.h" | |
33 | #include "utils.h" | |
34 | #include "dialogs.h" | |
35 | #include "document.h" | |
36 | #include "build.h" | |
37 | #include "main.h" | |
38 | #include "geanymenubuttonaction.h" | |
39 | #include "geanyentryaction.h" | |
40 | ||
41 | #include <string.h> | |
42 | #include <glib/gstdio.h> | |
43 | ||
44 | ||
45 | GeanyToolbarPrefs toolbar_prefs; | |
46 | static GtkUIManager *uim; | |
47 | static GtkActionGroup *group; | |
48 | static GSList *plugin_items = NULL; | |
49 | ||
50 | /* Available toolbar actions | |
51 | * Fields: name, stock_id, label, accelerator, tooltip, callback */ | |
52 | static const GtkActionEntry ui_entries[] = { | |
53 | /* custom actions defined in toolbar_init(): "New", "Open", "SearchEntry", "GotoEntry", "Build" */ | |
54 | { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Save the current file"), G_CALLBACK(on_toolbutton_save_clicked) }, | |
55 | { "SaveAs", GTK_STOCK_SAVE_AS, NULL, NULL, N_("Save as"), G_CALLBACK(on_save_as1_activate) }, | |
56 | { "SaveAll", GEANY_STOCK_SAVE_ALL, NULL, NULL, N_("Save all open files"), G_CALLBACK(on_save_all1_activate) }, | |
57 | { "Reload", GTK_STOCK_REVERT_TO_SAVED, NULL, NULL, N_("Reload the current file from disk"), G_CALLBACK(on_toolbutton_reload_clicked) }, | |
58 | { "Close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current file"), G_CALLBACK(on_toolbutton_close_clicked) }, | |
59 | { "CloseAll", GEANY_STOCK_CLOSE_ALL, NULL, NULL, N_("Close all open files"), G_CALLBACK(on_toolbutton_close_all_clicked) }, | |
60 | { "Cut", GTK_STOCK_CUT, NULL, NULL, N_("Cut the current selection"), G_CALLBACK(on_cut1_activate) }, | |
61 | { "Copy", GTK_STOCK_COPY, NULL, NULL, N_("Copy the current selection"), G_CALLBACK(on_copy1_activate) }, | |
62 | { "Paste", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the contents of the clipboard"), G_CALLBACK(on_paste1_activate) }, | |
63 | { "Delete", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the current selection"), G_CALLBACK(on_delete1_activate) }, | |
64 | { "Undo", GTK_STOCK_UNDO, NULL, NULL, N_("Undo the last modification"), G_CALLBACK(on_undo1_activate) }, | |
65 | { "Redo", GTK_STOCK_REDO, NULL, NULL, N_("Redo the last modification"), G_CALLBACK(on_redo1_activate) }, | |
66 | { "NavBack", GTK_STOCK_GO_BACK, NULL, NULL, N_("Navigate back a location"), G_CALLBACK(on_back_activate) }, | |
67 | { "NavFor", GTK_STOCK_GO_FORWARD, NULL, NULL, N_("Navigate forward a location"), G_CALLBACK(on_forward_activate) }, | |
68 | { "Compile", GTK_STOCK_CONVERT, N_("Compile"), NULL, N_("Compile the current file"), G_CALLBACK(on_toolbutton_compile_clicked) }, | |
69 | { "Run", GTK_STOCK_EXECUTE, NULL, NULL, N_("Run or view the current file"), G_CALLBACK(on_toolbutton_run_clicked) }, | |
70 | { "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) }, | |
71 | { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, NULL, N_("Zoom in the text"), G_CALLBACK(on_zoom_in1_activate) }, | |
72 | { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, NULL, N_("Zoom out the text"), G_CALLBACK(on_zoom_out1_activate) }, | |
73 | { "UnIndent", GTK_STOCK_UNINDENT, NULL, NULL, N_("Decrease indentation"), G_CALLBACK(on_menu_decrease_indent1_activate) }, | |
74 | { "Indent", GTK_STOCK_INDENT, NULL, NULL, N_("Increase indentation"), G_CALLBACK(on_menu_increase_indent1_activate) }, | |
75 | { "Search", GTK_STOCK_FIND, NULL, NULL, N_("Find the entered text in the current file"), G_CALLBACK(on_toolbutton_search_clicked) }, | |
76 | { "Goto", GTK_STOCK_JUMP_TO, NULL, NULL, N_("Jump to the entered line number"), G_CALLBACK(on_toolbutton_goto_clicked) }, | |
77 | { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, N_("Show the preferences dialog"), G_CALLBACK(on_toolbutton_preferences_clicked) }, | |
78 | { "Quit", GTK_STOCK_QUIT, NULL, NULL, N_("Quit Geany"), G_CALLBACK(on_toolbutton_quit_clicked) }, | |
79 | { "Print", GTK_STOCK_PRINT, NULL, NULL, N_("Print document"), G_CALLBACK(on_print1_activate) }, | |
80 | { "Replace", GTK_STOCK_FIND_AND_REPLACE, NULL, NULL, N_("Replace text in the current document"), G_CALLBACK(on_replace1_activate) } | |
81 | }; | |
82 | static const guint ui_entries_n = G_N_ELEMENTS(ui_entries); | |
83 | ||
84 | ||
85 | /* fallback UI definition */ | |
86 | static const gchar *toolbar_markup = | |
87 | "<ui>" | |
88 | "<toolbar name='GeanyToolbar'>" | |
89 | "<toolitem action='New'/>" | |
90 | "<toolitem action='Open'/>" | |
91 | "<toolitem action='Save'/>" | |
92 | "<toolitem action='SaveAll'/>" | |
93 | "<separator/>" | |
94 | "<toolitem action='Reload'/>" | |
95 | "<toolitem action='Close'/>" | |
96 | "<separator/>" | |
97 | "<toolitem action='NavBack'/>" | |
98 | "<toolitem action='NavFor'/>" | |
99 | "<separator/>" | |
100 | "<toolitem action='Compile'/>" | |
101 | "<toolitem action='Build'/>" | |
102 | "<toolitem action='Run'/>" | |
103 | "<separator/>" | |
104 | "<toolitem action='Color'/>" | |
105 | "<separator/>" | |
106 | "<toolitem action='SearchEntry'/>" | |
107 | "<toolitem action='Search'/>" | |
108 | "<separator/>" | |
109 | "<toolitem action='GotoEntry'/>" | |
110 | "<toolitem action='Goto'/>" | |
111 | "<separator/>" | |
112 | "<toolitem action='Quit'/>" | |
113 | "</toolbar>" | |
114 | "</ui>"; | |
115 | ||
116 | ||
117 | /* Note: The returned widget pointer is only valid until the toolbar is reloaded. So, either | |
118 | * update the widget pointer in this case (i.e. request it again) or better use | |
119 | * toolbar_get_action_by_name() instead. The action objects will remain the same even when the | |
120 | * toolbar is reloaded. */ | |
121 | GtkWidget *toolbar_get_widget_by_name(const gchar *name) | |
122 | { | |
123 | GtkWidget *widget; | |
124 | gchar *path; | |
125 | ||
126 | g_return_val_if_fail(name != NULL, NULL); | |
127 | ||
128 | path = g_strconcat("/ui/GeanyToolbar/", name, NULL); | |
129 | widget = gtk_ui_manager_get_widget(uim, path); | |
130 | ||
131 | g_free(path); | |
132 | return widget; | |
133 | } | |
134 | ||
135 | ||
136 | /* Note: The returned widget pointer is only valid until the toolbar is reloaded. See | |
137 | * toolbar_get_widget_by_name for details(). */ | |
138 | GtkWidget *toolbar_get_widget_child_by_name(const gchar *name) | |
139 | { | |
140 | GtkWidget *widget = toolbar_get_widget_by_name(name); | |
141 | ||
142 | if (G_LIKELY(widget != NULL)) | |
143 | return gtk_bin_get_child(GTK_BIN(widget)); | |
144 | else | |
145 | return NULL; | |
146 | } | |
147 | ||
148 | ||
149 | GtkAction *toolbar_get_action_by_name(const gchar *name) | |
150 | { | |
151 | g_return_val_if_fail(name != NULL, NULL); | |
152 | ||
153 | return gtk_action_group_get_action(group, name); | |
154 | } | |
155 | ||
156 | ||
157 | static void toolbar_item_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data) | |
158 | { | |
159 | plugin_items = g_slist_remove(plugin_items, widget); | |
160 | } | |
161 | ||
162 | ||
163 | void toolbar_item_ref(GtkToolItem *item) | |
164 | { | |
165 | g_return_if_fail(item != NULL); | |
166 | ||
167 | plugin_items = g_slist_append(plugin_items, item); | |
168 | g_signal_connect(item, "destroy", G_CALLBACK(toolbar_item_destroy_cb), NULL); | |
169 | } | |
170 | ||
171 | ||
172 | static GtkWidget *toolbar_reload(const gchar *markup) | |
173 | { | |
174 | gint i; | |
175 | GSList *l; | |
176 | GtkWidget *entry; | |
177 | GError *error = NULL; | |
178 | gchar *filename; | |
179 | static guint merge_id = 0; | |
180 | GtkWidget *toolbar_new_file_menu = NULL; | |
181 | GtkWidget *toolbar_recent_files_menu = NULL; | |
182 | GtkWidget *toolbar_build_menu = NULL; | |
183 | ||
184 | /* Cleanup old toolbar */ | |
185 | if (merge_id > 0) | |
186 | { | |
187 | /* ref plugins toolbar items to keep them after we destroyed the toolbar */ | |
188 | foreach_slist(l, plugin_items) | |
189 | { | |
190 | g_object_ref(l->data); | |
191 | gtk_container_remove(GTK_CONTAINER(main_widgets.toolbar), GTK_WIDGET(l->data)); | |
192 | } | |
193 | /* ref and hold the submenus of the New, Open and Build toolbar items */ | |
194 | toolbar_new_file_menu = geany_menu_button_action_get_menu( | |
195 | GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "New"))); | |
196 | g_object_ref(toolbar_new_file_menu); | |
197 | toolbar_recent_files_menu = geany_menu_button_action_get_menu( | |
198 | GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Open"))); | |
199 | g_object_ref(toolbar_recent_files_menu); | |
200 | toolbar_build_menu = geany_menu_button_action_get_menu( | |
201 | GEANY_MENU_BUTTON_ACTION(gtk_action_group_get_action(group, "Build"))); | |
202 | g_object_ref(toolbar_build_menu); | |
203 | ||
204 | /* Get rid of it! */ | |
205 | gtk_widget_destroy(main_widgets.toolbar); | |
206 | ||
207 | gtk_ui_manager_remove_ui(uim, merge_id); | |
208 | gtk_ui_manager_ensure_update(uim); | |
209 | } | |
210 | ||
211 | if (markup != NULL) | |
212 | { | |
213 | merge_id = gtk_ui_manager_add_ui_from_string(uim, markup, -1, &error); | |
214 | } | |
215 | else | |
216 | { | |
217 | /* Load the toolbar UI XML file from disk (first from config dir, then try data dir) */ | |
218 | filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL); | |
219 | merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error); | |
220 | if (merge_id == 0) | |
221 | { | |
222 | if (! g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) | |
223 | geany_debug("Loading user toolbar UI definition failed (%s).", error->message); | |
224 | g_error_free(error); | |
225 | error = NULL; | |
226 | ||
227 | SETPTR(filename, g_build_filename(app->datadir, "ui_toolbar.xml", NULL)); | |
228 | merge_id = gtk_ui_manager_add_ui_from_file(uim, filename, &error); | |
229 | } | |
230 | g_free(filename); | |
231 | } | |
232 | if (error != NULL) | |
233 | { | |
234 | geany_debug("UI creation failed, using internal fallback definition. Error message: %s", | |
235 | error->message); | |
236 | g_error_free(error); | |
237 | /* finally load the internally defined markup as fallback */ | |
238 | merge_id = gtk_ui_manager_add_ui_from_string(uim, toolbar_markup, -1, NULL); | |
239 | } | |
240 | main_widgets.toolbar = gtk_ui_manager_get_widget(uim, "/ui/GeanyToolbar"); | |
241 | ui_init_toolbar_widgets(); | |
242 | ||
243 | /* add the toolbar again to the main window */ | |
244 | if (toolbar_prefs.append_to_menu) | |
245 | { | |
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); | |
249 | } | |
250 | else | |
251 | { | |
252 | GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1"); | |
253 | ||
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); | |
256 | } | |
257 | gtk_widget_show(main_widgets.toolbar); | |
258 | ||
259 | /* re-add und unref the plugin toolbar items */ | |
260 | i = toolbar_get_insert_position(); | |
261 | foreach_slist(l, plugin_items) | |
262 | { | |
263 | gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), l->data, i); | |
264 | g_object_unref(l->data); | |
265 | i++; | |
266 | } | |
267 | /* re-add und unref the submenus of menu toolbar items */ | |
268 | if (toolbar_new_file_menu != NULL) | |
269 | { | |
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); | |
273 | } | |
274 | if (toolbar_recent_files_menu != NULL) | |
275 | { | |
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); | |
279 | } | |
280 | if (toolbar_build_menu != NULL) | |
281 | { | |
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); | |
285 | } | |
286 | ||
287 | /* update button states */ | |
288 | if (main_status.main_window_realized) | |
289 | { | |
290 | GeanyDocument *doc = document_get_current(); | |
291 | gboolean doc_changed = (doc != NULL) ? doc->changed : FALSE; | |
292 | ||
293 | ui_document_buttons_update(); | |
294 | ui_save_buttons_toggle(doc_changed); /* update save all */ | |
295 | ui_update_popup_reundo_items(doc); | |
296 | ||
297 | toolbar_apply_settings(); | |
298 | } | |
299 | ||
300 | /* Signals */ | |
301 | g_signal_connect(main_widgets.toolbar, "button-press-event", | |
302 | G_CALLBACK(toolbar_popup_menu), NULL); | |
303 | g_signal_connect(main_widgets.toolbar, "key-press-event", | |
304 | G_CALLBACK(on_escape_key_press_event), NULL); | |
305 | ||
306 | /* We don't need to disconnect those signals as this is done automatically when the entry | |
307 | * widgets are destroyed, happens when the toolbar itself is destroyed. */ | |
308 | entry = toolbar_get_widget_child_by_name("SearchEntry"); | |
309 | if (entry != NULL) | |
310 | g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL); | |
311 | entry = toolbar_get_widget_child_by_name("GotoEntry"); | |
312 | if (entry != NULL) | |
313 | g_signal_connect(entry, "motion-notify-event", G_CALLBACK(on_motion_event), NULL); | |
314 | ||
315 | return main_widgets.toolbar; | |
316 | } | |
317 | ||
318 | ||
319 | static void toolbar_notify_style_cb(GObject *object, GParamSpec *arg1, gpointer data) | |
320 | { | |
321 | const gchar *arg_name = g_param_spec_get_name(arg1); | |
322 | gint value; | |
323 | ||
324 | if (toolbar_prefs.use_gtk_default_style && utils_str_equal(arg_name, "gtk-toolbar-style")) | |
325 | { | |
326 | value = ui_get_gtk_settings_integer(arg_name, toolbar_prefs.icon_style); | |
327 | gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), value); | |
328 | } | |
329 | else if (toolbar_prefs.use_gtk_default_icon && utils_str_equal(arg_name, "gtk-toolbar-size")) | |
330 | { | |
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); | |
333 | } | |
334 | } | |
335 | ||
336 | ||
337 | GtkWidget *toolbar_init(void) | |
338 | { | |
339 | GtkWidget *toolbar; | |
340 | GtkAction *action_new; | |
341 | GtkAction *action_open; | |
342 | GtkAction *action_build; | |
343 | GtkAction *action_searchentry; | |
344 | GtkAction *action_gotoentry; | |
345 | GtkSettings *gtk_settings; | |
346 | ||
347 | uim = gtk_ui_manager_new(); | |
348 | group = gtk_action_group_new("GeanyToolbar"); | |
349 | ||
350 | gtk_action_group_set_translation_domain(group, GETTEXT_PACKAGE); | |
351 | gtk_action_group_add_actions(group, ui_entries, ui_entries_n, NULL); | |
352 | ||
353 | /* Create our custom actions */ | |
354 | action_new = geany_menu_button_action_new( | |
355 | "New", NULL, | |
356 | _("Create a new file"), | |
357 | _("Create a new file from a template"), | |
358 | GTK_STOCK_NEW); | |
359 | g_signal_connect(action_new, "button-clicked", G_CALLBACK(on_toolbutton_new_clicked), NULL); | |
360 | gtk_action_group_add_action(group, action_new); | |
361 | ||
362 | action_open = geany_menu_button_action_new( | |
363 | "Open", NULL, | |
364 | _("Open an existing file"), | |
365 | _("Open a recent file"), | |
366 | GTK_STOCK_OPEN); | |
367 | g_signal_connect(action_open, "button-clicked", G_CALLBACK(on_toolbutton_open_clicked), NULL); | |
368 | gtk_action_group_add_action(group, action_open); | |
369 | ||
370 | action_build = geany_menu_button_action_new( | |
371 | "Build", NULL, | |
372 | _("Build the current file"), | |
373 | _("Choose more build actions"), | |
374 | GEANY_STOCK_BUILD); | |
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); | |
378 | ||
379 | action_searchentry = geany_entry_action_new( | |
380 | "SearchEntry", _("Search Field"), _("Find the entered text in the current file"), FALSE); | |
381 | g_signal_connect(action_searchentry, "entry-activate", | |
382 | G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(FALSE)); | |
383 | g_signal_connect(action_searchentry, "entry-activate-backward", | |
384 | G_CALLBACK(on_toolbar_search_entry_activate), GINT_TO_POINTER(TRUE)); | |
385 | g_signal_connect(action_searchentry, "entry-changed", | |
386 | G_CALLBACK(on_toolbar_search_entry_changed), NULL); | |
387 | gtk_action_group_add_action(group, action_searchentry); | |
388 | ||
389 | action_gotoentry = geany_entry_action_new( | |
390 | "GotoEntry", _("Goto Field"), _("Jump to the entered line number"), TRUE); | |
391 | g_signal_connect(action_gotoentry, "entry-activate", | |
392 | G_CALLBACK(on_toolbutton_goto_entry_activate), NULL); | |
393 | gtk_action_group_add_action(group, action_gotoentry); | |
394 | ||
395 | gtk_ui_manager_insert_action_group(uim, group, 0); | |
396 | ||
397 | toolbar = toolbar_reload(NULL); | |
398 | #if GTK_CHECK_VERSION(3, 0, 0) | |
399 | gtk_style_context_add_class(gtk_widget_get_style_context(toolbar), "primary-toolbar"); | |
400 | #endif | |
401 | ||
402 | gtk_settings = gtk_widget_get_settings(GTK_WIDGET(toolbar)); | |
403 | if (gtk_settings != NULL) | |
404 | { | |
405 | g_signal_connect(gtk_settings, "notify::gtk-toolbar-style", | |
406 | G_CALLBACK(toolbar_notify_style_cb), NULL); | |
407 | } | |
408 | ||
409 | return toolbar; | |
410 | } | |
411 | ||
412 | ||
413 | void toolbar_update_ui(void) | |
414 | { | |
415 | static GtkWidget *hbox_menubar = NULL; | |
416 | static GtkWidget *menubar = NULL; | |
417 | GtkWidget *menubar_toolbar_separator = NULL; | |
418 | GtkWidget *parent; | |
419 | GtkToolItem *first_item; | |
420 | ||
421 | if (menubar == NULL) | |
422 | { /* cache widget pointers */ | |
423 | hbox_menubar = ui_lookup_widget(main_widgets.window, "hbox_menubar"); | |
424 | menubar = ui_lookup_widget(main_widgets.window, "menubar1"); | |
425 | } | |
426 | /* the separator between the menubar and the toolbar */ | |
427 | first_item = gtk_toolbar_get_nth_item(GTK_TOOLBAR(main_widgets.toolbar), 0); | |
428 | if (first_item != NULL && GTK_IS_SEPARATOR_TOOL_ITEM(first_item)) | |
429 | { | |
430 | gtk_widget_destroy(GTK_WIDGET(first_item)); | |
431 | } | |
432 | ||
433 | parent = gtk_widget_get_parent(main_widgets.toolbar); | |
434 | ||
435 | if (toolbar_prefs.append_to_menu) | |
436 | { | |
437 | if (parent != NULL) | |
438 | { | |
439 | if (parent != hbox_menubar) | |
440 | { /* here we manually 'reparent' the toolbar, gtk_widget_reparent() doesn't | |
441 | * like to do it */ | |
442 | g_object_ref(main_widgets.toolbar); | |
443 | ||
444 | gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar); | |
445 | gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0); | |
446 | gtk_box_reorder_child(GTK_BOX(hbox_menubar), main_widgets.toolbar, 1); | |
447 | ||
448 | g_object_unref(main_widgets.toolbar); | |
449 | } | |
450 | } | |
451 | else | |
452 | gtk_box_pack_start(GTK_BOX(hbox_menubar), main_widgets.toolbar, TRUE, TRUE, 0); | |
453 | ||
454 | /* the separator between the menubar and the toolbar */ | |
455 | menubar_toolbar_separator = GTK_WIDGET(gtk_separator_tool_item_new()); | |
456 | gtk_widget_show(menubar_toolbar_separator); | |
457 | gtk_toolbar_insert(GTK_TOOLBAR(main_widgets.toolbar), | |
458 | GTK_TOOL_ITEM(menubar_toolbar_separator), 0); | |
459 | } | |
460 | else | |
461 | { | |
462 | GtkWidget *box = ui_lookup_widget(main_widgets.window, "vbox1"); | |
463 | ||
464 | if (parent != NULL) | |
465 | { | |
466 | if (parent != box) | |
467 | { | |
468 | g_object_ref(main_widgets.toolbar); | |
469 | ||
470 | gtk_container_remove(GTK_CONTAINER(parent), main_widgets.toolbar); | |
471 | gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0); | |
472 | gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1); | |
473 | ||
474 | g_object_unref(main_widgets.toolbar); | |
475 | } | |
476 | } | |
477 | else | |
478 | { | |
479 | gtk_box_pack_start(GTK_BOX(box), main_widgets.toolbar, FALSE, FALSE, 0); | |
480 | gtk_box_reorder_child(GTK_BOX(box), main_widgets.toolbar, 1); | |
481 | } | |
482 | } | |
483 | /* we need to adjust the packing flags for the menubar to expand it if it is alone in the | |
484 | * hbox and not expand it if the toolbar is appended */ | |
485 | gtk_box_set_child_packing(GTK_BOX(hbox_menubar), menubar, | |
486 | ! (toolbar_prefs.visible && toolbar_prefs.append_to_menu), TRUE, 0, GTK_PACK_START); | |
487 | } | |
488 | ||
489 | ||
490 | /* Returns the position for adding new toolbar items. The returned position can be used | |
491 | * to add new toolbar items with @c gtk_toolbar_insert(). The toolbar object can be accessed | |
492 | * with @a geany->main_widgets->toolbar. | |
493 | * The position is always the last one before the Quit button or the very last position if the | |
494 | * Quit button is not the last toolbar item. | |
495 | * | |
496 | * @return The position for new toolbar items. | |
497 | */ | |
498 | gint toolbar_get_insert_position(void) | |
499 | { | |
500 | GtkWidget *quit = toolbar_get_widget_by_name("Quit"); | |
501 | gint quit_pos = -1, pos; | |
502 | ||
503 | if (quit != NULL) | |
504 | quit_pos = gtk_toolbar_get_item_index(GTK_TOOLBAR(main_widgets.toolbar), GTK_TOOL_ITEM(quit)); | |
505 | ||
506 | pos = gtk_toolbar_get_n_items(GTK_TOOLBAR(main_widgets.toolbar)); | |
507 | if (quit_pos == (pos - 1)) | |
508 | { | |
509 | /* if the toolbar item before the quit button is a separator, insert new items before */ | |
510 | if (GTK_IS_SEPARATOR_TOOL_ITEM(gtk_toolbar_get_nth_item( | |
511 | GTK_TOOLBAR(main_widgets.toolbar), quit_pos - 1))) | |
512 | { | |
513 | return quit_pos - 1; | |
514 | } | |
515 | /* else return the position of the quit button to insert new items before */ | |
516 | return quit_pos; | |
517 | } | |
518 | ||
519 | return pos; | |
520 | } | |
521 | ||
522 | ||
523 | void toolbar_finalize(void) | |
524 | { | |
525 | GeanyMenubuttonAction *open_action = GEANY_MENU_BUTTON_ACTION(toolbar_get_action_by_name("Open")); | |
526 | g_object_unref(geany_menu_button_action_get_menu(open_action)); | |
527 | geany_menu_button_action_set_menu(open_action, NULL); | |
528 | ||
529 | /* unref'ing the GtkUIManager object will destroy all its widgets unless they were ref'ed */ | |
530 | g_object_unref(uim); | |
531 | g_object_unref(group); | |
532 | ||
533 | g_slist_free(plugin_items); | |
534 | } | |
535 | ||
536 | ||
537 | void toolbar_show_hide(void) | |
538 | { | |
539 | ignore_callback = TRUE; | |
540 | gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM( | |
541 | ui_lookup_widget(main_widgets.window, "menu_show_toolbar1")), toolbar_prefs.visible); | |
542 | ui_widget_show_hide(main_widgets.toolbar, toolbar_prefs.visible); | |
543 | ignore_callback = FALSE; | |
544 | } | |
545 | ||
546 | ||
547 | /* sets the icon style of the toolbar */ | |
548 | static void toolbar_set_icon_style(void) | |
549 | { | |
550 | gint icon_style; | |
551 | ||
552 | icon_style = toolbar_prefs.icon_style; | |
553 | ||
554 | if (toolbar_prefs.use_gtk_default_style) | |
555 | icon_style = ui_get_gtk_settings_integer("gtk-toolbar-style", toolbar_prefs.icon_style); | |
556 | ||
557 | gtk_toolbar_set_style(GTK_TOOLBAR(main_widgets.toolbar), icon_style); | |
558 | } | |
559 | ||
560 | ||
561 | /* sets the icon size of the toolbar */ | |
562 | static void toolbar_set_icon_size(void) | |
563 | { | |
564 | gint icon_size; | |
565 | ||
566 | icon_size = toolbar_prefs.icon_size; | |
567 | ||
568 | if (toolbar_prefs.use_gtk_default_icon) | |
569 | icon_size = ui_get_gtk_settings_integer("gtk-toolbar-icon-size", toolbar_prefs.icon_size); | |
570 | ||
571 | gtk_toolbar_set_icon_size(GTK_TOOLBAR(main_widgets.toolbar), icon_size); | |
572 | } | |
573 | ||
574 | ||
575 | void toolbar_apply_settings(void) | |
576 | { | |
577 | toolbar_set_icon_style(); | |
578 | toolbar_set_icon_size(); | |
579 | } | |
580 | ||
581 | ||
582 | #define TB_EDITOR_SEPARATOR _("Separator") | |
583 | #define TB_EDITOR_SEPARATOR_LABEL _("--- Separator ---") | |
584 | typedef struct | |
585 | { | |
586 | GtkWidget *dialog; | |
587 | ||
588 | GtkTreeView *tree_available; | |
589 | GtkTreeView *tree_used; | |
590 | ||
591 | GtkListStore *store_available; | |
592 | GtkListStore *store_used; | |
593 | ||
594 | GtkTreePath *last_drag_path; | |
595 | GtkTreeViewDropPosition last_drag_pos; | |
596 | ||
597 | GtkWidget *drag_source; | |
598 | } TBEditorWidget; | |
599 | ||
600 | static const GtkTargetEntry tb_editor_dnd_targets[] = | |
601 | { | |
602 | { "GEANY_TB_EDITOR_ROW", 0, 0 } | |
603 | }; | |
604 | static const gint tb_editor_dnd_targets_len = G_N_ELEMENTS(tb_editor_dnd_targets); | |
605 | ||
606 | enum | |
607 | { | |
608 | TB_EDITOR_COL_ACTION, | |
609 | TB_EDITOR_COL_LABEL, | |
610 | TB_EDITOR_COL_ICON, | |
611 | TB_EDITOR_COLS_MAX | |
612 | }; | |
613 | ||
614 | static void tb_editor_handler_start_element(GMarkupParseContext *context, const gchar *element_name, | |
615 | const gchar **attribute_names, | |
616 | const gchar **attribute_values, gpointer data, | |
617 | GError **error) | |
618 | { | |
619 | gint i; | |
620 | GSList **actions = data; | |
621 | ||
622 | /* This is very basic parsing, stripped down any error checking, requires a valid UI markup. */ | |
623 | if (utils_str_equal(element_name, "separator")) | |
624 | *actions = g_slist_append(*actions, g_strdup(TB_EDITOR_SEPARATOR)); | |
625 | ||
626 | for (i = 0; attribute_names[i] != NULL; i++) | |
627 | { | |
628 | if (utils_str_equal(attribute_names[i], "action")) | |
629 | { | |
630 | *actions = g_slist_append(*actions, g_strdup(attribute_values[i])); | |
631 | } | |
632 | } | |
633 | } | |
634 | ||
635 | ||
636 | static const GMarkupParser tb_editor_xml_parser = | |
637 | { | |
638 | tb_editor_handler_start_element, NULL, NULL, NULL, NULL | |
639 | }; | |
640 | ||
641 | ||
642 | static GSList *tb_editor_parse_ui(const gchar *buffer, gssize length, GError **error) | |
643 | { | |
644 | GMarkupParseContext *context; | |
645 | GSList *list = NULL; | |
646 | ||
647 | context = g_markup_parse_context_new(&tb_editor_xml_parser, 0, &list, NULL); | |
648 | g_markup_parse_context_parse(context, buffer, length, error); | |
649 | g_markup_parse_context_free(context); | |
650 | ||
651 | return list; | |
652 | } | |
653 | ||
654 | ||
655 | static void tb_editor_set_item_values(const gchar *name, GtkListStore *store, GtkTreeIter *iter) | |
656 | { | |
657 | gchar *icon = NULL; | |
658 | gchar *label = NULL; | |
659 | gchar *label_clean = NULL; | |
660 | GtkAction *action; | |
661 | ||
662 | action = gtk_action_group_get_action(group, name); | |
663 | if (action == NULL) | |
664 | { | |
665 | if (utils_str_equal(name, TB_EDITOR_SEPARATOR)) | |
666 | label_clean = g_strdup(TB_EDITOR_SEPARATOR_LABEL); | |
667 | else | |
668 | return; | |
669 | } | |
670 | else | |
671 | { | |
672 | g_object_get(action, "icon-name", &icon, NULL); | |
673 | if (icon == NULL) | |
674 | g_object_get(action, "stock-id", &icon, NULL); | |
675 | ||
676 | g_object_get(action, "label", &label, NULL); | |
677 | if (label != NULL) | |
678 | label_clean = utils_str_remove_chars(g_strdup(label), "_"); | |
679 | } | |
680 | ||
681 | gtk_list_store_set(store, iter, | |
682 | TB_EDITOR_COL_ACTION, name, | |
683 | TB_EDITOR_COL_LABEL, label_clean, | |
684 | TB_EDITOR_COL_ICON, icon, | |
685 | -1); | |
686 | ||
687 | g_free(icon); | |
688 | g_free(label); | |
689 | g_free(label_clean); | |
690 | } | |
691 | ||
692 | ||
693 | static void tb_editor_scroll_to_iter(GtkTreeView *treeview, GtkTreeIter *iter) | |
694 | { | |
695 | GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(treeview), iter); | |
696 | gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.0); | |
697 | gtk_tree_path_free(path); | |
698 | } | |
699 | ||
700 | ||
701 | static void tb_editor_free_path(TBEditorWidget *tbw) | |
702 | { | |
703 | if (tbw->last_drag_path != NULL) | |
704 | { | |
705 | gtk_tree_path_free(tbw->last_drag_path); | |
706 | tbw->last_drag_path = NULL; | |
707 | } | |
708 | } | |
709 | ||
710 | ||
711 | static void tb_editor_btn_remove_clicked_cb(GtkWidget *button, TBEditorWidget *tbw) | |
712 | { | |
713 | GtkTreeModel *model_used; | |
714 | GtkTreeSelection *selection_used; | |
715 | GtkTreeIter iter_used, iter_new; | |
716 | gchar *action_name; | |
717 | ||
718 | selection_used = gtk_tree_view_get_selection(tbw->tree_used); | |
719 | if (gtk_tree_selection_get_selected(selection_used, &model_used, &iter_used)) | |
720 | { | |
721 | gtk_tree_model_get(model_used, &iter_used, TB_EDITOR_COL_ACTION, &action_name, -1); | |
722 | if (gtk_list_store_remove(tbw->store_used, &iter_used)) | |
723 | gtk_tree_selection_select_iter(selection_used, &iter_used); | |
724 | ||
725 | if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) | |
726 | { | |
727 | gtk_list_store_append(tbw->store_available, &iter_new); | |
728 | tb_editor_set_item_values(action_name, tbw->store_available, &iter_new); | |
729 | tb_editor_scroll_to_iter(tbw->tree_available, &iter_new); | |
730 | } | |
731 | ||
732 | g_free(action_name); | |
733 | } | |
734 | } | |
735 | ||
736 | ||
737 | static void tb_editor_btn_add_clicked_cb(GtkWidget *button, TBEditorWidget *tbw) | |
738 | { | |
739 | GtkTreeModel *model_available; | |
740 | GtkTreeSelection *selection_available, *selection_used; | |
741 | GtkTreeIter iter_available, iter_new, iter_selected; | |
742 | gchar *action_name; | |
743 | ||
744 | selection_available = gtk_tree_view_get_selection(tbw->tree_available); | |
745 | if (gtk_tree_selection_get_selected(selection_available, &model_available, &iter_available)) | |
746 | { | |
747 | gtk_tree_model_get(model_available, &iter_available, | |
748 | TB_EDITOR_COL_ACTION, &action_name, -1); | |
749 | if (! utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) | |
750 | { | |
751 | if (gtk_list_store_remove(tbw->store_available, &iter_available)) | |
752 | gtk_tree_selection_select_iter(selection_available, &iter_available); | |
753 | } | |
754 | ||
755 | selection_used = gtk_tree_view_get_selection(tbw->tree_used); | |
756 | if (gtk_tree_selection_get_selected(selection_used, NULL, &iter_selected)) | |
757 | gtk_list_store_insert_before(tbw->store_used, &iter_new, &iter_selected); | |
758 | else | |
759 | gtk_list_store_append(tbw->store_used, &iter_new); | |
760 | ||
761 | tb_editor_set_item_values(action_name, tbw->store_used, &iter_new); | |
762 | tb_editor_scroll_to_iter(tbw->tree_used, &iter_new); | |
763 | ||
764 | g_free(action_name); | |
765 | } | |
766 | } | |
767 | ||
768 | ||
769 | static gboolean tb_editor_drag_motion_cb(GtkWidget *widget, GdkDragContext *drag_context, | |
770 | gint x, gint y, guint ltime, TBEditorWidget *tbw) | |
771 | { | |
772 | if (tbw->last_drag_path != NULL) | |
773 | gtk_tree_path_free(tbw->last_drag_path); | |
774 | gtk_tree_view_get_drag_dest_row(GTK_TREE_VIEW(widget), | |
775 | &(tbw->last_drag_path), &(tbw->last_drag_pos)); | |
776 | ||
777 | return FALSE; | |
778 | } | |
779 | ||
780 | ||
781 | static void tb_editor_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context, | |
782 | GtkSelectionData *data, guint info, guint ltime, | |
783 | TBEditorWidget *tbw) | |
784 | { | |
785 | GtkTreeIter iter; | |
786 | GtkTreeSelection *selection; | |
787 | GtkTreeModel *model; | |
788 | GdkAtom atom; | |
789 | gchar *name; | |
790 | ||
791 | selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); | |
792 | if (! gtk_tree_selection_get_selected(selection, &model, &iter)) | |
793 | return; | |
794 | ||
795 | gtk_tree_model_get(model, &iter, TB_EDITOR_COL_ACTION, &name, -1); | |
796 | if (G_UNLIKELY(EMPTY(name))) | |
797 | { | |
798 | g_free(name); | |
799 | return; | |
800 | } | |
801 | ||
802 | atom = gdk_atom_intern(tb_editor_dnd_targets[0].target, FALSE); | |
803 | gtk_selection_data_set(data, atom, 8, (guchar*) name, strlen(name)); | |
804 | ||
805 | g_free(name); | |
806 | ||
807 | tbw->drag_source = widget; | |
808 | } | |
809 | ||
810 | ||
811 | static void tb_editor_drag_data_rcvd_cb(GtkWidget *widget, GdkDragContext *context, | |
812 | gint x, gint y, GtkSelectionData *data, guint info, | |
813 | guint ltime, TBEditorWidget *tbw) | |
814 | { | |
815 | GtkTreeView *tree = GTK_TREE_VIEW(widget); | |
816 | gboolean del = FALSE; | |
817 | ||
818 | if (gtk_selection_data_get_length(data) >= 0 && gtk_selection_data_get_format(data) == 8) | |
819 | { | |
820 | gboolean is_sep; | |
821 | gchar *text = NULL; | |
822 | ||
823 | text = (gchar*) gtk_selection_data_get_data(data); | |
824 | is_sep = utils_str_equal(text, TB_EDITOR_SEPARATOR); | |
825 | /* If the source of the action is equal to the target, we do just re-order and so need | |
826 | * to delete the separator to get it moved, not just copied. */ | |
827 | if (is_sep && widget == tbw->drag_source) | |
828 | is_sep = FALSE; | |
829 | ||
830 | if (tree != tbw->tree_available || ! is_sep) | |
831 | { | |
832 | GtkTreeIter iter, iter_before, *iter_before_ptr; | |
833 | GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree)); | |
834 | ||
835 | if (tbw->last_drag_path != NULL) | |
836 | { | |
837 | gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter_before, tbw->last_drag_path); | |
838 | ||
839 | if (gtk_list_store_iter_is_valid(store, &iter_before)) | |
840 | iter_before_ptr = &iter_before; | |
841 | else | |
842 | iter_before_ptr = NULL; | |
843 | ||
844 | if (tbw->last_drag_pos == GTK_TREE_VIEW_DROP_BEFORE || | |
845 | tbw->last_drag_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) | |
846 | gtk_list_store_insert_before(store, &iter, iter_before_ptr); | |
847 | else | |
848 | gtk_list_store_insert_after(store, &iter, iter_before_ptr); | |
849 | } | |
850 | else | |
851 | gtk_list_store_append(store, &iter); | |
852 | ||
853 | tb_editor_set_item_values(text, store, &iter); | |
854 | tb_editor_scroll_to_iter(tree, &iter); | |
855 | } | |
856 | if (tree != tbw->tree_used || ! is_sep) | |
857 | del = TRUE; | |
858 | } | |
859 | ||
860 | tbw->drag_source = NULL; /* reset the value just to be sure */ | |
861 | tb_editor_free_path(tbw); | |
862 | gtk_drag_finish(context, TRUE, del, ltime); | |
863 | } | |
864 | ||
865 | ||
866 | static gboolean tb_editor_foreach_used(GtkTreeModel *model, GtkTreePath *path, | |
867 | GtkTreeIter *iter, gpointer data) | |
868 | { | |
869 | gchar *action_name; | |
870 | ||
871 | gtk_tree_model_get(model, iter, TB_EDITOR_COL_ACTION, &action_name, -1); | |
872 | ||
873 | if (utils_str_equal(action_name, TB_EDITOR_SEPARATOR)) | |
874 | g_string_append_printf(data, "\t\t<separator/>\n"); | |
875 | else if (G_LIKELY(!EMPTY(action_name))) | |
876 | g_string_append_printf(data, "\t\t<toolitem action='%s' />\n", action_name); | |
877 | ||
878 | g_free(action_name); | |
879 | return FALSE; | |
880 | } | |
881 | ||
882 | ||
883 | static void tb_editor_write_markup(TBEditorWidget *tbw) | |
884 | { | |
885 | /* <ui> must be the first tag, otherwise gtk_ui_manager_add_ui_from_string() will fail. */ | |
886 | const gchar *template = "<ui>\n<!--\n\ | |
887 | This is Geany's toolbar UI definition.\nThe DTD can be found at \n\ | |
888 | http://library.gnome.org/devel/gtk/stable/GtkUIManager.html#GtkUIManager.description.\n\n\ | |
889 | You can re-order all items and freely add and remove available actions.\n\ | |
890 | You cannot add new actions which are not listed in the documentation.\n\ | |
891 | Everything you add or change must be inside the /ui/toolbar/ path.\n\n\ | |
892 | For changes to take effect, you need to restart Geany. Alternatively you can use the toolbar\n\ | |
893 | editor in Geany.\n\n\ | |
894 | A list of available actions can be found in the documentation included with Geany or\n\ | |
895 | at http://www.geany.org/manual/current/index.html#customizing-the-toolbar.\n-->\n\ | |
896 | \t<toolbar name='GeanyToolbar'>\n"; | |
897 | gchar *filename; | |
898 | GString *str = g_string_new(template); | |
899 | ||
900 | gtk_tree_model_foreach(GTK_TREE_MODEL(tbw->store_used), tb_editor_foreach_used, str); | |
901 | ||
902 | g_string_append(str, "\n\t</toolbar>\n</ui>\n"); | |
903 | ||
904 | toolbar_reload(str->str); | |
905 | ||
906 | filename = g_build_filename(app->configdir, "ui_toolbar.xml", NULL); | |
907 | utils_write_file(filename, str->str); | |
908 | g_free(filename); | |
909 | ||
910 | g_string_free(str, TRUE); | |
911 | } | |
912 | ||
913 | ||
914 | static void tb_editor_available_items_changed_cb(GtkTreeModel *model, GtkTreePath *arg1, | |
915 | GtkTreeIter *arg2, TBEditorWidget *tbw) | |
916 | { | |
917 | tb_editor_write_markup(tbw); | |
918 | } | |
919 | ||
920 | ||
921 | static void tb_editor_available_items_deleted_cb(GtkTreeModel *model, GtkTreePath *arg1, | |
922 | TBEditorWidget *tbw) | |
923 | { | |
924 | tb_editor_write_markup(tbw); | |
925 | } | |
926 | ||
927 | ||
928 | static TBEditorWidget *tb_editor_create_dialog(GtkWindow *parent) | |
929 | { | |
930 | GtkWidget *dialog, *vbox, *hbox, *vbox_buttons, *button_add, *button_remove; | |
931 | GtkWidget *swin_available, *swin_used, *tree_available, *tree_used, *label; | |
932 | GtkCellRenderer *text_renderer, *icon_renderer; | |
933 | GtkTreeViewColumn *column; | |
934 | TBEditorWidget *tbw = g_new(TBEditorWidget, 1); | |
935 | ||
936 | if (parent == NULL) | |
937 | parent = GTK_WINDOW(main_widgets.window); | |
938 | ||
939 | dialog = gtk_dialog_new_with_buttons(_("Customize Toolbar"), | |
940 | parent, | |
941 | GTK_DIALOG_DESTROY_WITH_PARENT, | |
942 | GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); | |
943 | vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); | |
944 | gtk_box_set_spacing(GTK_BOX(vbox), 6); | |
945 | gtk_widget_set_name(dialog, "GeanyDialog"); | |
946 | gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 400); | |
947 | gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE); | |
948 | ||
949 | tbw->store_available = gtk_list_store_new(TB_EDITOR_COLS_MAX, | |
950 | G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); | |
951 | tbw->store_used = gtk_list_store_new(TB_EDITOR_COLS_MAX, | |
952 | G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); | |
953 | ||
954 | label = gtk_label_new( | |
955 | _("Select items to be displayed on the toolbar. Items can be reordered by drag and drop.")); | |
956 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); | |
957 | ||
958 | tree_available = gtk_tree_view_new(); | |
959 | gtk_tree_view_set_model(GTK_TREE_VIEW(tree_available), GTK_TREE_MODEL(tbw->store_available)); | |
960 | gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_available), TRUE); | |
961 | gtk_tree_sortable_set_sort_column_id( | |
962 | GTK_TREE_SORTABLE(tbw->store_available), TB_EDITOR_COL_LABEL, GTK_SORT_ASCENDING); | |
963 | ||
964 | icon_renderer = gtk_cell_renderer_pixbuf_new(); | |
965 | column = gtk_tree_view_column_new_with_attributes( | |
966 | NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL); | |
967 | gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column); | |
968 | ||
969 | text_renderer = gtk_cell_renderer_text_new(); | |
970 | column = gtk_tree_view_column_new_with_attributes( | |
971 | _("Available Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL); | |
972 | gtk_tree_view_append_column(GTK_TREE_VIEW(tree_available), column); | |
973 | ||
974 | swin_available = gtk_scrolled_window_new(NULL, NULL); | |
975 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_available), | |
976 | GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); | |
977 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_available), GTK_SHADOW_ETCHED_IN); | |
978 | gtk_container_add(GTK_CONTAINER(swin_available), tree_available); | |
979 | ||
980 | tree_used = gtk_tree_view_new(); | |
981 | gtk_tree_view_set_model(GTK_TREE_VIEW(tree_used), GTK_TREE_MODEL(tbw->store_used)); | |
982 | gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_used), TRUE); | |
983 | gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_used), TRUE); | |
984 | ||
985 | icon_renderer = gtk_cell_renderer_pixbuf_new(); | |
986 | column = gtk_tree_view_column_new_with_attributes( | |
987 | NULL, icon_renderer, "stock-id", TB_EDITOR_COL_ICON, NULL); | |
988 | gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column); | |
989 | ||
990 | text_renderer = gtk_cell_renderer_text_new(); | |
991 | column = gtk_tree_view_column_new_with_attributes( | |
992 | _("Displayed Items"), text_renderer, "text", TB_EDITOR_COL_LABEL, NULL); | |
993 | gtk_tree_view_append_column(GTK_TREE_VIEW(tree_used), column); | |
994 | ||
995 | swin_used = gtk_scrolled_window_new(NULL, NULL); | |
996 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin_used), | |
997 | GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); | |
998 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin_used), GTK_SHADOW_ETCHED_IN); | |
999 | gtk_container_add(GTK_CONTAINER(swin_used), tree_used); | |
1000 | ||
1001 | /* drag'n'drop */ | |
1002 | gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_available), GDK_BUTTON1_MASK, | |
1003 | tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); | |
1004 | gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_available), | |
1005 | tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); | |
1006 | g_signal_connect(tree_available, "drag-data-get", | |
1007 | G_CALLBACK(tb_editor_drag_data_get_cb), tbw); | |
1008 | g_signal_connect(tree_available, "drag-data-received", | |
1009 | G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw); | |
1010 | g_signal_connect(tree_available, "drag-motion", | |
1011 | G_CALLBACK(tb_editor_drag_motion_cb), tbw); | |
1012 | ||
1013 | gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_used), GDK_BUTTON1_MASK, | |
1014 | tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); | |
1015 | gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(tree_used), | |
1016 | tb_editor_dnd_targets, tb_editor_dnd_targets_len, GDK_ACTION_MOVE); | |
1017 | g_signal_connect(tree_used, "drag-data-get", | |
1018 | G_CALLBACK(tb_editor_drag_data_get_cb), tbw); | |
1019 | g_signal_connect(tree_used, "drag-data-received", | |
1020 | G_CALLBACK(tb_editor_drag_data_rcvd_cb), tbw); | |
1021 | g_signal_connect(tree_used, "drag-motion", | |
1022 | G_CALLBACK(tb_editor_drag_motion_cb), tbw); | |
1023 | ||
1024 | ||
1025 | button_add = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, NULL); | |
1026 | button_remove = ui_button_new_with_image(GTK_STOCK_GO_BACK, NULL); | |
1027 | g_signal_connect(button_add, "clicked", G_CALLBACK(tb_editor_btn_add_clicked_cb), tbw); | |
1028 | g_signal_connect(button_remove, "clicked", G_CALLBACK(tb_editor_btn_remove_clicked_cb), tbw); | |
1029 | ||
1030 | vbox_buttons = gtk_vbox_new(FALSE, 6); | |
1031 | /* FIXME this is a little hack'ish, any better ideas? */ | |
1032 | gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0); | |
1033 | gtk_box_pack_start(GTK_BOX(vbox_buttons), button_add, FALSE, FALSE, 0); | |
1034 | gtk_box_pack_start(GTK_BOX(vbox_buttons), button_remove, FALSE, FALSE, 0); | |
1035 | gtk_box_pack_start(GTK_BOX(vbox_buttons), gtk_label_new(""), TRUE, TRUE, 0); | |
1036 | ||
1037 | hbox = gtk_hbox_new(FALSE, 6); | |
1038 | gtk_box_pack_start(GTK_BOX(hbox), swin_available, TRUE, TRUE, 0); | |
1039 | gtk_box_pack_start(GTK_BOX(hbox), vbox_buttons, FALSE, FALSE, 0); | |
1040 | gtk_box_pack_start(GTK_BOX(hbox), swin_used, TRUE, TRUE, 0); | |
1041 | ||
1042 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6); | |
1043 | gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); | |
1044 | ||
1045 | gtk_widget_show_all(vbox); | |
1046 | ||
1047 | g_object_unref(tbw->store_available); | |
1048 | g_object_unref(tbw->store_used); | |
1049 | ||
1050 | tbw->dialog = dialog; | |
1051 | tbw->tree_available = GTK_TREE_VIEW(tree_available); | |
1052 | tbw->tree_used = GTK_TREE_VIEW(tree_used); | |
1053 | ||
1054 | tbw->last_drag_path = NULL; | |
1055 | ||
1056 | return tbw; | |
1057 | } | |
1058 | ||
1059 | ||
1060 | void toolbar_configure(GtkWindow *parent) | |
1061 | { | |
1062 | gchar *markup; | |
1063 | const gchar *name; | |
1064 | GSList *sl, *used_items; | |
1065 | GList *l, *all_items; | |
1066 | GtkTreeIter iter; | |
1067 | GtkTreePath *path; | |
1068 | TBEditorWidget *tbw; | |
1069 | ||
1070 | /* read the current active toolbar items */ | |
1071 | markup = gtk_ui_manager_get_ui(uim); | |
1072 | used_items = tb_editor_parse_ui(markup, -1, NULL); | |
1073 | g_free(markup); | |
1074 | ||
1075 | /* get all available actions */ | |
1076 | all_items = gtk_action_group_list_actions(group); | |
1077 | ||
1078 | /* create the GUI */ | |
1079 | tbw = tb_editor_create_dialog(parent); | |
1080 | ||
1081 | /* fill the stores */ | |
1082 | gtk_list_store_insert_with_values(tbw->store_available, NULL, -1, | |
1083 | TB_EDITOR_COL_ACTION, TB_EDITOR_SEPARATOR, | |
1084 | TB_EDITOR_COL_LABEL, TB_EDITOR_SEPARATOR_LABEL, | |
1085 | -1); | |
1086 | foreach_list(l, all_items) | |
1087 | { | |
1088 | name = gtk_action_get_name(l->data); | |
1089 | if (g_slist_find_custom(used_items, name, (GCompareFunc) strcmp) == NULL) | |
1090 | { | |
1091 | gtk_list_store_append(tbw->store_available, &iter); | |
1092 | tb_editor_set_item_values(name, tbw->store_available, &iter); | |
1093 | } | |
1094 | } | |
1095 | foreach_slist(sl, used_items) | |
1096 | { | |
1097 | gtk_list_store_append(tbw->store_used, &iter); | |
1098 | tb_editor_set_item_values(sl->data, tbw->store_used, &iter); | |
1099 | } | |
1100 | /* select first item */ | |
1101 | path = gtk_tree_path_new_from_string("0"); | |
1102 | gtk_tree_selection_select_path(gtk_tree_view_get_selection(tbw->tree_used), path); | |
1103 | gtk_tree_path_free(path); | |
1104 | ||
1105 | /* connect the changed signals after populating the store */ | |
1106 | g_signal_connect(tbw->store_used, "row-changed", | |
1107 | G_CALLBACK(tb_editor_available_items_changed_cb), tbw); | |
1108 | g_signal_connect(tbw->store_used, "row-deleted", | |
1109 | G_CALLBACK(tb_editor_available_items_deleted_cb), tbw); | |
1110 | ||
1111 | /* run it */ | |
1112 | gtk_dialog_run(GTK_DIALOG(tbw->dialog)); | |
1113 | ||
1114 | gtk_widget_destroy(tbw->dialog); | |
1115 | ||
1116 | g_slist_foreach(used_items, (GFunc) g_free, NULL); | |
1117 | g_slist_free(used_items); | |
1118 | g_list_free(all_items); | |
1119 | tb_editor_free_path(tbw); | |
1120 | g_free(tbw); | |
1121 | } |