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 #include "action-holder.h"
24 
25 #include "gtkapplication.h"
26 #include "gtkapplicationwindow.h"
27 #include "gtktreeview.h"
28 #include "gtkliststore.h"
29 #include "gtkwidgetprivate.h"
30 #include "gtkactionmuxerprivate.h"
31 #include "gtkpopover.h"
32 #include "gtklabel.h"
33 #include "gtkstack.h"
34 #include "gtklistbox.h"
35 #include "gtksizegroup.h"
36 #include "gtkboxlayout.h"
37 
38 
39 struct _GtkInspectorActions
40 {
41   GtkWidget parent;
42 
43   GtkWidget *list;
44   GtkWidget *button;
45 
46   GObject *object;
47 
48   GListStore *actions;
49   GtkSortListModel *sorted;
50   GtkColumnViewColumn *name;
51 };
52 
53 typedef struct _GtkInspectorActionsClass
54 {
55   GtkWidgetClass parent;
56 } GtkInspectorActionsClass;
57 
58 enum {
59   PROP_0,
60   PROP_BUTTON
61 };
62 
G_DEFINE_TYPE(GtkInspectorActions,gtk_inspector_actions,GTK_TYPE_WIDGET)63 G_DEFINE_TYPE (GtkInspectorActions, gtk_inspector_actions, GTK_TYPE_WIDGET)
64 
65 static void
66 gtk_inspector_actions_init (GtkInspectorActions *sl)
67 {
68  GtkBoxLayout *layout;
69 
70   gtk_widget_init_template (GTK_WIDGET (sl));
71 
72   layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (sl)));
73   gtk_orientable_set_orientation (GTK_ORIENTABLE (layout), GTK_ORIENTATION_VERTICAL);
74 }
75 
76 static void
action_added(GObject * owner,const char * action_name,GtkInspectorActions * sl)77 action_added (GObject             *owner,
78               const char          *action_name,
79               GtkInspectorActions *sl)
80 {
81   ActionHolder *holder = action_holder_new (owner, action_name);
82   g_list_store_append (sl->actions, holder);
83   g_object_unref (holder);
84 }
85 
86 static void
setup_name_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)87 setup_name_cb (GtkSignalListItemFactory *factory,
88                GtkListItem              *list_item)
89 {
90   GtkWidget *label;
91 
92   label = gtk_label_new (NULL);
93   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
94   gtk_widget_add_css_class (label, "cell");
95   gtk_list_item_set_child (list_item, label);
96 }
97 
98 static void
bind_name_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)99 bind_name_cb (GtkSignalListItemFactory *factory,
100               GtkListItem              *list_item)
101 {
102   gpointer item;
103   GtkWidget *label;
104 
105   item = gtk_list_item_get_item (list_item);
106   label = gtk_list_item_get_child (list_item);
107 
108   gtk_label_set_label (GTK_LABEL (label), action_holder_get_name (ACTION_HOLDER (item)));
109 }
110 
111 static void
setup_enabled_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)112 setup_enabled_cb (GtkSignalListItemFactory *factory,
113                   GtkListItem              *list_item)
114 {
115   GtkWidget *label;
116 
117   label = gtk_label_new (NULL);
118   gtk_label_set_xalign (GTK_LABEL (label), 0.5);
119   gtk_widget_add_css_class (label, "cell");
120   gtk_list_item_set_child (list_item, label);
121 }
122 
123 static void
bind_enabled_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)124 bind_enabled_cb (GtkSignalListItemFactory *factory,
125                  GtkListItem              *list_item)
126 {
127   gpointer item;
128   GtkWidget *label;
129   GObject *owner;
130   const char *name;
131   gboolean enabled = FALSE;
132 
133   item = gtk_list_item_get_item (list_item);
134   label = gtk_list_item_get_child (list_item);
135 
136   owner = action_holder_get_owner (ACTION_HOLDER (item));
137   name = action_holder_get_name (ACTION_HOLDER (item));
138   if (G_IS_ACTION_GROUP (owner))
139     enabled = g_action_group_get_action_enabled (G_ACTION_GROUP (owner), name);
140   else if (GTK_IS_ACTION_MUXER (owner))
141     gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), name,
142                                    &enabled, NULL, NULL, NULL, NULL);
143 
144   gtk_label_set_label (GTK_LABEL (label), enabled ? "+" : "-");
145 }
146 
147 static void
setup_parameter_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)148 setup_parameter_cb (GtkSignalListItemFactory *factory,
149                     GtkListItem              *list_item)
150 {
151   GtkWidget *label;
152 
153   label = gtk_label_new (NULL);
154   gtk_label_set_xalign (GTK_LABEL (label), 0.5);
155   gtk_widget_add_css_class (label, "cell");
156   gtk_list_item_set_child (list_item, label);
157 }
158 
159 static void
bind_parameter_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)160 bind_parameter_cb (GtkSignalListItemFactory *factory,
161                    GtkListItem              *list_item)
162 {
163   gpointer item;
164   GtkWidget *label;
165   GObject *owner;
166   const char *name;
167   const char *parameter;
168 
169   item = gtk_list_item_get_item (list_item);
170   label = gtk_list_item_get_child (list_item);
171 
172   owner = action_holder_get_owner (ACTION_HOLDER (item));
173   name = action_holder_get_name (ACTION_HOLDER (item));
174   if (G_IS_ACTION_GROUP (owner))
175     parameter = (const char *)g_action_group_get_action_parameter_type (G_ACTION_GROUP (owner), name);
176   else if (GTK_IS_ACTION_MUXER (owner))
177     gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), name,
178                                    NULL, (const GVariantType **)&parameter, NULL, NULL, NULL);
179   else
180     parameter = "(Unknown)";
181 
182   gtk_label_set_label (GTK_LABEL (label), parameter);
183 }
184 
185 static void
setup_state_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)186 setup_state_cb (GtkSignalListItemFactory *factory,
187                 GtkListItem              *list_item)
188 {
189   GtkWidget *label;
190 
191   label = gtk_label_new (NULL);
192   gtk_widget_set_margin_start (label, 5);
193   gtk_widget_set_margin_end (label, 5);
194   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
195   gtk_widget_add_css_class (label, "cell");
196   gtk_list_item_set_child (list_item, label);
197 }
198 
199 static void
bind_state_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)200 bind_state_cb (GtkSignalListItemFactory *factory,
201                GtkListItem              *list_item)
202 {
203   gpointer item;
204   GtkWidget *label;
205   GObject *owner;
206   const char *name;
207   GVariant *state;
208 
209   item = gtk_list_item_get_item (list_item);
210   label = gtk_list_item_get_child (list_item);
211 
212   owner = action_holder_get_owner (ACTION_HOLDER (item));
213   name = action_holder_get_name (ACTION_HOLDER (item));
214   if (G_IS_ACTION_GROUP (owner))
215     state = g_action_group_get_action_state (G_ACTION_GROUP (owner), name);
216   else if (GTK_IS_ACTION_MUXER (owner))
217     gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), name,
218                                    NULL, NULL, NULL, NULL, &state);
219   else
220     state = NULL;
221 
222   if (state)
223     {
224       char *state_string;
225 
226       state_string = g_variant_print (state, FALSE);
227       gtk_label_set_label (GTK_LABEL (label), state_string);
228       g_free (state_string);
229       g_variant_unref (state);
230     }
231   else
232     gtk_label_set_label (GTK_LABEL (label), "");
233 }
234 
235 static void
setup_changes_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)236 setup_changes_cb (GtkSignalListItemFactory *factory,
237                   GtkListItem              *list_item)
238 {
239   GtkWidget *editor;
240 
241   editor = gtk_inspector_action_editor_new ();
242   gtk_widget_add_css_class (editor, "cell");
243   gtk_list_item_set_child (list_item, editor);
244 }
245 
246 static void
bind_changes_cb(GtkSignalListItemFactory * factory,GtkListItem * list_item)247 bind_changes_cb (GtkSignalListItemFactory *factory,
248                  GtkListItem              *list_item)
249 {
250   gpointer item;
251   GObject *owner;
252   const char *name;
253   GtkWidget *editor;
254 
255   item = gtk_list_item_get_item (list_item);
256   editor = gtk_list_item_get_child (list_item);
257 
258   owner = action_holder_get_owner (ACTION_HOLDER (item));
259   name = action_holder_get_name (ACTION_HOLDER (item));
260 
261   gtk_inspector_action_editor_set (GTK_INSPECTOR_ACTION_EDITOR (editor),
262                                    owner,
263                                    name);
264 }
265 
266 static void
add_group(GtkInspectorActions * sl,GActionGroup * group)267 add_group (GtkInspectorActions *sl,
268            GActionGroup        *group)
269 {
270   int i;
271   char **names;
272 
273   names = g_action_group_list_actions (group);
274   for (i = 0; names[i]; i++)
275     action_added (G_OBJECT (group), names[i], sl);
276   g_strfreev (names);
277 }
278 
279 static void
add_muxer(GtkInspectorActions * sl,GtkActionMuxer * muxer)280 add_muxer (GtkInspectorActions *sl,
281            GtkActionMuxer      *muxer)
282 {
283   int i;
284   char **names;
285 
286   names = gtk_action_muxer_list_actions (muxer, FALSE);
287   for (i = 0; names[i]; i++)
288     action_added (G_OBJECT (muxer), names[i], sl);
289   g_strfreev (names);
290 }
291 
292 static gboolean
reload(GtkInspectorActions * sl)293 reload (GtkInspectorActions *sl)
294 {
295   gboolean loaded = FALSE;
296 
297   g_object_unref (sl->actions);
298   sl->actions = g_list_store_new (ACTION_TYPE_HOLDER);
299 
300   if (GTK_IS_APPLICATION (sl->object))
301     {
302       add_group (sl, G_ACTION_GROUP (sl->object));
303       loaded = TRUE;
304     }
305   else if (GTK_IS_WIDGET (sl->object))
306     {
307       GtkActionMuxer *muxer;
308 
309       muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (sl->object), FALSE);
310       if (muxer)
311         {
312           add_muxer (sl, muxer);
313           loaded = TRUE;
314         }
315     }
316 
317   gtk_sort_list_model_set_model (sl->sorted, G_LIST_MODEL (sl->actions));
318 
319   return loaded;
320 }
321 
322 static void
refresh_all(GtkInspectorActions * sl)323 refresh_all (GtkInspectorActions *sl)
324 {
325   reload (sl);
326 }
327 
328 void
gtk_inspector_actions_set_object(GtkInspectorActions * sl,GObject * object)329 gtk_inspector_actions_set_object (GtkInspectorActions *sl,
330                                   GObject             *object)
331 {
332   GtkWidget *stack;
333   GtkStackPage *page;
334   gboolean loaded;
335 
336   stack = gtk_widget_get_parent (GTK_WIDGET (sl));
337   page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (sl));
338   gtk_stack_page_set_visible (page, FALSE);
339 
340   g_set_object (&sl->object, object);
341 
342   gtk_column_view_sort_by_column (GTK_COLUMN_VIEW (sl->list), sl->name, GTK_SORT_ASCENDING);
343   loaded = reload (sl);
344   gtk_stack_page_set_visible (page, loaded);
345 }
346 
347 static void
get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)348 get_property (GObject    *object,
349               guint       param_id,
350               GValue     *value,
351               GParamSpec *pspec)
352 {
353   GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
354 
355   switch (param_id)
356     {
357     case PROP_BUTTON:
358       g_value_set_object (value, sl->button);
359       break;
360 
361     default:
362       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
363       break;
364     }
365 }
366 
367 static void
set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)368 set_property (GObject      *object,
369               guint         param_id,
370               const GValue *value,
371               GParamSpec   *pspec)
372 {
373   GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
374 
375   switch (param_id)
376     {
377     case PROP_BUTTON:
378       sl->button = g_value_get_object (value);
379       break;
380 
381     default:
382       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
383       break;
384     }
385 }
386 
387 static char *
holder_name(gpointer item)388 holder_name (gpointer item)
389 {
390   return g_strdup (action_holder_get_name (ACTION_HOLDER (item)));
391 }
392 
393 static void
constructed(GObject * object)394 constructed (GObject *object)
395 {
396   GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
397   GtkSorter *sorter;
398   GListModel *model;
399 
400   g_signal_connect_swapped (sl->button, "clicked",
401                             G_CALLBACK (refresh_all), sl);
402 
403   sorter = GTK_SORTER (gtk_string_sorter_new (gtk_cclosure_expression_new (G_TYPE_STRING,
404                                                                NULL,
405                                                                0, NULL,
406                                                                (GCallback)holder_name,
407                                                                NULL, NULL)));
408   gtk_column_view_column_set_sorter (sl->name, sorter);
409   g_object_unref (sorter);
410 
411   sl->actions = g_list_store_new (ACTION_TYPE_HOLDER);
412   sl->sorted = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (sl->actions)),
413                                         g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (sl->list))));
414   model = G_LIST_MODEL (gtk_no_selection_new (g_object_ref (G_LIST_MODEL (sl->sorted))));
415   gtk_column_view_set_model (GTK_COLUMN_VIEW (sl->list), GTK_SELECTION_MODEL (model));
416   g_object_unref (model);
417 }
418 
419 static void
dispose(GObject * object)420 dispose (GObject *object)
421 {
422   GtkInspectorActions *sl = GTK_INSPECTOR_ACTIONS (object);
423   GtkWidget *child;
424 
425   g_clear_object (&sl->sorted);
426   g_clear_object (&sl->actions);
427   g_clear_object (&sl->object);
428 
429   while ((child = gtk_widget_get_first_child (GTK_WIDGET (sl))))
430     gtk_widget_unparent (child);
431 
432   G_OBJECT_CLASS (gtk_inspector_actions_parent_class)->dispose (object);
433 }
434 
435 static void
gtk_inspector_actions_class_init(GtkInspectorActionsClass * klass)436 gtk_inspector_actions_class_init (GtkInspectorActionsClass *klass)
437 {
438   GObjectClass *object_class = G_OBJECT_CLASS (klass);
439   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
440 
441   object_class->dispose = dispose;
442   object_class->get_property = get_property;
443   object_class->set_property = set_property;
444   object_class->constructed = constructed;
445 
446   g_object_class_install_property (object_class, PROP_BUTTON,
447       g_param_spec_object ("button", NULL, NULL,
448                            GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
449 
450   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/actions.ui");
451   gtk_widget_class_bind_template_child (widget_class, GtkInspectorActions, list);
452   gtk_widget_class_bind_template_child (widget_class, GtkInspectorActions, name);
453   gtk_widget_class_bind_template_callback (widget_class, setup_name_cb);
454   gtk_widget_class_bind_template_callback (widget_class, bind_name_cb);
455   gtk_widget_class_bind_template_callback (widget_class, setup_enabled_cb);
456   gtk_widget_class_bind_template_callback (widget_class, bind_enabled_cb);
457   gtk_widget_class_bind_template_callback (widget_class, setup_parameter_cb);
458   gtk_widget_class_bind_template_callback (widget_class, bind_parameter_cb);
459   gtk_widget_class_bind_template_callback (widget_class, setup_state_cb);
460   gtk_widget_class_bind_template_callback (widget_class, bind_state_cb);
461   gtk_widget_class_bind_template_callback (widget_class, setup_changes_cb);
462   gtk_widget_class_bind_template_callback (widget_class, bind_changes_cb);
463 
464   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
465 }
466 
467 // vim: set et sw=2 ts=2:
468