1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
4 * Copyright (C) Naba Kumar <naba@gnome.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 * @short_description: User Interface manager
24 * @see_also: #GtkAction, #GtkActionEntry, #GtkToggleAction,
25 * #GtkToggleActionEntry, #GtkRadioAction, #GtkRadioActionEntry,
26 * #GtkActionGroup, #GtkUIManager
27 * @stability: Unstable
28 * @include: libanjuta/
30 * #AnjutaUI subclasses #GtkUIManager, so you should really read #GtkUIManager
31 * documentation first to know about Actions, UI merging and UI XML file
32 * format. This documentation will cover only the relevent APIs.
34 * #AnjutaUI has its own methods for adding action groups, which is differnt
35 * from #GtkUIManager methods. All #AnjutaPlugin based classes should use
36 * these methods instead of #GtkUIManager methods. The reason is, in addition
37 * to adding the actions and groups to the UI manager, it also resgisters
38 * them for UI customization and accellerators editing. It also keeps
39 * record of all actions.
41 * An interesting side effect of this is that these
42 * actions could be conveniently accessed or activated with
43 * anjuta_ui_get_action() or anjuta_ui_activate_action_by_path(), without
44 * the need of original action group object. This makes it is possible for
45 * activating actions remotely from other plugins.
47 * anjuta_ui_get_accel_editor() will return a widget containing the
48 * UI customization and accellerators editor. All actions and action groups
49 * are organized into a tree view, which should be added to a visible
50 * container (e.g. a #GtkDialog based object) and displayed to users.
54 * Any actions additions/removals using #GtkUIManager are not
55 * registred with #AnjutaUI and hence their accellerators
56 * cannot be edited. Nor will they be listed in UI manager
57 * dialog. Hence, use #AnjutaUI methods whenever possible.
68 #include <glib/gi18n.h>
71 #include "resources.h"
72 #include "anjuta-ui.h"
73 #include "anjuta-utils.h"
74 #include "anjuta-debug.h"
76 struct _AnjutaUIPrivate
{
77 GtkIconFactory
*icon_factory
;
78 GtkTreeModel
*name_model
;
79 GtkTreeModel
*accel_model
;
80 GHashTable
*customizable_actions_hash
;
81 GHashTable
*uncustomizable_actions_hash
;
96 get_action_label (GtkAction
*action
)
98 gchar
*action_label
= NULL
;
100 g_object_get (G_OBJECT (action
), "label", &action_label
, NULL
);
101 if (action_label
&& strlen (action_label
))
104 s
= d
= action_label
;
107 /* FIXME: May break with multibyte chars */
115 action_label
= g_strdup (gtk_action_get_name (action
));
119 /* Find action in tree */
121 find_action (GtkTreeModel
*model
, GtkTreeIter
*iter
, GtkAction
*action
)
126 for (valid
= gtk_tree_model_get_iter_first (model
, &group
);
128 valid
= gtk_tree_model_iter_next (model
, &group
))
130 for (valid
= gtk_tree_model_iter_children (model
, iter
, &group
);
132 valid
= gtk_tree_model_iter_next (model
, iter
))
134 GtkAction
*child_action
;
136 gtk_tree_model_get (model
, iter
, COLUMN_ACTION
, &child_action
, -1);
137 g_object_unref (child_action
);
138 if (action
== child_action
) return TRUE
;
145 /* Find group in tree starting after sibling or from beginning if sibling is
148 find_group_after (GtkTreeModel
*model
, GtkTreeIter
*iter
, GtkTreeIter
*sibling
, const gchar
*group_label
)
154 valid
= gtk_tree_model_get_iter_first (model
, iter
);
159 valid
= gtk_tree_model_iter_next (model
, iter
);
166 gtk_tree_model_get (model
, iter
, COLUMN_ACTION_LABEL
, &label
, -1);
167 comp
= strcmp (label
, group_label
);
170 if (comp
== 0) return TRUE
;
172 valid
= gtk_tree_model_iter_next (model
, iter
);
178 /* Find position for new label */
180 find_sorted_name (GtkTreeModel
*model
, GtkTreeIter
*iter
, const gchar
*label
)
189 gtk_tree_model_get (model
, iter
, COLUMN_ACTION_LABEL
, &iter_label
, -1);
191 comp
= g_utf8_collate (label
, iter_label
);
194 if (comp
<= 0) return iter
;
196 valid
= gtk_tree_model_iter_next (model
, iter
);
202 /* Find position for new accel */
204 find_sorted_accel (GtkTreeModel
*model
, GtkTreeIter
*iter
, GtkAction
*action
)
208 const gchar
*accel_path
;
211 accel_path
= gtk_action_get_accel_path (action
);
212 if ((accel_path
== NULL
) ||
213 !gtk_accel_map_lookup_entry (accel_path
, &key
) ||
214 ((key
.accel_key
== 0) && (key
.accel_mods
== 0)))
220 for (valid
= gtk_tree_model_get_iter_first (model
, &group
);
222 valid
= gtk_tree_model_iter_next (model
, &group
))
224 for (valid
= gtk_tree_model_iter_children (model
, iter
, &group
);
226 valid
= gtk_tree_model_iter_next (model
, iter
))
228 GtkAction
*child_action
;
229 GtkAccelKey child_key
;
231 gtk_tree_model_get (model
, iter
, COLUMN_ACTION
, &child_action
, -1);
232 accel_path
= gtk_action_get_accel_path (child_action
);
233 g_object_unref (child_action
);
234 if ((accel_path
== NULL
) ||
235 !gtk_accel_map_lookup_entry (accel_path
, &child_key
) ||
236 ((child_key
.accel_key
== 0) && (child_key
.accel_mods
== 0)))
238 /* No more accelerator and as accelerators are sorted, there is
239 * no need to go further */
243 if ((child_key
.accel_key
> key
.accel_key
) ||
244 ((child_key
.accel_key
== key
.accel_key
) && (child_key
.accel_mods
>= key
.accel_mods
)))
246 /* Find next accelerator */
256 insert_sorted_by_accel (GtkTreeModel
*model
, GtkTreeIter
*iter
, const gchar
*group_label
, GtkAction
*action
)
261 if (find_sorted_accel (model
, &next
, action
))
265 /* Try to set next action in an already existing parent */
266 gtk_tree_model_iter_parent (model
, &parent
, &next
);
267 gtk_tree_model_get (model
, &parent
, COLUMN_ACTION_LABEL
, &label
, -1);
268 if (strcmp (label
, group_label
) == 0)
270 /* Already the right group, just insert action */
271 gtk_tree_store_insert_before (GTK_TREE_STORE (model
), iter
, &parent
, &next
);
275 /* Try to put in the previous group */
279 path
= gtk_tree_model_get_path (model
, &next
);
280 prev
= gtk_tree_path_prev (path
);
281 gtk_tree_path_free (path
);
285 path
= gtk_tree_model_get_path (model
, &parent
);
286 if (gtk_tree_path_prev (path
))
289 gtk_tree_model_get_iter (model
, &parent
, path
);
290 gtk_tree_model_get (model
, &parent
, COLUMN_ACTION_LABEL
, &label
, -1);
291 if (strcmp (label
, group_label
) != 0)
293 /* Create new parent */
295 gtk_tree_store_insert_after (GTK_TREE_STORE (model
), &parent
, NULL
, &next
);
301 gtk_tree_store_insert_before (GTK_TREE_STORE (model
), &parent
, NULL
, &next
);
303 gtk_tree_path_free (path
);
304 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
305 COLUMN_ACTION_LABEL
, group_label
,
306 COLUMN_SHOW_VISIBLE
, FALSE
,
309 /* Add action at the end */
310 gtk_tree_store_append (GTK_TREE_STORE (model
), iter
, &parent
);
314 /* Split parent and add new parent in the middle */
318 gtk_tree_store_insert_after (GTK_TREE_STORE (model
), &split
, NULL
, &parent
);
319 gtk_tree_store_set (GTK_TREE_STORE (model
), &split
,
320 COLUMN_ACTION_LABEL
, label
,
321 COLUMN_SHOW_VISIBLE
, FALSE
,
332 gpointer action_group
;
334 gtk_tree_model_get (model
, &next
,
335 COLUMN_PIXBUF
, &pixbuf
,
336 COLUMN_ACTION_LABEL
, &action_label
,
337 COLUMN_VISIBLE
, &visible
,
338 COLUMN_SENSITIVE
, &sensitive
,
339 COLUMN_ACTION
, &action
,
340 COLUMN_GROUP
, &action_group
,
342 gtk_tree_store_append (GTK_TREE_STORE (model
), &child
, &split
);
343 gtk_tree_store_set (GTK_TREE_STORE (model
), &child
,
344 COLUMN_PIXBUF
, pixbuf
,
345 COLUMN_ACTION_LABEL
, action_label
,
346 COLUMN_VISIBLE
, visible
,
347 COLUMN_SHOW_VISIBLE
, TRUE
,
348 COLUMN_SENSITIVE
, sensitive
,
349 COLUMN_ACTION
, action
,
350 COLUMN_GROUP
, action_group
,
352 if (pixbuf
) g_object_unref (pixbuf
);
353 if (action
) g_object_unref (action
);
354 g_free (action_label
);
356 valid
= gtk_tree_store_remove (GTK_TREE_STORE (model
), &next
);
360 gtk_tree_store_insert_before (GTK_TREE_STORE (model
), &parent
, NULL
, &split
);
361 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
362 COLUMN_ACTION_LABEL
, group_label
,
363 COLUMN_SHOW_VISIBLE
, FALSE
,
366 gtk_tree_store_append (GTK_TREE_STORE (model
), iter
, &parent
);
373 if (find_group_after (model
, &parent
, NULL
, group_label
))
376 GtkAction
*child_action
;
377 const gchar
*accel_path
;
380 /* Find last group */
381 while (find_group_after (model
, &next
, &parent
, group_label
))
386 gtk_tree_model_iter_children (model
, &child
, &parent
);
387 gtk_tree_model_get (model
, &child
, COLUMN_ACTION
, &child_action
, -1);
389 accel_path
= gtk_action_get_accel_path (child_action
);
390 g_object_unref (child_action
);
391 if ((accel_path
!= NULL
) &&
392 gtk_accel_map_lookup_entry (accel_path
, &key
) &&
393 ((key
.accel_key
!= 0) || (key
.accel_mods
!= 0)))
395 /* Create new group */
396 gtk_tree_store_append (GTK_TREE_STORE (model
), &parent
, NULL
);
397 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
398 COLUMN_ACTION_LABEL
, group_label
,
399 COLUMN_SHOW_VISIBLE
, FALSE
,
406 /* Create new group */
407 gtk_tree_store_append (GTK_TREE_STORE (model
), &parent
, NULL
);
408 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
409 COLUMN_ACTION_LABEL
, group_label
,
410 COLUMN_SHOW_VISIBLE
, FALSE
,
415 gtk_tree_store_append (GTK_TREE_STORE (model
), iter
, &parent
);
420 insert_sorted_by_name (GtkTreeModel
*model
, GtkTreeIter
*iter
, const gchar
*group_label
, GtkAction
*action
)
424 GtkTreeIter
*sibling
;
427 if (!find_group_after (model
, &parent
, NULL
, group_label
))
429 /* Insert group for label */
430 if (gtk_tree_model_get_iter_first (model
, &child
))
432 sibling
= find_sorted_name (model
, &child
, group_label
);
438 gtk_tree_store_insert_before (GTK_TREE_STORE (model
), &parent
, NULL
, sibling
);
439 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
440 COLUMN_ACTION_LABEL
, group_label
,
441 COLUMN_SHOW_VISIBLE
, FALSE
,
445 if (gtk_tree_model_iter_children (model
, &child
, &parent
))
447 label
= get_action_label (action
);
448 sibling
= find_sorted_name (model
, &child
, label
);
456 gtk_tree_store_insert_before (GTK_TREE_STORE (model
), iter
, &parent
, sibling
);
460 fill_action_data (GtkTreeModel
*model
, GtkTreeIter
*iter
, GtkAction
*action
, GtkActionGroup
*group
)
464 GdkPixbuf
*pixbuf
= NULL
;
465 GtkWidget
*dummy
= NULL
;
467 action_label
= get_action_label (action
);
468 g_object_get (G_OBJECT (action
), "stock-id", &icon
, NULL
);
471 GtkWidget
*dummy
= gtk_label_new ("Dummy");
472 g_object_ref_sink(G_OBJECT(dummy
));
473 pixbuf
= gtk_widget_render_icon_pixbuf (dummy
, icon
, GTK_ICON_SIZE_MENU
);
475 gtk_tree_store_set (GTK_TREE_STORE (model
), iter
,
476 COLUMN_PIXBUF
, pixbuf
,
477 COLUMN_ACTION_LABEL
, action_label
,
478 COLUMN_VISIBLE
, gtk_action_get_visible (action
),
479 COLUMN_SHOW_VISIBLE
, TRUE
,
480 COLUMN_SENSITIVE
, gtk_action_get_sensitive (action
),
481 COLUMN_ACTION
, action
,
484 if (pixbuf
!= NULL
) g_object_unref (G_OBJECT (pixbuf
));
485 if (dummy
!= NULL
) g_object_unref (dummy
);
487 g_free (action_label
);
490 /* Remove all actions in the action group group */
492 remove_action_in_group (GtkTreeModel
*model
, GtkActionGroup
*group
)
497 valid
= gtk_tree_model_get_iter_first (model
, &parent
);
500 /* Check each action, as a parent can contains actions from different
504 valid
= gtk_tree_model_iter_children (model
, &child
, &parent
);
507 gpointer child_group
;
509 gtk_tree_model_get (model
, &child
, COLUMN_GROUP
, &child_group
, -1);
511 if (child_group
== (gpointer
)group
)
513 valid
= gtk_tree_store_remove (GTK_TREE_STORE (model
), &child
);
517 valid
= gtk_tree_model_iter_next (model
, &child
);
521 /* if parent is now empty remove it */
522 if (!gtk_tree_model_iter_has_child (model
, &parent
))
524 valid
= gtk_tree_store_remove (GTK_TREE_STORE (model
), &parent
);
528 valid
= gtk_tree_model_iter_next (model
, &parent
);
535 sensitivity_toggled (GtkCellRendererToggle
*cell
,
536 const gchar
*path_str
, GtkTreeView
*tree_view
)
539 GtkTreeModel
*other_model
;
545 model
= gtk_tree_view_get_model (tree_view
);
546 path
= gtk_tree_path_new_from_string (path_str
);
547 gtk_tree_model_get_iter (model
, &iter
, path
);
548 gtk_tree_path_free (path
);
550 gtk_tree_model_get (model
, &iter
,
551 COLUMN_SENSITIVE
, &sensitive
,
552 COLUMN_ACTION
, &action
, -1);
553 g_object_set (G_OBJECT (action
), "sensitive", !sensitive
, NULL
);
554 g_object_unref (action
);
556 gtk_tree_store_set (GTK_TREE_STORE (model
), &iter
,
557 COLUMN_SENSITIVE
, !sensitive
, -1);
559 /* Update other model */
560 other_model
= g_object_get_data (G_OBJECT (tree_view
), "other_model");
561 if (find_action (other_model
, &iter
, action
))
563 gtk_tree_store_set (GTK_TREE_STORE (other_model
), &iter
,
564 COLUMN_SENSITIVE
, !sensitive
, -1);
570 visibility_toggled (GtkCellRendererToggle
*cell
,
571 const gchar
*path_str
, GtkTreeView
*tree_view
)
574 GtkTreeModel
*other_model
;
580 model
= gtk_tree_view_get_model (tree_view
);
581 path
= gtk_tree_path_new_from_string (path_str
);
582 gtk_tree_model_get_iter (model
, &iter
, path
);
583 gtk_tree_path_free (path
);
585 gtk_tree_model_get (model
, &iter
,
586 COLUMN_VISIBLE
, &visible
,
587 COLUMN_ACTION
, &action
, -1);
588 g_object_set (G_OBJECT (action
), "visible", !visible
, NULL
);
589 g_object_unref (action
);
590 gtk_tree_store_set (GTK_TREE_STORE (model
), &iter
,
591 COLUMN_VISIBLE
, !visible
, -1);
593 /* Update other model */
594 other_model
= g_object_get_data (G_OBJECT (tree_view
), "other_model");
595 if (find_action (other_model
, &iter
, action
))
597 gtk_tree_store_set (GTK_TREE_STORE (other_model
), &iter
,
598 COLUMN_VISIBLE
, !visible
, -1);
603 accel_edited_callback (GtkCellRendererAccel
*cell
,
604 const char *path_string
,
606 GdkModifierType mask
,
607 guint hardware_keycode
,
608 GtkTreeView
*tree_view
)
614 const gchar
*accel_path
;
616 model
= gtk_tree_view_get_model (tree_view
);
617 path
= gtk_tree_path_new_from_string (path_string
);
618 gtk_tree_model_get_iter (model
, &iter
, path
);
619 gtk_tree_path_free (path
);
621 gtk_tree_model_get (model
, &iter
,
622 COLUMN_ACTION
, &action
, -1);
628 accel_path
= gtk_action_get_accel_path (action
);
629 g_object_unref (action
);
631 gtk_accel_map_change_entry (accel_path
, keyval
, mask
, TRUE
);
636 accel_cleared_callback (GtkCellRendererAccel
*cell
,
637 const char *path_string
,
638 GtkTreeView
*tree_view
)
644 const gchar
*accel_path
;
647 model
= gtk_tree_view_get_model (tree_view
);
648 path
= gtk_tree_path_new_from_string (path_string
);
649 gtk_tree_model_get_iter (model
, &iter
, path
);
650 gtk_tree_path_free (path
);
652 gtk_tree_model_get (model
, &iter
,
653 COLUMN_ACTION
, &action
, -1);
659 accel_path
= gtk_action_get_accel_path (action
);
660 g_object_unref (action
);
662 gtk_accel_map_change_entry (accel_path
, 0, 0, TRUE
);
666 /* Fill the model sorted by accelerator with data from default model sorted by
669 fill_sort_by_accel_store (GtkTreeStore
*store
, GtkTreeModel
*model
)
674 for (valid
= gtk_tree_model_get_iter_first (model
, &group
);
676 valid
= gtk_tree_model_iter_next (model
, &group
))
681 gtk_tree_model_get (model
, &group
,
682 COLUMN_ACTION_LABEL
, &group_label
,
685 for (valid
= gtk_tree_model_iter_children (model
, &iter
, &group
);
687 valid
= gtk_tree_model_iter_next (model
, &iter
))
695 gpointer action_group
;
697 gtk_tree_model_get (model
, &iter
,
698 COLUMN_PIXBUF
, &pixbuf
,
699 COLUMN_ACTION_LABEL
, &action_label
,
700 COLUMN_VISIBLE
, &visible
,
701 COLUMN_SENSITIVE
, &sensitive
,
702 COLUMN_ACTION
, &action
,
703 COLUMN_GROUP
, &action_group
,
708 insert_sorted_by_accel (GTK_TREE_MODEL (store
), &child
, group_label
, action
);
710 gtk_tree_store_set (store
, &child
,
711 COLUMN_PIXBUF
, pixbuf
,
712 COLUMN_ACTION_LABEL
, action_label
,
713 COLUMN_VISIBLE
, visible
,
714 COLUMN_SHOW_VISIBLE
, TRUE
,
715 COLUMN_SENSITIVE
, sensitive
,
716 COLUMN_ACTION
, action
,
717 COLUMN_GROUP
, action_group
,
722 if (pixbuf
!= NULL
) g_object_unref (pixbuf
);
723 g_free (action_label
);
724 if (action
!= NULL
) g_object_unref (action
);
726 g_free (group_label
);
730 /* Switch to the model sorted by name if use_name is true or sorted by
731 * accelerator if use_name is false */
733 change_tree_model (GtkTreeView
*tree_view
, gboolean use_name
)
737 has_name
= GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_view
), "name_model"));
738 if (has_name
!= use_name
)
741 GtkTreeModel
*other_model
;
744 GList
*expanded
= NULL
;
745 GtkAction
*selected_action
= NULL
;
746 gchar
*selected_group
= NULL
;
747 GtkTreeSelection
*selection
;
751 model
= gtk_tree_view_get_model (tree_view
);
752 selection
= gtk_tree_view_get_selection (tree_view
);
753 if (gtk_tree_selection_get_selected (selection
, NULL
, &iter
))
755 gtk_tree_model_get (model
, &iter
,
756 COLUMN_ACTION_LABEL
, &selected_group
,
757 COLUMN_ACTION
, &selected_action
,
761 /* Save expanded groups */
762 for (path
= gtk_tree_path_new_first ();
763 gtk_tree_model_get_iter (model
, &iter
, path
);
764 gtk_tree_path_next (path
))
766 if (gtk_tree_view_row_expanded (tree_view
, path
))
770 gtk_tree_model_get (model
, &iter
, COLUMN_ACTION_LABEL
, &label
, -1);
771 expanded
= g_list_prepend (expanded
, label
);
772 g_message ("expanded %s", label
);
775 gtk_tree_path_free (path
);
778 other_model
= g_object_get_data (G_OBJECT (tree_view
), "other_model");
780 if (!gtk_tree_model_get_iter_first (other_model
, &iter
) && !use_name
)
782 /* Accelerator model is empty fill it */
783 fill_sort_by_accel_store (GTK_TREE_STORE (other_model
), model
);
786 gtk_tree_view_set_model (tree_view
, other_model
);
787 g_object_set_data (G_OBJECT (tree_view
), "other_model", model
);
788 g_object_set_data (G_OBJECT (tree_view
), "name_model", GINT_TO_POINTER (use_name
));
790 /* Expand same group */
791 for (item
= g_list_first (expanded
); item
!= NULL
; item
= g_list_next (item
))
793 gchar
*label
= (gchar
*)item
->data
;
796 for (valid
= find_group_after (other_model
, &iter
, NULL
, label
);
798 valid
= find_group_after (other_model
, &iter
, &iter
, label
))
800 path
= gtk_tree_model_get_path (other_model
, &iter
);
801 gtk_tree_view_expand_row (tree_view
, path
, FALSE
);
802 gtk_tree_path_free (path
);
806 g_list_free (expanded
);
808 /* Restore selection */
809 if (selected_action
!= NULL
)
811 find_action (other_model
, &iter
, selected_action
);
812 gtk_tree_selection_select_iter (selection
, &iter
);
813 g_object_unref (selected_action
);
815 else if (selected_group
!= NULL
)
817 find_group_after (other_model
, &iter
, NULL
, selected_group
);
818 gtk_tree_selection_select_iter (selection
, &iter
);
820 /* Display selected row */
821 if ((selected_action
!= NULL
) || (selected_group
!= NULL
))
825 path
= gtk_tree_model_get_path (other_model
, &iter
);
826 gtk_tree_view_scroll_to_cell (tree_view
, path
, NULL
, TRUE
, 0.5, 0);
827 gtk_tree_path_free (path
);
829 g_free (selected_group
);
834 accel_sort_by_accel_callback (GtkTreeViewColumn
*column
,
837 GtkTreeView
*tree_view
= (GtkTreeView
*)data
;
839 change_tree_model (tree_view
, FALSE
);
843 accel_sort_by_name_callback (GtkTreeViewColumn
*column
,
846 GtkTreeView
*tree_view
= (GtkTreeView
*)data
;
848 change_tree_model (tree_view
, TRUE
);
852 accel_set_func (GtkTreeViewColumn
*tree_column
,
853 GtkCellRenderer
*cell
,
859 const gchar
*accel_path
;
862 gtk_tree_model_get (model
, iter
,
863 COLUMN_ACTION
, &action
, -1);
865 g_object_set (G_OBJECT (cell
), "visible", FALSE
, NULL
);
868 if ((accel_path
= gtk_action_get_accel_path (action
)))
870 if (gtk_accel_map_lookup_entry (accel_path
, &key
))
872 g_object_set (G_OBJECT (cell
), "visible", TRUE
,
873 "accel-key", key
.accel_key
,
874 "accel-mods", key
.accel_mods
, NULL
);
877 g_object_set (G_OBJECT (cell
), "visible", TRUE
,
879 "accel-mods", 0, NULL
);
881 g_object_unref (action
);
885 G_DEFINE_TYPE(AnjutaUI
, anjuta_ui
, GTK_TYPE_UI_MANAGER
)
888 anjuta_ui_dispose (GObject
*obj
)
890 AnjutaUI
*ui
= ANJUTA_UI (obj
);
892 if (ui
->priv
->name_model
) {
893 /* This will also release the refs on actions.
894 * Clear is necessary because following unref() might not actually
895 * finalize the model. It basically ensures all refs on actions
896 * are released irrespective of whether the model is finalized
899 gtk_tree_store_clear (GTK_TREE_STORE (ui
->priv
->name_model
));
901 g_object_unref (G_OBJECT (ui
->priv
->name_model
));
902 ui
->priv
->name_model
= NULL
;
904 if (ui
->priv
->accel_model
) {
905 gtk_tree_store_clear (GTK_TREE_STORE (ui
->priv
->accel_model
));
906 g_object_unref (G_OBJECT (ui
->priv
->accel_model
));
907 ui
->priv
->accel_model
= NULL
;
909 if (ui
->priv
->customizable_actions_hash
)
911 /* This will also release the refs on all action groups */
912 g_hash_table_destroy (ui
->priv
->customizable_actions_hash
);
913 ui
->priv
->customizable_actions_hash
= NULL
;
915 if (ui
->priv
->uncustomizable_actions_hash
)
917 /* This will also release the refs on all action groups */
918 g_hash_table_destroy (ui
->priv
->uncustomizable_actions_hash
);
919 ui
->priv
->uncustomizable_actions_hash
= NULL
;
921 if (ui
->priv
->icon_factory
) {
922 g_object_unref (G_OBJECT (ui
->priv
->icon_factory
));
923 ui
->priv
->icon_factory
= NULL
;
925 G_OBJECT_CLASS (anjuta_ui_parent_class
)->dispose (obj
);
929 anjuta_ui_finalize (GObject
*obj
)
931 AnjutaUI
*ui
= ANJUTA_UI (obj
);
933 G_OBJECT_CLASS (anjuta_ui_parent_class
)->finalize (obj
);
937 anjuta_ui_class_init (AnjutaUIClass
*class)
939 GObjectClass
*object_class
= G_OBJECT_CLASS (class);
941 object_class
->dispose
= anjuta_ui_dispose
;
942 object_class
->finalize
= anjuta_ui_finalize
;
946 anjuta_ui_init (AnjutaUI
*ui
)
950 /* Initialize member data */
951 ui
->priv
= g_new0 (AnjutaUIPrivate
, 1);
952 ui
->priv
->customizable_actions_hash
=
953 g_hash_table_new_full (g_str_hash
,
955 (GDestroyNotify
) g_free
,
957 ui
->priv
->uncustomizable_actions_hash
=
958 g_hash_table_new_full (g_str_hash
,
960 (GDestroyNotify
) g_free
,
962 /* Create Icon factory */
963 ui
->priv
->icon_factory
= gtk_icon_factory_new ();
964 gtk_icon_factory_add_default (ui
->priv
->icon_factory
);
966 /* Create Accel editor sorted by name model */
967 store
= gtk_tree_store_new (N_COLUMNS
,
976 /* unreferenced in dispose() method. */
977 ui
->priv
->name_model
= GTK_TREE_MODEL (store
);
979 /* Create Accel editor sorted by accelerator model */
980 store
= gtk_tree_store_new (N_COLUMNS
,
989 /* unreferenced in dispose() method. */
990 ui
->priv
->accel_model
= GTK_TREE_MODEL (store
);
996 * Creates a new instance of #AnjutaUI.
998 * Return value: A #AnjutaUI object
1001 anjuta_ui_new (void)
1003 return g_object_new (ANJUTA_TYPE_UI
, NULL
);
1007 * anjuta_ui_add_action_group_entries:
1008 * @ui: A #AnjutaUI object.
1009 * @action_group_name: Untranslated name of the action group.
1010 * @action_group_label: Translated label of the action group.
1011 * @entries: (array length=num_entries): An array of action entries.
1012 * @num_entries: Number of elements in the action entries array.
1013 * @can_customize: If true the actions are customizable by user.
1014 * @translation_domain: The translation domain used to translated the entries.
1015 * It is usually the GETTEXT_PACKAGE macro in a project.
1016 * @user_data: User data to pass to action objects. This is the data that
1017 * will come as user_data in "activate" signal of the actions.
1019 * #GtkAction objects are created from the #GtkActionEntry structures and
1020 * added to the UI Manager. "activate" signal of #GtkAction is connected for
1021 * all the action objects using the callback in the entry structure and the
1022 * @user_data passed here.
1024 * This group of actions are registered with the name @action_group_name
1025 * in #AnjutaUI. A #GtkAction object from this action group can be later
1026 * retrieved by anjuta_ui_get_action() using @action_group_name and action name.
1027 * @action_group_label is used as the display name for the action group in
1028 * UI manager dialog where action shortcuts are configured.
1030 * Return value: (transfer none): A #GtkActionGroup object holding all the
1034 anjuta_ui_add_action_group_entries (AnjutaUI
*ui
,
1035 const gchar
*action_group_name
,
1036 const gchar
*action_group_label
,
1037 GtkActionEntry
*entries
,
1039 const gchar
*translation_domain
,
1040 gboolean can_customize
,
1043 GtkActionGroup
*action_group
;
1045 g_return_val_if_fail (ANJUTA_IS_UI (ui
), NULL
);
1046 g_return_val_if_fail (action_group_name
!= NULL
, NULL
);
1047 g_return_val_if_fail (action_group_name
!= NULL
, NULL
);
1049 action_group
= gtk_action_group_new (action_group_name
);
1051 gtk_action_group_set_translation_domain (action_group
, translation_domain
);
1052 gtk_action_group_add_actions (action_group
, entries
, num_entries
,
1054 anjuta_ui_add_action_group (ui
, action_group_name
,
1055 action_group_label
, action_group
,
1057 return action_group
;
1061 * anjuta_ui_add_toggle_action_group_entries:
1062 * @ui: A #AnjutaUI object.
1063 * @action_group_name: Untranslated name of the action group.
1064 * @action_group_label: Translated label of the action group.
1065 * @entries: (array length=num_entries): An array of action entries.
1066 * @num_entries: Number of elements in the action entries array.
1067 * @translation_domain: The translation domain used to translated the entries.
1068 * It is usually the GETTEXT_PACKAGE macro in a project.
1069 * @user_data: User data to pass to action objects. This is the data that
1070 * will come as user_data in "activate" signal of the actions.
1072 * This is similar to anjuta_ui_add_action_group_entries(), except that
1073 * it adds #GtkToggleAction objects after creating them from the @entries.
1075 * Return value: (transfer none): A #GtkActionGroup object holding all the
1079 anjuta_ui_add_toggle_action_group_entries (AnjutaUI
*ui
,
1080 const gchar
*action_group_name
,
1081 const gchar
*action_group_label
,
1082 GtkToggleActionEntry
*entries
,
1084 const gchar
*translation_domain
,
1085 gboolean can_customize
,
1088 GtkActionGroup
*action_group
;
1090 g_return_val_if_fail (ANJUTA_IS_UI (ui
), NULL
);
1091 g_return_val_if_fail (action_group_name
!= NULL
, NULL
);
1092 g_return_val_if_fail (action_group_name
!= NULL
, NULL
);
1094 action_group
= gtk_action_group_new (action_group_name
);
1095 gtk_action_group_set_translation_domain (action_group
, translation_domain
);
1096 gtk_action_group_add_toggle_actions (action_group
, entries
, num_entries
,
1098 anjuta_ui_add_action_group (ui
, action_group_name
,
1099 action_group_label
, action_group
,
1101 return action_group
;
1105 * anjuta_ui_add_action_group:
1106 * @ui: A #AnjutaUI object.
1107 * @action_group_name: Untranslated name of the action group.
1108 * @action_group_label: Translated label of the action group.
1109 * @action_group: (transfer full): #GtkActionGroup object to add.
1111 * This is similar to anjuta_ui_add_action_group_entries(), except that
1112 * it adds #GtkActionGroup object @action_group directly. All actions in this
1113 * group are automatically registered in #AnjutaUI and can be retrieved
1114 * normally with anjuta_ui_get_action().
1117 anjuta_ui_add_action_group (AnjutaUI
*ui
,
1118 const gchar
*action_group_name
,
1119 const gchar
*action_group_label
,
1120 GtkActionGroup
*action_group
,
1121 gboolean can_customize
)
1125 g_return_if_fail (ANJUTA_IS_UI (ui
));
1126 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group
));
1127 g_return_if_fail (action_group_name
!= NULL
);
1128 g_return_if_fail (action_group_name
!= NULL
);
1130 gtk_ui_manager_insert_action_group (GTK_UI_MANAGER (ui
), action_group
, 0);
1134 g_hash_table_insert (ui
->priv
->customizable_actions_hash
,
1135 g_strdup (action_group_name
), action_group
);
1139 g_hash_table_insert (ui
->priv
->uncustomizable_actions_hash
,
1140 g_strdup (action_group_name
), action_group
);
1143 actions
= gtk_action_group_list_actions (action_group
);
1144 for (l
= actions
; l
; l
= l
->next
)
1149 GtkAction
*action
= l
->data
;
1154 signal_id
= g_signal_lookup ("activate", GTK_TYPE_ACTION
);
1155 n_handlers
= g_signal_has_handler_pending (action
, signal_id
,
1157 if (n_handlers
== 0)
1158 continue; /* The action element is not user configuration */
1160 insert_sorted_by_name (ui
->priv
->name_model
, &iter
, action_group_label
, action
);
1161 fill_action_data (ui
->priv
->name_model
, &iter
, action
, action_group
);
1163 if (gtk_tree_model_get_iter_first (ui
->priv
->accel_model
, &iter
))
1165 /* accel model is filled only if needed */
1166 insert_sorted_by_accel (ui
->priv
->accel_model
, &iter
, action_group_label
, action
);
1167 fill_action_data (ui
->priv
->accel_model
, &iter
, action
, action_group
);
1170 g_list_free(actions
);
1174 on_action_group_remove_hash (gpointer key
, gpointer value
, gpointer data
)
1183 * anjuta_ui_remove_action_group:
1184 * @ui: A #AnjutaUI object
1185 * @action_group: #GtkActionGroup object to remove.
1187 * Removes a previous added action group. All actions in this group are
1188 * also unregistered from UI manager.
1191 anjuta_ui_remove_action_group (AnjutaUI
*ui
, GtkActionGroup
*action_group
)
1193 g_return_if_fail (ANJUTA_IS_UI (ui
));
1195 remove_action_in_group (ui
->priv
->name_model
, action_group
);
1196 remove_action_in_group (ui
->priv
->accel_model
, action_group
);
1198 gtk_ui_manager_remove_action_group (GTK_UI_MANAGER (ui
), action_group
);
1200 g_hash_table_foreach_remove (ui
->priv
->customizable_actions_hash
,
1201 on_action_group_remove_hash
, action_group
);
1202 g_hash_table_foreach_remove (ui
->priv
->uncustomizable_actions_hash
,
1203 on_action_group_remove_hash
, action_group
);
1207 * anjuta_ui_get_action:
1208 * @ui: This #AnjutaUI object
1209 * @action_group_name: Group name.
1210 * @action_name: Action name.
1212 * Returns the action object with the name @action_name in @action_group_name.
1213 * Note that it will be only sucessully returned if the group has been added
1214 * using methods in #AnjutaUI.
1216 * Returns: (transfer none): A #GtkAction object
1219 anjuta_ui_get_action (AnjutaUI
*ui
, const gchar
*action_group_name
,
1220 const gchar
*action_name
)
1222 GtkActionGroup
*action_group
;
1225 g_return_val_if_fail (ANJUTA_IS_UI (ui
), NULL
);
1227 action_group
= g_hash_table_lookup (ui
->priv
->customizable_actions_hash
,
1231 action_group
= g_hash_table_lookup (ui
->priv
->uncustomizable_actions_hash
,
1234 if (GTK_IS_ACTION_GROUP (action_group
) == FALSE
)
1236 g_warning ("Unable to find action group \"%s\"", action_group_name
);
1239 action
= gtk_action_group_get_action (action_group
, action_name
);
1240 if (GTK_IS_ACTION (action
))
1242 g_warning ("Unable to find action \"%s\" in group \"%s\"",
1243 action_name
, action_group_name
);
1248 * anjuta_ui_activate_action_by_path:
1249 * @ui: This #AnjutaUI object
1250 * @action_path: Path of the action in the form "GroupName/ActionName"
1252 * Activates the action represented by @action_path. The path is in the form
1253 * "ActionGroupName/ActionName". Note that it will only work if the group has
1254 * been added using methods in #AnjutaUI.
1257 anjuta_ui_activate_action_by_path (AnjutaUI
*ui
, const gchar
*action_path
)
1259 const gchar
*action_group_name
;
1260 const gchar
*action_name
;
1264 g_return_if_fail (ANJUTA_IS_UI (ui
));
1265 g_return_if_fail (action_path
!= NULL
);
1267 strv
= g_strsplit (action_path
, "/", 2);
1268 action_group_name
= strv
[0];
1269 action_name
= strv
[1];
1271 g_return_if_fail (action_group_name
!= NULL
&& action_name
!= NULL
);
1273 action
= anjuta_ui_get_action (ui
, action_group_name
, action_name
);
1275 gtk_action_activate (action
);
1280 * anjuta_ui_activate_action_by_group:
1281 * @ui: This #AnjutaUI object
1282 * @action_group: Action group.
1283 * @action_name: Action name.
1285 * Activates the action @action_name in the #GtkActionGroup @action_group.
1286 * "ActionGroupName/ActionName". Note that it will only work if the group has
1287 * been added using methods in #AnjutaUI.
1290 anjuta_ui_activate_action_by_group (AnjutaUI
*ui
, GtkActionGroup
*action_group
,
1291 const gchar
*action_name
)
1295 g_return_if_fail (ANJUTA_IS_UI (ui
));
1296 g_return_if_fail (action_group
!= NULL
&& action_name
!= NULL
);
1298 action
= gtk_action_group_get_action (action_group
, action_name
);
1299 if (GTK_IS_ACTION (action
))
1300 gtk_action_activate (action
);
1305 * @ui: A #AnjutaUI object.
1306 * @ui_filename: UI file to merge into UI manager.
1308 * Merges XML UI definition in @ui_filename. UI elements defined in the xml
1309 * are merged with existing UI elements in UI manager. The format of the
1310 * file content is the standard XML UI definition tree. For more detail,
1311 * read the documentation for #GtkUIManager.
1313 * Return value: Integer merge ID
1316 anjuta_ui_merge (AnjutaUI
*ui
, const gchar
*ui_filename
)
1321 g_return_val_if_fail (ANJUTA_IS_UI (ui
), -1);
1322 g_return_val_if_fail (ui_filename
!= NULL
, -1);
1323 id
= gtk_ui_manager_add_ui_from_file(GTK_UI_MANAGER (ui
),
1327 gchar
*basename
= g_path_get_basename (ui_filename
);
1328 DEBUG_PRINT ("merged [%d] %s", id
, basename
);
1333 g_warning ("Could not merge [%s]: %s", ui_filename
, err
->message
);
1338 * anjuta_ui_unmerge:
1339 * @ui: A #AnjutaUI object.
1340 * @id: Merge ID returned by anjuta_ui_merge().
1342 * Unmerges UI with the ID value @id (returned by anjuta_ui_merge() when
1343 * it was merged. For more detail, read the documentation for #GtkUIManager.
1346 anjuta_ui_unmerge (AnjutaUI
*ui
, gint id
)
1348 /* DEBUG_PRINT ("Menu unmerging %d", id); */
1349 g_return_if_fail (ANJUTA_IS_UI (ui
));
1350 gtk_ui_manager_remove_ui(GTK_UI_MANAGER (ui
), id
);
1354 * anjuta_ui_get_accel_group:
1355 * @ui: A #AnjutaUI object.
1357 * Returns the #GtkAccelGroup object associated with this UI manager.
1359 * Returns: (transfer none): A #GtkAccelGroup object.
1362 anjuta_ui_get_accel_group (AnjutaUI
*ui
)
1364 g_return_val_if_fail (ANJUTA_IS_UI (ui
), NULL
);
1365 return gtk_ui_manager_get_accel_group (GTK_UI_MANAGER (ui
));
1369 * anjuta_ui_get_accel_editor:
1370 * @ui: A #AnjutaUI object.
1372 * Creates an accel editor widget and returns it. It should be added to
1373 * container and displayed to users.
1375 * Returns: (transfer none): a #GtkWidget containing the editor.
1378 anjuta_ui_get_accel_editor (AnjutaUI
*ui
)
1380 GtkWidget
*tree_view
, *sw
;
1381 GtkTreeStore
*store
;
1382 GtkTreeViewColumn
*column
;
1383 GtkCellRenderer
*renderer
;
1385 store
= GTK_TREE_STORE (ui
->priv
->name_model
);
1387 tree_view
= gtk_tree_view_new_with_model (GTK_TREE_MODEL (store
));
1388 g_object_set_data (G_OBJECT (tree_view
), "other_model", ui
->priv
->accel_model
);
1389 g_object_set_data (G_OBJECT (tree_view
), "name_model", GINT_TO_POINTER (TRUE
));
1390 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree_view
), TRUE
);
1393 column
= gtk_tree_view_column_new ();
1394 gtk_tree_view_column_set_title (column
, dgettext (GETTEXT_PACKAGE
, "Action"));
1396 renderer
= gtk_cell_renderer_pixbuf_new ();
1397 gtk_tree_view_column_pack_start (column
, renderer
, FALSE
);
1398 gtk_tree_view_column_add_attribute (column
, renderer
, "pixbuf",
1401 renderer
= gtk_cell_renderer_text_new ();
1402 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1403 gtk_tree_view_column_add_attribute (column
, renderer
, "text",
1404 COLUMN_ACTION_LABEL
);
1405 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view
), column
);
1406 gtk_tree_view_set_expander_column (GTK_TREE_VIEW (tree_view
), column
);
1407 gtk_tree_view_column_set_clickable (column
, TRUE
);
1408 g_signal_connect (G_OBJECT (column
), "clicked",
1409 G_CALLBACK (accel_sort_by_name_callback
),
1412 renderer
= gtk_cell_renderer_toggle_new ();
1413 g_signal_connect (G_OBJECT (renderer
), "toggled",
1414 G_CALLBACK (visibility_toggled
), tree_view
);
1415 column
= gtk_tree_view_column_new_with_attributes (dgettext (GETTEXT_PACKAGE
, "Visible"),
1420 COLUMN_SHOW_VISIBLE
,
1422 gtk_tree_view_column_set_alignment (column
, 0.5f
);
1423 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view
), column
);
1425 renderer
= gtk_cell_renderer_toggle_new ();
1426 g_signal_connect (G_OBJECT (renderer
), "toggled",
1427 G_CALLBACK (sensitivity_toggled
), tree_view
);
1428 column
= gtk_tree_view_column_new_with_attributes (_("Sensitive"),
1433 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view
), column
);
1435 column
= gtk_tree_view_column_new ();
1436 gtk_tree_view_column_set_title (column
, dgettext (GETTEXT_PACKAGE
, "Shortcut"));
1437 renderer
= g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL
,
1440 g_signal_connect (G_OBJECT (renderer
), "accel-edited",
1441 G_CALLBACK (accel_edited_callback
),
1443 g_signal_connect (G_OBJECT (renderer
), "accel-cleared",
1444 G_CALLBACK (accel_cleared_callback
),
1446 g_object_set (G_OBJECT (renderer
), "editable", TRUE
, NULL
);
1447 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1448 gtk_tree_view_column_set_cell_data_func (column
, renderer
, accel_set_func
, NULL
, NULL
);
1449 gtk_tree_view_column_set_clickable (column
, TRUE
);
1450 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view
), column
);
1451 g_signal_connect (G_OBJECT (column
), "clicked",
1452 G_CALLBACK (accel_sort_by_accel_callback
),
1455 sw
= gtk_scrolled_window_new (NULL
, NULL
);
1456 gtk_container_set_border_width (GTK_CONTAINER (sw
), 6);
1457 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
1458 GTK_POLICY_AUTOMATIC
,
1459 GTK_POLICY_AUTOMATIC
);
1460 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw
),
1462 gtk_container_add (GTK_CONTAINER (sw
), tree_view
);
1463 gtk_widget_show_all (sw
);
1468 * anjuta_ui_get_icon_factory:
1469 * @ui: A #AnjutaUI object
1471 * This returns the IconFactory object. All icons should be registered using
1472 * this icon factory. Read the documentation for #GtkIconFactory on how to
1475 * Return value: (transfer none): The #GtkIconFactory object used by it
1478 anjuta_ui_get_icon_factory (AnjutaUI
*ui
)
1480 g_return_val_if_fail (ANJUTA_IS_UI (ui
), NULL
);
1481 return ui
->priv
->icon_factory
;
1485 * anjuta_ui_dump_tree:
1486 * @ui: A #AnjutaUI object.
1488 * Dumps the current UI XML tree in STDOUT. Useful for debugging.
1491 anjuta_ui_dump_tree (AnjutaUI
*ui
)
1495 g_return_if_fail (ANJUTA_IS_UI(ui
));
1497 gtk_ui_manager_ensure_update (GTK_UI_MANAGER (ui
));
1498 ui_str
= gtk_ui_manager_get_ui (GTK_UI_MANAGER (ui
));
1499 /* DEBUG_PRINT ("%s", ui_str); */
1507 anjuta_ui_get_accel_file (void)
1509 return anjuta_util_get_user_config_file_path ("anjuta-accels", NULL
);
1513 anjuta_ui_load_accels (const gchar
*filename
)
1517 gtk_accel_map_load (filename
);
1521 gchar
*def_filename
= anjuta_ui_get_accel_file ();
1522 if (def_filename
!= NULL
)
1524 gtk_accel_map_load (def_filename
);
1525 g_free (def_filename
);
1531 anjuta_ui_save_accels (const gchar
*filename
)
1535 gtk_accel_map_save (filename
);
1539 gchar
* def_filename
= anjuta_ui_get_accel_file ();
1541 if (def_filename
!= NULL
)
1543 gtk_accel_map_save (def_filename
);
1544 g_free (def_filename
);
1549 static void anjuta_ui_remove_accel (AnjutaUI
*ui
,
1550 const gchar
*accel_path
, guint accel_key
,
1551 GdkModifierType accel_mods
, gboolean changed
)
1553 gtk_accel_group_disconnect_key (anjuta_ui_get_accel_group(ui
), accel_key
, accel_mods
);
1557 anjuta_ui_unload_accels (AnjutaUI
*ui
)
1559 anjuta_ui_save_accels (NULL
);
1560 gtk_accel_map_foreach_unfiltered (ui
, (GtkAccelMapForeach
) anjuta_ui_remove_accel
);