1 /*
2  * Copyright (c) 2014 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 #include <glib/gi18n-lib.h>
20 
21 #include "actions.h"
22 #include "action-editor.h"
23 
24 #include "gtkapplication.h"
25 #include "gtkapplicationwindow.h"
26 #include "gtktreeview.h"
27 #include "gtkliststore.h"
28 #include "gtkwidgetprivate.h"
29 #include "gtkpopover.h"
30 #include "gtklabel.h"
31 
32 enum
33 {
34   COLUMN_PREFIX,
35   COLUMN_NAME,
36   COLUMN_ENABLED,
37   COLUMN_PARAMETER,
38   COLUMN_STATE,
39   COLUMN_GROUP
40 };
41 
42 struct _GtkInspectorActionsPrivate
43 {
44   GtkListStore *model;
45   GHashTable *groups;
46   GHashTable *iters;
47 };
48 
G_DEFINE_TYPE_WITH_PRIVATE(GtkInspectorActions,gtk_inspector_actions,GTK_TYPE_BOX)49 G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorActions, gtk_inspector_actions, GTK_TYPE_BOX)
50 
51 static void
52 gtk_inspector_actions_init (GtkInspectorActions *sl)
53 {
54   sl->priv = gtk_inspector_actions_get_instance_private (sl);
55   sl->priv->iters = g_hash_table_new_full (g_str_hash,
56                                            g_str_equal,
57                                            g_free,
58                                            (GDestroyNotify) gtk_tree_iter_free);
59   sl->priv->groups = g_hash_table_new_full (g_direct_hash,
60                                             g_direct_equal,
61                                             NULL,
62                                             g_free);
63   gtk_widget_init_template (GTK_WIDGET (sl));
64 }
65 
66 static void
add_action(GtkInspectorActions * sl,GActionGroup * group,const gchar * prefix,const gchar * name)67 add_action (GtkInspectorActions *sl,
68             GActionGroup        *group,
69             const gchar         *prefix,
70             const gchar         *name)
71 {
72   GtkTreeIter iter;
73   gboolean enabled;
74   const gchar *parameter;
75   GVariant *state;
76   gchar *state_string;
77 
78   enabled = g_action_group_get_action_enabled (group, name);
79   parameter = (const gchar *)g_action_group_get_action_parameter_type (group, name);
80   state = g_action_group_get_action_state (group, name);
81   if (state)
82     state_string = g_variant_print (state, FALSE);
83   else
84     state_string = g_strdup ("");
85   gtk_list_store_append (sl->priv->model, &iter);
86   gtk_list_store_set (sl->priv->model, &iter,
87                       COLUMN_PREFIX, prefix,
88                       COLUMN_NAME, name,
89                       COLUMN_ENABLED, enabled,
90                       COLUMN_PARAMETER, parameter,
91                       COLUMN_STATE, state_string,
92                       COLUMN_GROUP, group,
93                       -1);
94   g_hash_table_insert (sl->priv->iters,
95                        g_strconcat (prefix, ".", name, NULL),
96                        gtk_tree_iter_copy (&iter));
97   g_free (state_string);
98 }
99 
100 static void
action_added_cb(GActionGroup * group,const gchar * action_name,GtkInspectorActions * sl)101 action_added_cb (GActionGroup        *group,
102                  const gchar         *action_name,
103                  GtkInspectorActions *sl)
104 {
105   const gchar *prefix;
106   prefix = g_hash_table_lookup (sl->priv->groups, group);
107   add_action (sl, group, prefix, action_name);
108 }
109 
110 static void
action_removed_cb(GActionGroup * group,const gchar * action_name,GtkInspectorActions * sl)111 action_removed_cb (GActionGroup        *group,
112                    const gchar         *action_name,
113                    GtkInspectorActions *sl)
114 {
115   const gchar *prefix;
116   gchar *key;
117   GtkTreeIter *iter;
118   prefix = g_hash_table_lookup (sl->priv->groups, group);
119   key = g_strconcat (prefix, ".", action_name, NULL);
120   iter = g_hash_table_lookup (sl->priv->iters, key);
121   gtk_list_store_remove (sl->priv->model, iter);
122   g_hash_table_remove (sl->priv->iters, key);
123   g_free (key);
124 }
125 
126 static void
action_enabled_changed_cb(GActionGroup * group,const gchar * action_name,gboolean enabled,GtkInspectorActions * sl)127 action_enabled_changed_cb (GActionGroup        *group,
128                            const gchar         *action_name,
129                            gboolean             enabled,
130                            GtkInspectorActions *sl)
131 {
132   const gchar *prefix;
133   gchar *key;
134   GtkTreeIter *iter;
135   prefix = g_hash_table_lookup (sl->priv->groups, group);
136   key = g_strconcat (prefix, ".", action_name, NULL);
137   iter = g_hash_table_lookup (sl->priv->iters, key);
138   gtk_list_store_set (sl->priv->model, iter,
139                       COLUMN_ENABLED, enabled,
140                       -1);
141   g_free (key);
142 }
143 
144 static void
action_state_changed_cb(GActionGroup * group,const gchar * action_name,GVariant * state,GtkInspectorActions * sl)145 action_state_changed_cb (GActionGroup        *group,
146                          const gchar         *action_name,
147                          GVariant            *state,
148                          GtkInspectorActions *sl)
149 {
150   const gchar *prefix;
151   gchar *key;
152   GtkTreeIter *iter;
153   gchar *state_string;
154   prefix = g_hash_table_lookup (sl->priv->groups, group);
155   key = g_strconcat (prefix, ".", action_name, NULL);
156   iter = g_hash_table_lookup (sl->priv->iters, key);
157   if (state)
158     state_string = g_variant_print (state, FALSE);
159   else
160     state_string = g_strdup ("");
161   gtk_list_store_set (sl->priv->model, iter,
162                       COLUMN_STATE, state_string,
163                       -1);
164   g_free (state_string);
165   g_free (key);
166 }
167 
168 static void
add_group(GtkInspectorActions * sl,GActionGroup * group,const gchar * prefix)169 add_group (GtkInspectorActions *sl,
170            GActionGroup        *group,
171            const gchar         *prefix)
172 {
173   gint i;
174   gchar **names;
175 
176   gtk_widget_show (GTK_WIDGET (sl));
177 
178   g_signal_connect (group, "action-added", G_CALLBACK (action_added_cb), sl);
179   g_signal_connect (group, "action-removed", G_CALLBACK (action_removed_cb), sl);
180   g_signal_connect (group, "action-enabled-changed", G_CALLBACK (action_enabled_changed_cb), sl);
181   g_signal_connect (group, "action-state-changed", G_CALLBACK (action_state_changed_cb), sl);
182   g_hash_table_insert (sl->priv->groups, group, g_strdup (prefix));
183 
184   names = g_action_group_list_actions (group);
185   for (i = 0; names[i]; i++)
186     add_action (sl, group, prefix, names[i]);
187   g_strfreev (names);
188 }
189 
190 static void
disconnect_group(gpointer key,gpointer value,gpointer data)191 disconnect_group (gpointer key, gpointer value, gpointer data)
192 {
193   GActionGroup *group = key;
194   GtkInspectorActions *sl = data;
195 
196   g_signal_handlers_disconnect_by_func (group, action_added_cb, sl);
197   g_signal_handlers_disconnect_by_func (group, action_removed_cb, sl);
198   g_signal_handlers_disconnect_by_func (group, action_enabled_changed_cb, sl);
199   g_signal_handlers_disconnect_by_func (group, action_state_changed_cb, sl);
200 }
201 
202 void
gtk_inspector_actions_set_object(GtkInspectorActions * sl,GObject * object)203 gtk_inspector_actions_set_object (GtkInspectorActions *sl,
204                                   GObject             *object)
205 {
206   gtk_widget_hide (GTK_WIDGET (sl));
207   g_hash_table_foreach (sl->priv->groups, disconnect_group, sl);
208   g_hash_table_remove_all (sl->priv->groups);
209   g_hash_table_remove_all (sl->priv->iters);
210   gtk_list_store_clear (sl->priv->model);
211 
212   if (GTK_IS_APPLICATION (object))
213     add_group (sl, G_ACTION_GROUP (object), "app");
214   else if (GTK_IS_APPLICATION_WINDOW (object))
215     add_group (sl, G_ACTION_GROUP (object), "win");
216   else if (GTK_IS_WIDGET (object))
217     {
218       const gchar **prefixes;
219       GActionGroup *group;
220       gint i;
221 
222       prefixes = gtk_widget_list_action_prefixes (GTK_WIDGET (object));
223       if (prefixes)
224         {
225           for (i = 0; prefixes[i]; i++)
226             {
227               group = gtk_widget_get_action_group (GTK_WIDGET (object), prefixes[i]);
228               add_group (sl, group, prefixes[i]);
229             }
230           g_free (prefixes);
231         }
232     }
233 }
234 
235 static void
row_activated(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,GtkInspectorActions * sl)236 row_activated (GtkTreeView         *tv,
237                GtkTreePath         *path,
238                GtkTreeViewColumn   *col,
239                GtkInspectorActions *sl)
240 {
241   GtkTreeIter iter;
242   GdkRectangle rect;
243   GtkWidget *popover;
244   gchar *prefix;
245   gchar *name;
246   GActionGroup *group;
247   GtkWidget *editor;
248 
249   gtk_tree_model_get_iter (GTK_TREE_MODEL (sl->priv->model), &iter, path);
250   gtk_tree_model_get (GTK_TREE_MODEL (sl->priv->model),
251                       &iter,
252                       COLUMN_PREFIX, &prefix,
253                       COLUMN_NAME, &name,
254                       COLUMN_GROUP, &group,
255                       -1);
256 
257   gtk_tree_model_get_iter (GTK_TREE_MODEL (sl->priv->model), &iter, path);
258   gtk_tree_view_get_cell_area (tv, path, col, &rect);
259   gtk_tree_view_convert_bin_window_to_widget_coords (tv, rect.x, rect.y, &rect.x, &rect.y);
260 
261   popover = gtk_popover_new (GTK_WIDGET (tv));
262   gtk_popover_set_pointing_to (GTK_POPOVER (popover), &rect);
263 
264   editor = gtk_inspector_action_editor_new (group, prefix, name);
265   gtk_container_add (GTK_CONTAINER (popover), editor);
266   gtk_popover_popup (GTK_POPOVER (popover));
267 
268   g_signal_connect (popover, "hide", G_CALLBACK (gtk_widget_destroy), NULL);
269 
270   g_free (name);
271   g_free (prefix);
272 }
273 
274 static void
gtk_inspector_actions_class_init(GtkInspectorActionsClass * klass)275 gtk_inspector_actions_class_init (GtkInspectorActionsClass *klass)
276 {
277   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
278 
279   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/actions.ui");
280   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorActions, model);
281   gtk_widget_class_bind_template_callback (widget_class, row_activated);
282 }
283 
284 // vim: set et sw=2 ts=2:
285