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 | |
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 (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (sl))); |
73 | gtk_orientable_set_orientation (GTK_ORIENTABLE (layout), orientation: GTK_ORIENTATION_VERTICAL); |
74 | } |
75 | |
76 | static void |
77 | action_added (GObject *owner, |
78 | const char *action_name, |
79 | GtkInspectorActions *sl) |
80 | { |
81 | ActionHolder *holder = action_holder_new (owner, name: action_name); |
82 | g_list_store_append (store: sl->actions, item: holder); |
83 | g_object_unref (object: holder); |
84 | } |
85 | |
86 | static void |
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), xalign: 0.0); |
94 | gtk_widget_add_css_class (widget: label, css_class: "cell" ); |
95 | gtk_list_item_set_child (self: list_item, child: label); |
96 | } |
97 | |
98 | static void |
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 (self: list_item); |
106 | label = gtk_list_item_get_child (self: list_item); |
107 | |
108 | gtk_label_set_label (GTK_LABEL (label), str: action_holder_get_name (holder: ACTION_HOLDER (ptr: item))); |
109 | } |
110 | |
111 | static void |
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), xalign: 0.5); |
119 | gtk_widget_add_css_class (widget: label, css_class: "cell" ); |
120 | gtk_list_item_set_child (self: list_item, child: label); |
121 | } |
122 | |
123 | static void |
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 (self: list_item); |
134 | label = gtk_list_item_get_child (self: list_item); |
135 | |
136 | owner = action_holder_get_owner (holder: ACTION_HOLDER (ptr: item)); |
137 | name = action_holder_get_name (holder: ACTION_HOLDER (ptr: item)); |
138 | if (G_IS_ACTION_GROUP (owner)) |
139 | enabled = g_action_group_get_action_enabled (G_ACTION_GROUP (owner), action_name: name); |
140 | else if (GTK_IS_ACTION_MUXER (owner)) |
141 | gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), action_name: name, |
142 | enabled: &enabled, NULL, NULL, NULL, NULL); |
143 | |
144 | gtk_label_set_label (GTK_LABEL (label), str: enabled ? "+" : "-" ); |
145 | } |
146 | |
147 | static void |
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), xalign: 0.5); |
155 | gtk_widget_add_css_class (widget: label, css_class: "cell" ); |
156 | gtk_list_item_set_child (self: list_item, child: label); |
157 | } |
158 | |
159 | static void |
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 (self: list_item); |
170 | label = gtk_list_item_get_child (self: list_item); |
171 | |
172 | owner = action_holder_get_owner (holder: ACTION_HOLDER (ptr: item)); |
173 | name = action_holder_get_name (holder: ACTION_HOLDER (ptr: item)); |
174 | if (G_IS_ACTION_GROUP (owner)) |
175 | parameter = (const char *)g_action_group_get_action_parameter_type (G_ACTION_GROUP (owner), action_name: name); |
176 | else if (GTK_IS_ACTION_MUXER (owner)) |
177 | gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), action_name: name, |
178 | NULL, parameter_type: (const GVariantType **)¶meter, NULL, NULL, NULL); |
179 | else |
180 | parameter = "(Unknown)" ; |
181 | |
182 | gtk_label_set_label (GTK_LABEL (label), str: parameter); |
183 | } |
184 | |
185 | static void |
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 (widget: label, margin: 5); |
193 | gtk_widget_set_margin_end (widget: label, margin: 5); |
194 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0); |
195 | gtk_widget_add_css_class (widget: label, css_class: "cell" ); |
196 | gtk_list_item_set_child (self: list_item, child: label); |
197 | } |
198 | |
199 | static void |
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 (self: list_item); |
210 | label = gtk_list_item_get_child (self: list_item); |
211 | |
212 | owner = action_holder_get_owner (holder: ACTION_HOLDER (ptr: item)); |
213 | name = action_holder_get_name (holder: ACTION_HOLDER (ptr: item)); |
214 | if (G_IS_ACTION_GROUP (owner)) |
215 | state = g_action_group_get_action_state (G_ACTION_GROUP (owner), action_name: name); |
216 | else if (GTK_IS_ACTION_MUXER (owner)) |
217 | gtk_action_muxer_query_action (GTK_ACTION_MUXER (owner), action_name: name, |
218 | NULL, NULL, NULL, NULL, state: &state); |
219 | else |
220 | state = NULL; |
221 | |
222 | if (state) |
223 | { |
224 | char *state_string; |
225 | |
226 | state_string = g_variant_print (value: state, FALSE); |
227 | gtk_label_set_label (GTK_LABEL (label), str: state_string); |
228 | g_free (mem: state_string); |
229 | g_variant_unref (value: state); |
230 | } |
231 | else |
232 | gtk_label_set_label (GTK_LABEL (label), str: "" ); |
233 | } |
234 | |
235 | static void |
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 (widget: editor, css_class: "cell" ); |
243 | gtk_list_item_set_child (self: list_item, child: editor); |
244 | } |
245 | |
246 | static void |
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 (self: list_item); |
256 | editor = gtk_list_item_get_child (self: list_item); |
257 | |
258 | owner = action_holder_get_owner (holder: ACTION_HOLDER (ptr: item)); |
259 | name = action_holder_get_name (holder: ACTION_HOLDER (ptr: item)); |
260 | |
261 | gtk_inspector_action_editor_set (GTK_INSPECTOR_ACTION_EDITOR (editor), |
262 | owner, |
263 | name); |
264 | } |
265 | |
266 | static void |
267 | add_group (GtkInspectorActions *sl, |
268 | GActionGroup *group) |
269 | { |
270 | int i; |
271 | char **names; |
272 | |
273 | names = g_action_group_list_actions (action_group: group); |
274 | for (i = 0; names[i]; i++) |
275 | action_added (G_OBJECT (group), action_name: names[i], sl); |
276 | g_strfreev (str_array: names); |
277 | } |
278 | |
279 | static void |
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), action_name: names[i], sl); |
289 | g_strfreev (str_array: names); |
290 | } |
291 | |
292 | static gboolean |
293 | reload (GtkInspectorActions *sl) |
294 | { |
295 | gboolean loaded = FALSE; |
296 | |
297 | g_object_unref (object: 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 (self: sl->sorted, model: G_LIST_MODEL (ptr: sl->actions)); |
318 | |
319 | return loaded; |
320 | } |
321 | |
322 | static void |
323 | refresh_all (GtkInspectorActions *sl) |
324 | { |
325 | reload (sl); |
326 | } |
327 | |
328 | void |
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 (self: page, FALSE); |
339 | |
340 | g_set_object (&sl->object, object); |
341 | |
342 | gtk_column_view_sort_by_column (GTK_COLUMN_VIEW (sl->list), column: sl->name, direction: GTK_SORT_ASCENDING); |
343 | loaded = reload (sl); |
344 | gtk_stack_page_set_visible (self: page, visible: loaded); |
345 | } |
346 | |
347 | static void |
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, v_object: 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 |
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 * |
388 | holder_name (gpointer item) |
389 | { |
390 | return g_strdup (str: action_holder_get_name (holder: ACTION_HOLDER (ptr: item))); |
391 | } |
392 | |
393 | static void |
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 (ptr: gtk_string_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_STRING, |
404 | NULL, |
405 | n_params: 0, NULL, |
406 | callback_func: (GCallback)holder_name, |
407 | NULL, NULL))); |
408 | gtk_column_view_column_set_sorter (self: sl->name, sorter); |
409 | g_object_unref (object: 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 (ptr: gtk_no_selection_new (g_object_ref (G_LIST_MODEL (sl->sorted)))); |
415 | gtk_column_view_set_model (GTK_COLUMN_VIEW (sl->list), model: GTK_SELECTION_MODEL (ptr: model)); |
416 | g_object_unref (object: model); |
417 | } |
418 | |
419 | static void |
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 (widget: child); |
431 | |
432 | G_OBJECT_CLASS (gtk_inspector_actions_parent_class)->dispose (object); |
433 | } |
434 | |
435 | static void |
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 (oclass: object_class, property_id: PROP_BUTTON, |
447 | pspec: g_param_spec_object (name: "button" , NULL, NULL, |
448 | GTK_TYPE_WIDGET, flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
449 | |
450 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/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 | |