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 "action-editor.h"
22 
23 #include "gtksizegroup.h"
24 #include "gtktogglebutton.h"
25 #include "gtkentry.h"
26 #include "gtkbin.h"
27 #include "gtklabel.h"
28 
29 struct _GtkInspectorActionEditorPrivate
30 {
31   GActionGroup *group;
32   gchar *prefix;
33   gchar *name;
34   gboolean enabled;
35   const GVariantType *parameter_type;
36   GVariantType *state_type;
37   GtkWidget *activate_button;
38   GtkWidget *parameter_entry;
39   GtkWidget *state_entry;
40   GtkSizeGroup *sg;
41 };
42 
43 enum
44 {
45   PROP_0,
46   PROP_GROUP,
47   PROP_PREFIX,
48   PROP_NAME
49 };
50 
G_DEFINE_TYPE_WITH_PRIVATE(GtkInspectorActionEditor,gtk_inspector_action_editor,GTK_TYPE_BOX)51 G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorActionEditor, gtk_inspector_action_editor, GTK_TYPE_BOX)
52 
53 static void
54 gtk_inspector_action_editor_init (GtkInspectorActionEditor *editor)
55 {
56   editor->priv = gtk_inspector_action_editor_get_instance_private (editor);
57   g_object_set (editor,
58                 "orientation", GTK_ORIENTATION_VERTICAL,
59                 "spacing", 10,
60                 "margin", 10,
61                 NULL);
62 }
63 
64 typedef void (*VariantEditorChanged) (GtkWidget *editor, gpointer data);
65 
66 typedef struct {
67   GtkWidget *editor;
68   VariantEditorChanged callback;
69   gpointer   data;
70 } VariantEditorData;
71 
72 static void
variant_editor_changed_cb(GObject * obj,GParamSpec * pspec,VariantEditorData * data)73 variant_editor_changed_cb (GObject           *obj,
74                            GParamSpec        *pspec,
75                            VariantEditorData *data)
76 {
77   data->callback (data->editor, data->data);
78 }
79 
80 static GtkWidget *
variant_editor_new(const GVariantType * type,VariantEditorChanged callback,gpointer data)81 variant_editor_new (const GVariantType   *type,
82                     VariantEditorChanged  callback,
83                     gpointer              data)
84 {
85   GtkWidget *editor;
86   GtkWidget *label;
87   GtkWidget *entry;
88   VariantEditorData *d;
89 
90   d = g_new (VariantEditorData, 1);
91   d->callback = callback;
92   d->data = data;
93 
94   if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
95     {
96       editor = gtk_toggle_button_new_with_label ("FALSE");
97       g_signal_connect (editor, "notify::active", G_CALLBACK (variant_editor_changed_cb), d);
98     }
99   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
100     {
101       editor = gtk_entry_new ();
102       g_signal_connect (editor, "notify::text", G_CALLBACK (variant_editor_changed_cb), d);
103     }
104   else
105     {
106       editor = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
107       entry = gtk_entry_new ();
108       gtk_container_add (GTK_CONTAINER (editor), entry);
109       label = gtk_label_new (g_variant_type_peek_string (type));
110       gtk_container_add (GTK_CONTAINER (editor), label);
111       g_signal_connect (entry, "notify::text", G_CALLBACK (variant_editor_changed_cb), d);
112     }
113 
114   g_object_set_data (G_OBJECT (editor), "type", (gpointer)type);
115   d->editor = editor;
116   g_object_set_data_full (G_OBJECT (editor), "callback", d, g_free);
117 
118   gtk_widget_show_all (editor);
119 
120   return editor;
121 }
122 
123 static void
variant_editor_set_value(GtkWidget * editor,GVariant * value)124 variant_editor_set_value (GtkWidget *editor,
125                           GVariant  *value)
126 {
127   const GVariantType *type;
128   gpointer data;
129 
130   data = g_object_get_data (G_OBJECT (editor), "callback");
131   g_signal_handlers_block_by_func (editor, variant_editor_changed_cb, data);
132 
133   type = g_variant_get_type (value);
134   if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
135     {
136       GtkToggleButton *tb = GTK_TOGGLE_BUTTON (editor);
137       GtkWidget *child;
138 
139       gtk_toggle_button_set_active (tb, g_variant_get_boolean (value));
140       child = gtk_bin_get_child (GTK_BIN (tb));
141       gtk_label_set_text (GTK_LABEL (child),
142                           g_variant_get_boolean (value) ? "TRUE" : "FALSE");
143     }
144   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
145     {
146       GtkEntry *entry = GTK_ENTRY (editor);
147       gtk_entry_set_text (entry, g_variant_get_string (value, NULL));
148     }
149   else
150     {
151       GList *children;
152       GtkEntry *entry;
153       gchar *text;
154 
155       children = gtk_container_get_children (GTK_CONTAINER (editor));
156       entry = children->data;
157       g_list_free (children);
158 
159       text = g_variant_print (value, FALSE);
160       gtk_entry_set_text (entry, text);
161       g_free (text);
162     }
163 
164   g_signal_handlers_unblock_by_func (editor, variant_editor_changed_cb, data);
165 }
166 
167 static GVariant *
variant_editor_get_value(GtkWidget * editor)168 variant_editor_get_value (GtkWidget *editor)
169 {
170   const GVariantType *type;
171   GVariant *value;
172 
173   type = (const GVariantType *) g_object_get_data (G_OBJECT (editor), "type");
174   if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
175     {
176       GtkToggleButton *tb = GTK_TOGGLE_BUTTON (editor);
177       value = g_variant_new_boolean (gtk_toggle_button_get_active (tb));
178     }
179   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
180     {
181       GtkEntry *entry = GTK_ENTRY (editor);
182       value = g_variant_new_string (gtk_entry_get_text (entry));
183     }
184   else
185     {
186       GList *children;
187       GtkEntry *entry;
188       const gchar *text;
189 
190       children = gtk_container_get_children (GTK_CONTAINER (editor));
191       entry = children->data;
192       text = gtk_entry_get_text (entry);
193       g_list_free (children);
194 
195       value = g_variant_parse (type, text, NULL, NULL, NULL);
196     }
197 
198   return value;
199 }
200 
201 static void
activate_action(GtkWidget * button,GtkInspectorActionEditor * r)202 activate_action (GtkWidget                *button,
203                  GtkInspectorActionEditor *r)
204 {
205   GVariant *parameter = NULL;
206 
207   if (r->priv->parameter_entry)
208     parameter = variant_editor_get_value (r->priv->parameter_entry);
209   g_action_group_activate_action (r->priv->group, r->priv->name, parameter);
210 }
211 
212 static void
parameter_changed(GtkWidget * editor,gpointer data)213 parameter_changed (GtkWidget *editor,
214                    gpointer   data)
215 {
216   GtkInspectorActionEditor *r = data;
217   GVariant *value;
218 
219   value = variant_editor_get_value (editor);
220   gtk_widget_set_sensitive (r->priv->activate_button, r->priv->enabled && value != NULL);
221   if (value)
222     g_variant_unref (value);
223 }
224 
225 static void
state_changed(GtkWidget * editor,gpointer data)226 state_changed (GtkWidget *editor,
227                gpointer   data)
228 {
229   GtkInspectorActionEditor *r = data;
230   GVariant *value;
231 
232   value = variant_editor_get_value (editor);
233   if (value)
234     g_action_group_change_action_state (r->priv->group, r->priv->name, value);
235 }
236 
237 static void
action_enabled_changed_cb(GActionGroup * group,const gchar * action_name,gboolean enabled,GtkInspectorActionEditor * r)238 action_enabled_changed_cb (GActionGroup             *group,
239                            const gchar              *action_name,
240                            gboolean                  enabled,
241                            GtkInspectorActionEditor *r)
242 {
243   r->priv->enabled = enabled;
244   if (r->priv->parameter_entry)
245     {
246       gtk_widget_set_sensitive (r->priv->parameter_entry, enabled);
247       parameter_changed (r->priv->parameter_entry, r);
248     }
249 }
250 
251 static void
action_state_changed_cb(GActionGroup * group,const gchar * action_name,GVariant * state,GtkInspectorActionEditor * r)252 action_state_changed_cb (GActionGroup             *group,
253                          const gchar              *action_name,
254                          GVariant                 *state,
255                          GtkInspectorActionEditor *r)
256 {
257   if (r->priv->state_entry)
258     variant_editor_set_value (r->priv->state_entry, state);
259 }
260 
261 static void
constructed(GObject * object)262 constructed (GObject *object)
263 {
264   GtkInspectorActionEditor *r = GTK_INSPECTOR_ACTION_EDITOR (object);
265   GVariant *state;
266   gchar *fullname;
267   GtkWidget *row;
268   GtkWidget *label;
269 
270   r->priv->enabled = g_action_group_get_action_enabled (r->priv->group, r->priv->name);
271   state = g_action_group_get_action_state (r->priv->group, r->priv->name);
272 
273   fullname = g_strdup_printf ("%s.%s", r->priv->prefix, r->priv->name);
274   gtk_container_add (GTK_CONTAINER (r), gtk_label_new (fullname));
275   g_free (fullname);
276 
277   r->priv->sg = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
278 
279   row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
280 
281   r->priv->activate_button = gtk_button_new_with_label (_("Activate"));
282   g_signal_connect (r->priv->activate_button, "clicked", G_CALLBACK (activate_action), r);
283 
284   gtk_size_group_add_widget (r->priv->sg, r->priv->activate_button);
285   gtk_widget_set_sensitive (r->priv->activate_button, r->priv->enabled);
286   gtk_container_add (GTK_CONTAINER (row), r->priv->activate_button);
287 
288   r->priv->parameter_type = g_action_group_get_action_parameter_type (r->priv->group, r->priv->name);
289   if (r->priv->parameter_type)
290     {
291       r->priv->parameter_entry = variant_editor_new (r->priv->parameter_type, parameter_changed, r);
292       gtk_widget_set_sensitive (r->priv->parameter_entry, r->priv->enabled);
293       gtk_container_add (GTK_CONTAINER (row), r->priv->parameter_entry);
294     }
295 
296   gtk_container_add (GTK_CONTAINER (r), row);
297 
298   if (state)
299     {
300       r->priv->state_type = g_variant_type_copy (g_variant_get_type (state));
301       row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
302       label = gtk_label_new (_("State"));
303       gtk_size_group_add_widget (r->priv->sg, label);
304       gtk_container_add (GTK_CONTAINER (row), label);
305       r->priv->state_entry = variant_editor_new (r->priv->state_type, state_changed, r);
306       variant_editor_set_value (r->priv->state_entry, state);
307       gtk_container_add (GTK_CONTAINER (row), r->priv->state_entry);
308       gtk_container_add (GTK_CONTAINER (r), row);
309     }
310 
311   g_signal_connect (r->priv->group, "action-enabled-changed",
312                     G_CALLBACK (action_enabled_changed_cb), r);
313   g_signal_connect (r->priv->group, "action-state-changed",
314                     G_CALLBACK (action_state_changed_cb), r);
315 
316   gtk_widget_show_all (GTK_WIDGET (r));
317 }
318 
319 static void
finalize(GObject * object)320 finalize (GObject *object)
321 {
322   GtkInspectorActionEditor *r = GTK_INSPECTOR_ACTION_EDITOR (object);
323 
324   g_free (r->priv->prefix);
325   g_free (r->priv->name);
326   g_object_unref (r->priv->sg);
327   if (r->priv->state_type)
328     g_variant_type_free (r->priv->state_type);
329   g_signal_handlers_disconnect_by_func (r->priv->group, action_enabled_changed_cb, r);
330   g_signal_handlers_disconnect_by_func (r->priv->group, action_state_changed_cb, r);
331 
332   G_OBJECT_CLASS (gtk_inspector_action_editor_parent_class)->finalize (object);
333 }
334 
335 static void
get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)336 get_property (GObject    *object,
337               guint       param_id,
338               GValue     *value,
339               GParamSpec *pspec)
340 {
341   GtkInspectorActionEditor *r = GTK_INSPECTOR_ACTION_EDITOR (object);
342 
343   switch (param_id)
344     {
345     case PROP_GROUP:
346       g_value_set_object (value, r->priv->group);
347       break;
348 
349     case PROP_PREFIX:
350       g_value_set_string (value, r->priv->prefix);
351       break;
352 
353     case PROP_NAME:
354       g_value_set_string (value, r->priv->name);
355       break;
356 
357     default:
358       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
359       break;
360     }
361 }
362 
363 static void
set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)364 set_property (GObject      *object,
365               guint         param_id,
366               const GValue *value,
367               GParamSpec   *pspec)
368 {
369   GtkInspectorActionEditor *r = GTK_INSPECTOR_ACTION_EDITOR (object);
370 
371   switch (param_id)
372     {
373     case PROP_GROUP:
374       r->priv->group = g_value_get_object (value);
375       break;
376 
377     case PROP_PREFIX:
378       g_free (r->priv->prefix);
379       r->priv->prefix = g_value_dup_string (value);
380       break;
381 
382     case PROP_NAME:
383       g_free (r->priv->name);
384       r->priv->name = g_value_dup_string (value);
385       break;
386 
387     default:
388       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
389       break;
390     }
391 }
392 
393 static void
gtk_inspector_action_editor_class_init(GtkInspectorActionEditorClass * klass)394 gtk_inspector_action_editor_class_init (GtkInspectorActionEditorClass *klass)
395 {
396   GObjectClass *object_class = G_OBJECT_CLASS (klass);
397 
398   object_class->constructed = constructed;
399   object_class->finalize = finalize;
400   object_class->get_property = get_property;
401   object_class->set_property = set_property;
402 
403   g_object_class_install_property (object_class, PROP_GROUP,
404       g_param_spec_object ("group", "Action Group", "The Action Group containing the action",
405                            G_TYPE_ACTION_GROUP, G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
406 
407   g_object_class_install_property (object_class, PROP_PREFIX,
408       g_param_spec_string ("prefix", "Prefix", "The action name prefix",
409                            NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
410 
411   g_object_class_install_property (object_class, PROP_NAME,
412       g_param_spec_string ("name", "Name", "The action name",
413                            NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
414 }
415 
416 GtkWidget *
gtk_inspector_action_editor_new(GActionGroup * group,const gchar * prefix,const gchar * name)417 gtk_inspector_action_editor_new (GActionGroup *group,
418                                  const gchar  *prefix,
419                                  const gchar  *name)
420 {
421   return g_object_new (GTK_TYPE_INSPECTOR_ACTION_EDITOR,
422                        "group", group,
423                        "prefix", prefix,
424                        "name", name,
425                        NULL);
426 }
427