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