1 /*
2 * Copyright (C) 2003 Marco Pesenti Gritti
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * $Id$
19 */
20
21 #include "config.h"
22
23 #include "egg-toolbar-editor.h"
24 #include "egg-editable-toolbar.h"
25
26 #include <string.h>
27 #include <libxml/tree.h>
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30
31 static const GtkTargetEntry dest_drag_types[] = {
32 {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
33 };
34
35 static const GtkTargetEntry source_drag_types[] = {
36 {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
37 };
38
39
40 static void egg_toolbar_editor_finalize (GObject *object);
41 static void update_editor_sheet (EggToolbarEditor *editor);
42
43 enum
44 {
45 PROP_0,
46 PROP_UI_MANAGER,
47 PROP_TOOLBARS_MODEL
48 };
49
50 enum
51 {
52 SIGNAL_HANDLER_ITEM_ADDED,
53 SIGNAL_HANDLER_ITEM_REMOVED,
54 SIGNAL_HANDLER_TOOLBAR_REMOVED,
55 SIGNAL_HANDLER_LIST_SIZE /* Array size */
56 };
57
58 struct EggToolbarEditorPrivate
59 {
60 GtkUIManager *manager;
61 EggToolbarsModel *model;
62
63 GtkWidget *grid;
64 GtkWidget *scrolled_window;
65 GList *actions_list;
66 GList *factory_list;
67
68 /* These handlers need to be sanely disconnected when switching models */
69 gulong sig_handlers[SIGNAL_HANDLER_LIST_SIZE];
70 };
71
72 G_DEFINE_TYPE_WITH_PRIVATE (EggToolbarEditor, egg_toolbar_editor, GTK_TYPE_BOX);
73
74 static gint
compare_items(gconstpointer a,gconstpointer b)75 compare_items (gconstpointer a,
76 gconstpointer b)
77 {
78 const GtkWidget *item1 = a;
79 const GtkWidget *item2 = b;
80
81 char *key1 = g_object_get_data (G_OBJECT (item1),
82 "egg-collate-key");
83 char *key2 = g_object_get_data (G_OBJECT (item2),
84 "egg-collate-key");
85
86 return strcmp (key1, key2);
87 }
88
89 static GtkAction *
find_action(EggToolbarEditor * t,const char * name)90 find_action (EggToolbarEditor *t,
91 const char *name)
92 {
93 GList *l;
94 GtkAction *action = NULL;
95
96 l = gtk_ui_manager_get_action_groups (t->priv->manager);
97
98 g_return_val_if_fail (EGG_IS_TOOLBAR_EDITOR (t), NULL);
99 g_return_val_if_fail (name != NULL, NULL);
100
101 for (; l != NULL; l = l->next)
102 {
103 GtkAction *tmp;
104
105 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
106 tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name);
107 G_GNUC_END_IGNORE_DEPRECATIONS;
108 if (tmp)
109 action = tmp;
110 }
111
112 return action;
113 }
114
115 static void
egg_toolbar_editor_set_ui_manager(EggToolbarEditor * t,GtkUIManager * manager)116 egg_toolbar_editor_set_ui_manager (EggToolbarEditor *t,
117 GtkUIManager *manager)
118 {
119 g_return_if_fail (GTK_IS_UI_MANAGER (manager));
120
121 t->priv->manager = g_object_ref (manager);
122 }
123
124 static void
item_added_or_removed_cb(EggToolbarsModel * model,int tpos,int ipos,EggToolbarEditor * editor)125 item_added_or_removed_cb (EggToolbarsModel *model,
126 int tpos,
127 int ipos,
128 EggToolbarEditor *editor)
129 {
130 update_editor_sheet (editor);
131 }
132
133 static void
toolbar_removed_cb(EggToolbarsModel * model,int position,EggToolbarEditor * editor)134 toolbar_removed_cb (EggToolbarsModel *model,
135 int position,
136 EggToolbarEditor *editor)
137 {
138 update_editor_sheet (editor);
139 }
140
141 static void
egg_toolbar_editor_disconnect_model(EggToolbarEditor * t)142 egg_toolbar_editor_disconnect_model (EggToolbarEditor *t)
143 {
144 EggToolbarEditorPrivate *priv = t->priv;
145 EggToolbarsModel *model = priv->model;
146 gulong handler;
147 int i;
148
149 for (i = 0; i < SIGNAL_HANDLER_LIST_SIZE; i++)
150 {
151 handler = priv->sig_handlers[i];
152
153 if (handler != 0)
154 {
155 if (g_signal_handler_is_connected (model, handler))
156 {
157 g_signal_handler_disconnect (model, handler);
158 }
159
160 priv->sig_handlers[i] = 0;
161 }
162 }
163 }
164
165 void
egg_toolbar_editor_set_model(EggToolbarEditor * t,EggToolbarsModel * model)166 egg_toolbar_editor_set_model (EggToolbarEditor *t,
167 EggToolbarsModel *model)
168 {
169 EggToolbarEditorPrivate *priv;
170
171 g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (t));
172 g_return_if_fail (model != NULL);
173
174 priv = t->priv;
175
176 if (priv->model)
177 {
178 if (G_UNLIKELY (priv->model == model)) return;
179
180 egg_toolbar_editor_disconnect_model (t);
181 g_object_unref (priv->model);
182 }
183
184 priv->model = g_object_ref (model);
185
186 update_editor_sheet (t);
187
188 priv->sig_handlers[SIGNAL_HANDLER_ITEM_ADDED] =
189 g_signal_connect_object (model, "item_added",
190 G_CALLBACK (item_added_or_removed_cb), t, 0);
191 priv->sig_handlers[SIGNAL_HANDLER_ITEM_REMOVED] =
192 g_signal_connect_object (model, "item_removed",
193 G_CALLBACK (item_added_or_removed_cb), t, 0);
194 priv->sig_handlers[SIGNAL_HANDLER_TOOLBAR_REMOVED] =
195 g_signal_connect_object (model, "toolbar_removed",
196 G_CALLBACK (toolbar_removed_cb), t, 0);
197 }
198
199 static void
egg_toolbar_editor_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)200 egg_toolbar_editor_set_property (GObject *object,
201 guint prop_id,
202 const GValue *value,
203 GParamSpec *pspec)
204 {
205 EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object);
206
207 switch (prop_id)
208 {
209 case PROP_UI_MANAGER:
210 egg_toolbar_editor_set_ui_manager (t, g_value_get_object (value));
211 break;
212 case PROP_TOOLBARS_MODEL:
213 egg_toolbar_editor_set_model (t, g_value_get_object (value));
214 break;
215 }
216 }
217
218 static void
egg_toolbar_editor_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)219 egg_toolbar_editor_get_property (GObject *object,
220 guint prop_id,
221 GValue *value,
222 GParamSpec *pspec)
223 {
224 EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object);
225
226 switch (prop_id)
227 {
228 case PROP_UI_MANAGER:
229 g_value_set_object (value, t->priv->manager);
230 break;
231 case PROP_TOOLBARS_MODEL:
232 g_value_set_object (value, t->priv->model);
233 break;
234 }
235 }
236
237 static void
egg_toolbar_editor_class_init(EggToolbarEditorClass * klass)238 egg_toolbar_editor_class_init (EggToolbarEditorClass *klass)
239 {
240 GObjectClass *object_class = G_OBJECT_CLASS (klass);
241
242 object_class->finalize = egg_toolbar_editor_finalize;
243 object_class->set_property = egg_toolbar_editor_set_property;
244 object_class->get_property = egg_toolbar_editor_get_property;
245
246 g_object_class_install_property (object_class,
247 PROP_UI_MANAGER,
248 g_param_spec_object ("ui-manager",
249 "UI-Manager",
250 "UI Manager",
251 GTK_TYPE_UI_MANAGER,
252 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
253 G_PARAM_CONSTRUCT_ONLY));
254 g_object_class_install_property (object_class,
255 PROP_TOOLBARS_MODEL,
256 g_param_spec_object ("model",
257 "Model",
258 "Toolbars Model",
259 EGG_TYPE_TOOLBARS_MODEL,
260 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
261 G_PARAM_CONSTRUCT));
262
263 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
264 gtk_widget_class_set_css_name (widget_class, "EggToolbarEditor");
265 }
266
267 static void
egg_toolbar_editor_finalize(GObject * object)268 egg_toolbar_editor_finalize (GObject *object)
269 {
270 EggToolbarEditor *editor = EGG_TOOLBAR_EDITOR (object);
271
272 if (editor->priv->manager)
273 {
274 g_object_unref (editor->priv->manager);
275 }
276
277 if (editor->priv->model)
278 {
279 egg_toolbar_editor_disconnect_model (editor);
280 g_object_unref (editor->priv->model);
281 }
282
283 g_list_free (editor->priv->actions_list);
284 g_list_free (editor->priv->factory_list);
285
286 G_OBJECT_CLASS (egg_toolbar_editor_parent_class)->finalize (object);
287 }
288
289 GtkWidget *
egg_toolbar_editor_new(GtkUIManager * manager,EggToolbarsModel * model)290 egg_toolbar_editor_new (GtkUIManager *manager,
291 EggToolbarsModel *model)
292 {
293 return GTK_WIDGET (g_object_new (EGG_TYPE_TOOLBAR_EDITOR,
294 "ui-manager", manager,
295 "model", model,
296 NULL));
297 }
298
299 static void
drag_begin_cb(GtkWidget * widget,GdkDragContext * context)300 drag_begin_cb (GtkWidget *widget,
301 GdkDragContext *context)
302 {
303 gtk_widget_hide (widget);
304 }
305
306 static void
drag_end_cb(GtkWidget * widget,GdkDragContext * context)307 drag_end_cb (GtkWidget *widget,
308 GdkDragContext *context)
309 {
310 gtk_widget_show (widget);
311 }
312
313 static void
drag_data_get_cb(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,EggToolbarEditor * editor)314 drag_data_get_cb (GtkWidget *widget,
315 GdkDragContext *context,
316 GtkSelectionData *selection_data,
317 guint info,
318 guint32 time,
319 EggToolbarEditor *editor)
320 {
321 const char *target;
322
323 target = g_object_get_data (G_OBJECT (widget), "egg-item-name");
324 g_return_if_fail (target != NULL);
325
326 gtk_selection_data_set (selection_data,
327 gtk_selection_data_get_target (selection_data), 8,
328 (const guchar *) target, strlen (target));
329 }
330
331 static gchar *
elide_underscores(const gchar * original)332 elide_underscores (const gchar *original)
333 {
334 gchar *q, *result;
335 const gchar *p;
336 gboolean last_underscore;
337
338 q = result = g_malloc (strlen (original) + 1);
339 last_underscore = FALSE;
340
341 for (p = original; *p; p++)
342 {
343 if (!last_underscore && *p == '_')
344 last_underscore = TRUE;
345 else
346 {
347 last_underscore = FALSE;
348 *q++ = *p;
349 }
350 }
351
352 *q = '\0';
353
354 return result;
355 }
356
357 static void
set_drag_cursor(GtkWidget * widget)358 set_drag_cursor (GtkWidget *widget)
359 {
360 GdkCursor *cursor;
361 GdkScreen *screen;
362
363 screen = gtk_widget_get_screen (widget);
364
365 cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen),
366 GDK_HAND2);
367 gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
368 g_object_unref (cursor);
369 }
370
371 static void
event_box_realize_cb(GtkWidget * widget,GtkImage * icon)372 event_box_realize_cb (GtkWidget *widget, GtkImage *icon)
373 {
374 GtkImageType type;
375
376 set_drag_cursor (widget);
377
378 type = gtk_image_get_storage_type (icon);
379 if (type == GTK_IMAGE_STOCK)
380 {
381 gchar *stock_id;
382 GdkPixbuf *pixbuf;
383
384 gtk_image_get_stock (icon, &stock_id, NULL);
385 pixbuf = gtk_widget_render_icon_pixbuf (widget, stock_id,
386 GTK_ICON_SIZE_LARGE_TOOLBAR);
387 gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
388 g_object_unref (pixbuf);
389 }
390 else if (type == GTK_IMAGE_ICON_NAME)
391 {
392 const gchar *icon_name;
393 GdkScreen *screen;
394 GtkIconTheme *icon_theme;
395 gint width, height;
396 GdkPixbuf *pixbuf;
397
398 gtk_image_get_icon_name (icon, &icon_name, NULL);
399 screen = gtk_widget_get_screen (widget);
400 icon_theme = gtk_icon_theme_get_for_screen (screen);
401
402 if (!gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR,
403 &width, &height))
404 {
405 width = height = 24;
406 }
407
408 pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name,
409 MIN (width, height), 0, NULL);
410 if (G_UNLIKELY (!pixbuf))
411 return;
412
413 gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
414 g_object_unref (pixbuf);
415
416 }
417 else if (type == GTK_IMAGE_PIXBUF)
418 {
419 GdkPixbuf *pixbuf = gtk_image_get_pixbuf (icon);
420 gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
421 }
422 }
423
424 static GtkWidget *
editor_create_item(EggToolbarEditor * editor,GtkImage * icon,const char * label_text,GdkDragAction action)425 editor_create_item (EggToolbarEditor *editor,
426 GtkImage *icon,
427 const char *label_text,
428 GdkDragAction action)
429 {
430 GtkWidget *event_box;
431 GtkWidget *vbox;
432 GtkWidget *label;
433 gchar *label_no_mnemonic = NULL;
434
435 event_box = gtk_event_box_new ();
436 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
437 gtk_widget_show (event_box);
438 gtk_drag_source_set (event_box,
439 GDK_BUTTON1_MASK,
440 source_drag_types, G_N_ELEMENTS (source_drag_types), action);
441 g_signal_connect (event_box, "drag_data_get",
442 G_CALLBACK (drag_data_get_cb), editor);
443 g_signal_connect_after (event_box, "realize",
444 G_CALLBACK (event_box_realize_cb), icon);
445
446 if (action == GDK_ACTION_MOVE)
447 {
448 g_signal_connect (event_box, "drag_begin",
449 G_CALLBACK (drag_begin_cb), NULL);
450 g_signal_connect (event_box, "drag_end",
451 G_CALLBACK (drag_end_cb), NULL);
452 }
453
454 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
455 gtk_widget_show (vbox);
456 gtk_container_add (GTK_CONTAINER (event_box), vbox);
457
458 gtk_widget_show (GTK_WIDGET (icon));
459 gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (icon), FALSE, TRUE, 0);
460 label_no_mnemonic = elide_underscores (label_text);
461 label = gtk_label_new (label_no_mnemonic);
462 g_free (label_no_mnemonic);
463 gtk_widget_show (label);
464 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0);
465
466 return event_box;
467 }
468
469 static GtkWidget *
editor_create_item_from_name(EggToolbarEditor * editor,const char * name,GdkDragAction drag_action)470 editor_create_item_from_name (EggToolbarEditor *editor,
471 const char * name,
472 GdkDragAction drag_action)
473 {
474 GtkWidget *item;
475 const char *item_name;
476 char *short_label;
477 const char *collate_key;
478
479 if (strcmp (name, "_separator") == 0)
480 {
481 GtkWidget *icon;
482
483 icon = _egg_editable_toolbar_new_separator_image ();
484 short_label = _("Separator");
485 item_name = g_strdup (name);
486 collate_key = g_utf8_collate_key (short_label, -1);
487 item = editor_create_item (editor, GTK_IMAGE (icon),
488 short_label, drag_action);
489 }
490 else
491 {
492 GtkAction *action;
493 GtkWidget *icon;
494 char *stock_id, *icon_name = NULL;
495
496 action = find_action (editor, name);
497 g_return_val_if_fail (action != NULL, NULL);
498
499 g_object_get (action,
500 "icon-name", &icon_name,
501 "stock-id", &stock_id,
502 "short-label", &short_label,
503 NULL);
504
505 /* This is a workaround to catch named icons. */
506 if (icon_name)
507 icon = gtk_image_new_from_icon_name (icon_name,
508 GTK_ICON_SIZE_LARGE_TOOLBAR);
509 else
510 icon = gtk_image_new_from_icon_name (stock_id ? stock_id : "gtk-dnd",
511 GTK_ICON_SIZE_LARGE_TOOLBAR);
512
513 item_name = g_strdup (name);
514 collate_key = g_utf8_collate_key (short_label, -1);
515 item = editor_create_item (editor, GTK_IMAGE (icon),
516 short_label, drag_action);
517
518 g_free (short_label);
519 g_free (stock_id);
520 g_free (icon_name);
521 }
522
523 g_object_set_data_full (G_OBJECT (item), "egg-collate-key",
524 (gpointer) collate_key, g_free);
525 g_object_set_data_full (G_OBJECT (item), "egg-item-name",
526 (gpointer) item_name, g_free);
527
528 return item;
529 }
530
531 static gint
append_grid(GtkGrid * grid,GList * items,gint y,gint width)532 append_grid (GtkGrid *grid, GList *items, gint y, gint width)
533 {
534 if (items != NULL)
535 {
536 gint x = 0;
537 GtkWidget *item;
538
539 if (y > 0)
540 {
541 item = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
542 gtk_widget_set_hexpand (item, TRUE);
543 gtk_widget_set_vexpand (item, FALSE);
544 gtk_widget_show (item);
545
546 gtk_grid_attach (grid, item, 0, y, width, 1);
547 y++;
548 }
549
550 for (; items != NULL; items = items->next)
551 {
552 item = items->data;
553 gtk_widget_set_hexpand (item, FALSE);
554 gtk_widget_set_vexpand (item, FALSE);
555 gtk_widget_show (item);
556
557 if (x >= width)
558 {
559 x = 0;
560 y++;
561 }
562 gtk_grid_attach (grid, item, x, y, 1, 1);
563 x++;
564 }
565
566 y++;
567 }
568 return y;
569 }
570
571 static void
update_editor_sheet(EggToolbarEditor * editor)572 update_editor_sheet (EggToolbarEditor *editor)
573 {
574 gint y;
575 GPtrArray *items;
576 GList *to_move = NULL, *to_copy = NULL;
577 GtkWidget *grid;
578 GtkWidget *viewport;
579
580 g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
581
582 /* Create new grid. */
583 grid = gtk_grid_new ();
584 editor->priv->grid = grid;
585 gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
586 gtk_grid_set_row_spacing (GTK_GRID (grid), 24);
587 gtk_widget_show (grid);
588 gtk_drag_dest_set (grid, GTK_DEST_DEFAULT_ALL,
589 dest_drag_types, G_N_ELEMENTS (dest_drag_types),
590 GDK_ACTION_MOVE | GDK_ACTION_COPY);
591
592 /* Build two lists of items (one for copying, one for moving). */
593 items = egg_toolbars_model_get_name_avail (editor->priv->model);
594 while (items->len > 0)
595 {
596 GtkWidget *item;
597 const char *name;
598 gint flags;
599
600 name = g_ptr_array_index (items, 0);
601 g_ptr_array_remove_index_fast (items, 0);
602
603 flags = egg_toolbars_model_get_name_flags (editor->priv->model, name);
604 if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0)
605 {
606 item = editor_create_item_from_name (editor, name, GDK_ACTION_MOVE);
607 if (item != NULL)
608 to_move = g_list_insert_sorted (to_move, item, compare_items);
609 }
610 else
611 {
612 item = editor_create_item_from_name (editor, name, GDK_ACTION_COPY);
613 if (item != NULL)
614 to_copy = g_list_insert_sorted (to_copy, item, compare_items);
615 }
616 }
617
618 /* Add them to the sheet. */
619 y = 0;
620 y = append_grid (GTK_GRID (grid), to_move, y, 4);
621 y = append_grid (GTK_GRID (grid), to_copy, y, 4);
622
623 g_list_free (to_move);
624 g_list_free (to_copy);
625 g_ptr_array_free (items, TRUE);
626
627 /* Delete old grid. */
628 viewport = gtk_bin_get_child (GTK_BIN (editor->priv->scrolled_window));
629 if (viewport)
630 {
631 gtk_container_remove (GTK_CONTAINER (viewport),
632 gtk_bin_get_child (GTK_BIN (viewport)));
633 }
634
635 /* Add grid to window. */
636 gtk_scrolled_window_add_with_viewport
637 (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window), grid);
638 }
639
640 static void
setup_editor(EggToolbarEditor * editor)641 setup_editor (EggToolbarEditor *editor)
642 {
643 GtkWidget *scrolled_window;
644
645 gtk_container_set_border_width (GTK_CONTAINER (editor), 12);
646 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
647 editor->priv->scrolled_window = scrolled_window;
648 gtk_widget_show (scrolled_window);
649 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
650 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
651 gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0);
652 }
653
654 static void
egg_toolbar_editor_init(EggToolbarEditor * t)655 egg_toolbar_editor_init (EggToolbarEditor *t)
656 {
657 gtk_orientable_set_orientation (GTK_ORIENTABLE (t), GTK_ORIENTATION_VERTICAL);
658
659 t->priv = egg_toolbar_editor_get_instance_private (t);
660
661 t->priv->manager = NULL;
662 t->priv->actions_list = NULL;
663
664 setup_editor (t);
665 }
666
667