1 /*
2 * glade-gtk-popovermenu.c - GladeWidgetAdaptor for GtkPopoverMenu
3 *
4 * Copyright (C) 2014 Red Hat, Inc
5 *
6 * Authors:
7 * Matthias Clasen <mclasen@redhat.com>
8 *
9 * This library is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24 #include <config.h>
25 #include <glib/gi18n-lib.h>
26 #include <gladeui/glade.h>
27
28 #include "glade-popover-menu-editor.h"
29
30 static void
glade_gtk_popover_menu_parse_finished(GladeProject * project,GObject * object)31 glade_gtk_popover_menu_parse_finished (GladeProject * project,
32 GObject * object)
33 {
34 GladeWidget *gbox;
35 gint submenus;
36
37 gbox = glade_widget_get_from_gobject (object);
38 glade_widget_property_get (gbox, "submenus", &submenus);
39 glade_widget_property_set (gbox, "submenus", submenus);
40 }
41
42 static void
glade_gtk_popover_menu_selection_changed(GladeProject * project,GladeWidget * gwidget)43 glade_gtk_popover_menu_selection_changed (GladeProject * project,
44 GladeWidget * gwidget)
45 {
46 GList *list;
47 GtkWidget *page, *sel_widget;
48 GtkWidget *popover = GTK_WIDGET (glade_widget_get_object (gwidget));
49
50 if ((list = glade_project_selection_get (project)) != NULL &&
51 g_list_length (list) == 1)
52 {
53 sel_widget = list->data;
54
55 if (GTK_IS_WIDGET (sel_widget) &&
56 gtk_widget_is_ancestor (sel_widget, popover))
57 {
58 GList *children, *l;
59
60 children = gtk_container_get_children (GTK_CONTAINER (popover));
61 for (l = children; l; l = l->next)
62 {
63 page = l->data;
64 if (sel_widget == page ||
65 gtk_widget_is_ancestor (sel_widget, page))
66 {
67 gint position;
68 glade_widget_property_get (glade_widget_get_from_gobject (page), "position", &position);
69 glade_widget_property_set (glade_widget_get_from_gobject (popover), "current", position);
70 break;
71 }
72 }
73 g_list_free (children);
74 }
75 }
76 }
77
78 static void
glade_gtk_popover_menu_project_changed(GladeWidget * gwidget,GParamSpec * pspec,gpointer userdata)79 glade_gtk_popover_menu_project_changed (GladeWidget * gwidget,
80 GParamSpec * pspec,
81 gpointer userdata)
82 {
83 GladeProject * project = glade_widget_get_project (gwidget);
84 GladeProject * old_project = g_object_get_data (G_OBJECT (gwidget), "popover-menu-project-ptr");
85
86 if (old_project)
87 g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
88 G_CALLBACK (glade_gtk_popover_menu_selection_changed),
89 gwidget);
90
91 if (project)
92 g_signal_connect (G_OBJECT (project), "selection-changed",
93 G_CALLBACK (glade_gtk_popover_menu_selection_changed),
94 gwidget);
95
96 g_object_set_data (G_OBJECT (gwidget), "popover-menu-project-ptr", project);
97 }
98
99 static gint
get_visible_child(GtkPopoverMenu * popover,GtkWidget ** visible_child)100 get_visible_child (GtkPopoverMenu *popover, GtkWidget **visible_child)
101 {
102 gchar *visible;
103 GList *children, *l;
104 gint ret, i;
105
106 ret = -1;
107
108 g_object_get (G_OBJECT (popover), "visible-submenu", &visible, NULL);
109 children = gtk_container_get_children (GTK_CONTAINER (popover));
110 for (l = children, i = 0; visible && l; l = l->next, i++)
111 {
112 GtkWidget *child = l->data;
113 gchar *name;
114 gboolean found;
115
116 gtk_container_child_get (GTK_CONTAINER (popover), child, "submenu", &name, NULL);
117 found = name != NULL && !strcmp (visible, name);
118 g_free (name);
119 if (found)
120 {
121 if (visible_child)
122 *visible_child = child;
123 ret = i;
124 break;
125 }
126 }
127 g_list_free (children);
128 g_free (visible);
129
130 return ret;
131 }
132
133 static void
glade_gtk_popover_menu_visible_submenu_changed(GObject * popover,GParamSpec * pspec,gpointer data)134 glade_gtk_popover_menu_visible_submenu_changed (GObject *popover,
135 GParamSpec *pspec,
136 gpointer data)
137 {
138 GladeWidget *gwidget = glade_widget_get_from_gobject (popover);
139 GladeProject *project = glade_widget_get_project (gwidget);
140 gint current;
141 GList *list;
142 GtkWidget *visible_child;
143
144 current = get_visible_child (GTK_POPOVER_MENU (popover), &visible_child);
145 glade_widget_property_set (gwidget, "current", current);
146
147 if ((list = glade_project_selection_get (project)) != NULL &&
148 list->next == NULL)
149 {
150 GObject *selected = list->data;
151
152 if (GTK_IS_WIDGET (selected) &&
153 gtk_widget_is_ancestor (GTK_WIDGET (selected), GTK_WIDGET (popover)) &&
154 (GtkWidget*)selected != visible_child &&
155 !gtk_widget_is_ancestor (GTK_WIDGET (selected), GTK_WIDGET (visible_child)))
156 {
157 glade_project_selection_clear (project, TRUE);
158 }
159 }
160 }
161
162 void
glade_gtk_popover_menu_post_create(GladeWidgetAdaptor * adaptor,GObject * container,GladeCreateReason reason)163 glade_gtk_popover_menu_post_create (GladeWidgetAdaptor *adaptor,
164 GObject *container,
165 GladeCreateReason reason)
166 {
167 GladeWidget *parent = glade_widget_get_from_gobject (container);
168 GladeProject *project = glade_widget_get_project (parent);
169
170 if (reason == GLADE_CREATE_LOAD)
171 g_signal_connect (project, "parse-finished",
172 G_CALLBACK (glade_gtk_popover_menu_parse_finished),
173 container);
174
175 g_signal_connect (G_OBJECT (parent), "notify::project",
176 G_CALLBACK (glade_gtk_popover_menu_project_changed), NULL);
177
178 glade_gtk_popover_menu_project_changed (parent, NULL, NULL);
179
180 g_signal_connect (container, "notify::visible-submenu",
181 G_CALLBACK (glade_gtk_popover_menu_visible_submenu_changed), NULL);
182
183 GWA_GET_CLASS (GTK_TYPE_POPOVER)->post_create (adaptor, container, reason);
184 }
185
186 void
glade_gtk_popover_menu_add_child(GladeWidgetAdaptor * adaptor,GObject * parent,GObject * child)187 glade_gtk_popover_menu_add_child (GladeWidgetAdaptor *adaptor,
188 GObject *parent,
189 GObject *child)
190 {
191 gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (child));
192
193 if (!glade_widget_superuser ())
194 {
195 GladeWidget *gbox;
196 gint submenus;
197
198 gbox = glade_widget_get_from_gobject (parent);
199
200 glade_widget_property_get (gbox, "submenus", &submenus);
201 glade_widget_property_set (gbox, "submenus", submenus);
202 }
203 }
204
205 void
glade_gtk_popover_menu_remove_child(GladeWidgetAdaptor * adaptor,GObject * parent,GObject * child)206 glade_gtk_popover_menu_remove_child (GladeWidgetAdaptor *adaptor,
207 GObject *parent,
208 GObject *child)
209 {
210 gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (child));
211
212 if (!glade_widget_superuser ())
213 {
214 GladeWidget *gbox;
215 gint submenus;
216
217 gbox = glade_widget_get_from_gobject (parent);
218
219 glade_widget_property_get (gbox, "submenus", &submenus);
220 glade_widget_property_set (gbox, "submenus", submenus);
221 }
222 }
223
224 void
glade_gtk_popover_menu_replace_child(GladeWidgetAdaptor * adaptor,GObject * container,GObject * current,GObject * new_widget)225 glade_gtk_popover_menu_replace_child (GladeWidgetAdaptor * adaptor,
226 GObject * container,
227 GObject * current,
228 GObject * new_widget)
229 {
230 gchar *visible;
231 gchar *name;
232 gint position;
233 GladeWidget *gwidget;
234
235 g_object_get (G_OBJECT (container), "visible-submenu", &visible, NULL);
236
237 gtk_container_child_get (GTK_CONTAINER (container),
238 GTK_WIDGET (current),
239 "submenu", &name,
240 "position", &position,
241 NULL);
242
243 gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (new_widget));
244 gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current));
245
246 gtk_container_child_set (GTK_CONTAINER (container),
247 GTK_WIDGET (new_widget),
248 "submenu", name,
249 "position", position,
250 NULL);
251
252 g_object_set (G_OBJECT (container), "visible-submenu", visible, NULL);
253
254 gwidget = glade_widget_get_from_gobject (new_widget);
255 if (gwidget)
256 {
257 glade_widget_pack_property_set (gwidget, "submenu", name);
258 glade_widget_pack_property_set (gwidget, "position", position);
259 }
260
261 g_free (visible);
262 g_free (name);
263 }
264
265 typedef struct {
266 gint size;
267 gboolean include_placeholders;
268 } ChildData;
269
270 static void
count_child(GtkWidget * child,gpointer data)271 count_child (GtkWidget *child, gpointer data)
272 {
273 ChildData *cdata = data;
274
275 if (cdata->include_placeholders || !GLADE_IS_PLACEHOLDER (child))
276 cdata->size++;
277 }
278
279 static gint
count_children(GtkContainer * container,gboolean include_placeholders)280 count_children (GtkContainer *container,
281 gboolean include_placeholders)
282 {
283 ChildData data;
284
285 data.size = 0;
286 data.include_placeholders = include_placeholders;
287 gtk_container_foreach (container, count_child, &data);
288 return data.size;
289 }
290
291 static gchar *
get_unused_name(GtkPopoverMenu * popover)292 get_unused_name (GtkPopoverMenu *popover)
293 {
294 gint i;
295 gchar *name = NULL;
296 GList *children, *l;
297 gboolean exists;
298
299 children = gtk_container_get_children (GTK_CONTAINER (popover));
300
301 i = g_list_length (children);
302 while (1)
303 {
304 name = g_strdup_printf ("submenu%d", i);
305 exists = FALSE;
306 for (l = children; l && !exists; l = l->next)
307 {
308 gchar *submenu;
309 gtk_container_child_get (GTK_CONTAINER (popover), GTK_WIDGET (l->data),
310 "submenu", &submenu, NULL);
311 if (!strcmp (submenu, name))
312 exists = TRUE;
313 g_free (submenu);
314 }
315 if (!exists)
316 break;
317
318 g_free (name);
319 i++;
320 }
321
322 g_list_free (children);
323
324 return name;
325 }
326
327 static void
glade_gtk_popover_menu_set_submenus(GObject * object,const GValue * value)328 glade_gtk_popover_menu_set_submenus (GObject * object,
329 const GValue * value)
330 {
331 GladeWidget *gbox;
332 GtkWidget *child;
333 gint new_size, i;
334 gint old_size;
335 gchar *name;
336 gint page;
337
338 new_size = g_value_get_int (value);
339 old_size = count_children (GTK_CONTAINER (object), TRUE);
340
341 if (old_size == new_size)
342 return;
343 else if (old_size < new_size)
344 {
345 for (i = old_size; i < new_size; i++)
346 {
347 name = get_unused_name (GTK_POPOVER_MENU (object));
348 child = glade_placeholder_new ();
349 gtk_container_add_with_properties (GTK_CONTAINER (object), child,
350 "submenu", name, NULL);
351 g_free (name);
352 }
353 }
354 else
355 {
356 GList *children, *l;
357
358 children = gtk_container_get_children (GTK_CONTAINER (object));
359 for (l = g_list_last (children); l; l = l->prev)
360 {
361 if (old_size <= new_size)
362 break;
363
364 child = l->data;
365 if (GLADE_IS_PLACEHOLDER (child))
366 {
367 gtk_container_remove (GTK_CONTAINER (object), child);
368 old_size--;
369 }
370 }
371 }
372
373 gbox = glade_widget_get_from_gobject (object);
374 glade_widget_property_get (gbox, "current", &page);
375 glade_widget_property_set (gbox, "current", page);
376 }
377
378 static void
glade_gtk_popover_menu_set_current(GObject * object,const GValue * value)379 glade_gtk_popover_menu_set_current (GObject *object,
380 const GValue *value)
381 {
382 gint new_page;
383 GList *children;
384 GtkWidget *child;
385 gchar *submenu;
386
387 new_page = g_value_get_int (value);
388 children = gtk_container_get_children (GTK_CONTAINER (object));
389 child = g_list_nth_data (children, new_page);
390 if (child)
391 {
392 gtk_container_child_get (GTK_CONTAINER (object), child,
393 "submenu", &submenu,
394 NULL);
395 gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (object), submenu);
396 g_free (submenu);
397 }
398
399 g_list_free (children);
400 }
401
402 void
glade_gtk_popover_menu_set_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,const GValue * value)403 glade_gtk_popover_menu_set_property (GladeWidgetAdaptor * adaptor,
404 GObject * object,
405 const gchar * id,
406 const GValue * value)
407 {
408 if (!strcmp (id, "submenus"))
409 glade_gtk_popover_menu_set_submenus (object, value);
410 else if (!strcmp (id, "current"))
411 glade_gtk_popover_menu_set_current (object, value);
412 else
413 GWA_GET_CLASS (GTK_TYPE_POPOVER)->set_property (adaptor, object, id, value);
414 }
415
416 void
glade_gtk_popover_menu_get_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,GValue * value)417 glade_gtk_popover_menu_get_property (GladeWidgetAdaptor * adaptor,
418 GObject * object,
419 const gchar * id,
420 GValue * value)
421 {
422 if (!strcmp (id, "submenus"))
423 {
424 g_value_reset (value);
425 g_value_set_int (value, count_children (GTK_CONTAINER (object), TRUE));
426 }
427 else if (!strcmp (id, "current"))
428 {
429 g_value_reset (value);
430 g_value_set_int (value, get_visible_child (GTK_POPOVER_MENU (object), NULL));
431 }
432 else
433 GWA_GET_CLASS (GTK_TYPE_POPOVER)->get_property (adaptor, object, id, value);
434 }
435
436 static gboolean
glade_gtk_popover_menu_verify_submenus(GObject * object,const GValue * value)437 glade_gtk_popover_menu_verify_submenus (GObject * object,
438 const GValue *value)
439 {
440 gint new_size, old_size;
441
442 new_size = g_value_get_int (value);
443 old_size = count_children (GTK_CONTAINER (object), FALSE);
444
445 return old_size <= new_size;
446 }
447
448 static gboolean
glade_gtk_popover_menu_verify_current(GObject * object,const GValue * value)449 glade_gtk_popover_menu_verify_current (GObject *object,
450 const GValue *value)
451 {
452 gint current;
453 gint submenus;
454
455 current = g_value_get_int (value);
456 submenus = count_children (GTK_CONTAINER (object), TRUE);
457
458 return 0 <= current && current < submenus;
459 }
460
461 gboolean
glade_gtk_popover_menu_verify_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,const GValue * value)462 glade_gtk_popover_menu_verify_property (GladeWidgetAdaptor * adaptor,
463 GObject * object,
464 const gchar * id,
465 const GValue * value)
466 {
467 if (!strcmp (id, "submenus"))
468 return glade_gtk_popover_menu_verify_submenus (object, value);
469 else if (!strcmp (id, "current"))
470 return glade_gtk_popover_menu_verify_current (object, value);
471 else if (GWA_GET_CLASS (GTK_TYPE_POPOVER)->verify_property)
472 return GWA_GET_CLASS (GTK_TYPE_POPOVER)->verify_property (adaptor, object, id, value);
473
474 return TRUE;
475 }
476
477 static void
update_position(GtkWidget * widget,gpointer data)478 update_position (GtkWidget *widget, gpointer data)
479 {
480 GtkContainer *parent = data;
481 GladeWidget *gwidget;
482 gint position;
483
484 gwidget = glade_widget_get_from_gobject (widget);
485 if (gwidget)
486 {
487 gtk_container_child_get (parent, widget, "position", &position, NULL);
488 glade_widget_pack_property_set (gwidget, "position", position);
489 }
490 }
491
492 static void
glade_gtk_popover_menu_set_child_position(GObject * container,GObject * child,GValue * value)493 glade_gtk_popover_menu_set_child_position (GObject * container,
494 GObject * child,
495 GValue * value)
496 {
497 static gboolean recursion = FALSE;
498 gint new_position, old_position;
499 gchar *visible_child;
500 GladeWidget *gbox;
501
502 g_object_get (container, "visible-submenu", &visible_child, NULL);
503
504 if (recursion)
505 return;
506
507 gtk_container_child_get (GTK_CONTAINER (container), GTK_WIDGET (child), "position", &old_position, NULL);
508 new_position = g_value_get_int (value);
509
510 if (old_position != new_position)
511 {
512 recursion = TRUE;
513 gtk_container_child_set (GTK_CONTAINER (container), GTK_WIDGET (child),
514 "position", new_position,
515 NULL);
516 gtk_container_forall (GTK_CONTAINER (container), update_position, container);
517 recursion = FALSE;
518 }
519
520 g_object_set (container, "visible-submenu", visible_child, NULL);
521 g_free (visible_child);
522
523 gbox = glade_widget_get_from_gobject (container);
524 glade_widget_pack_property_set (gbox, "visible-submenu", get_visible_child (GTK_POPOVER_MENU (container), NULL));
525 }
526
527 void
glade_gtk_popover_menu_set_child_property(GladeWidgetAdaptor * adaptor,GObject * container,GObject * child,const gchar * id,GValue * value)528 glade_gtk_popover_menu_set_child_property (GladeWidgetAdaptor * adaptor,
529 GObject * container,
530 GObject * child,
531 const gchar * id,
532 GValue * value)
533 {
534 if (!strcmp (id, "position"))
535 glade_gtk_popover_menu_set_child_position (container, child, value);
536 else if (!strcmp (id, "submenu"))
537 gtk_container_child_set_property (GTK_CONTAINER (container),
538 GTK_WIDGET (child), id, value);
539 else
540 GWA_GET_CLASS (GTK_TYPE_POPOVER)->child_set_property (adaptor, container, child, id, value);
541 }
542
543 void
glade_gtk_popover_menu_get_child_property(GladeWidgetAdaptor * adaptor,GObject * container,GObject * child,const gchar * id,GValue * value)544 glade_gtk_popover_menu_get_child_property (GladeWidgetAdaptor * adaptor,
545 GObject * container,
546 GObject * child,
547 const gchar * id,
548 GValue * value)
549 {
550 gtk_container_child_get_property (GTK_CONTAINER (container),
551 GTK_WIDGET (child), id, value);
552 }
553
554 GladeEditable *
glade_gtk_popover_menu_create_editable(GladeWidgetAdaptor * adaptor,GladeEditorPageType type)555 glade_gtk_popover_menu_create_editable (GladeWidgetAdaptor * adaptor,
556 GladeEditorPageType type)
557 {
558 if (type == GLADE_PAGE_GENERAL)
559 return (GladeEditable *) glade_popover_menu_editor_new ();
560 else
561 return GWA_GET_CLASS (GTK_TYPE_POPOVER)->create_editable (adaptor, type);
562 }
563
564