aboutsummaryrefslogtreecommitdiff
path: root/libqmenumodel/src/gtk/gtkmenutracker.c
diff options
context:
space:
mode:
Diffstat (limited to 'libqmenumodel/src/gtk/gtkmenutracker.c')
-rw-r--r--libqmenumodel/src/gtk/gtkmenutracker.c245
1 files changed, 184 insertions, 61 deletions
diff --git a/libqmenumodel/src/gtk/gtkmenutracker.c b/libqmenumodel/src/gtk/gtkmenutracker.c
index 8b72965..95e08bc 100644
--- a/libqmenumodel/src/gtk/gtkmenutracker.c
+++ b/libqmenumodel/src/gtk/gtkmenutracker.c
@@ -12,9 +12,7 @@
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
@@ -23,7 +21,7 @@
#include "gtkmenutracker.h"
-/**
+/*< private >
* SECTION:gtkmenutracker
* @Title: GtkMenuTracker
* @Short_description: A helper class for interpreting #GMenuModel
@@ -48,11 +46,11 @@
*
* Certain properties on the #GtkMenuTrackerItem are mutable, and you must
* listen for changes in the item. For more details, see the documentation
- * for #GtkMenuTrackerItem along with https://live.gnome.org/GApplication/GMenuModel.
+ * for #GtkMenuTrackerItem along with https://wiki.gnome.org/Projects/GLib/GApplication/GMenuModel.
*
* The idea of @with_separators is for special cases where menu models may
* be tracked in places where separators are not available, like in toplevel
- * "File", "Edit" menu bars. Ignoring separator items is wrong, as #GtkMenuTracker
+ * "File", “Edit” menu bars. Ignoring separator items is wrong, as #GtkMenuTracker
* expects the position to change, so we must tell #GtkMenuTracker to ignore
* separators itself.
*/
@@ -62,6 +60,8 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
struct _GtkMenuTracker
{
GtkActionObservable *observable;
+ guint merge_sections : 1;
+ guint mac_os_mode : 1;
GtkMenuTrackerInsertFunc insert_func;
GtkMenuTrackerRemoveFunc remove_func;
gpointer user_data;
@@ -71,12 +71,14 @@ struct _GtkMenuTracker
struct _GtkMenuTrackerSection
{
- GMenuModel *model;
+ gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */
GSList *items;
gchar *action_namespace;
+ guint separator_label : 1;
guint with_separators : 1;
guint has_separator : 1;
+ guint is_fake : 1;
gulong handler;
};
@@ -84,6 +86,7 @@ struct _GtkMenuTrackerSection
static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
GMenuModel *model,
gboolean with_separators,
+ gboolean separator_label,
gint offset,
const gchar *action_namespace,
GPtrArray *items_already_created);
@@ -91,7 +94,7 @@ static void gtk_menu_tracker_section_free (GtkMenuTrackerS
static GtkMenuTrackerSection *
gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
- GMenuModel *model,
+ gpointer model,
gint *offset)
{
GSList *item;
@@ -119,7 +122,7 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
(*offset)++;
}
- return FALSE;
+ return NULL;
}
/* this is responsible for syncing the showing of a separator for a
@@ -127,7 +130,7 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
*
* we only ever show separators if we have _actual_ children (ie: we do
* not show a separator if the section contains only empty child
- * sections). it's difficult to determine this on-the-fly, so we have
+ * sections). it’s difficult to determine this on-the-fly, so we have
* this separate function to come back later and figure it out.
*
* 'section' is that section.
@@ -142,11 +145,11 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
*
* could_have_separator is true in two situations:
*
- * - our parent section had with_separators defined and we are not the
- * first section (ie: we should add a separator if we have content in
+ * - our parent section had with_separators defined and there are items
+ * before us (ie: we should add a separator if we have content in
* order to divide us from the items above)
*
- * - if we had a 'label' attribute set for this section
+ * - if we had a “label” attribute set for this section
*
* parent_model and parent_index are passed in so that we can give them
* to the insertion callback so that it can see the label (and anything
@@ -168,6 +171,7 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
gboolean should_have_separator;
gint n_items = 0;
GSList *item;
+ GPtrArray *items = g_ptr_array_new ();
gint i = 0;
for (item = section->items; item; item = item->next)
@@ -176,13 +180,15 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
if (subsection)
{
- gboolean could_have_separator;
+ gboolean separator;
- could_have_separator = (section->with_separators && i > 0) ||
- g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL);
+ separator = (section->with_separators && n_items > 0) || subsection->separator_label;
+ /* Only pass the parent_model and parent_index in case they may be used to create the separator. */
n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
- could_have_separator, section->model, i);
+ separator,
+ separator ? section->model : NULL,
+ separator ? i : 0);
}
else
n_items++;
@@ -190,19 +196,18 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
i++;
}
- should_have_separator = could_have_separator && n_items != 0;
+ should_have_separator = !section->is_fake && could_have_separator && n_items != 0;
if (should_have_separator > section->has_separator)
{
/* Add a separator */
- GtkMenuTrackerItem *item;
- GPtrArray *items = g_ptr_array_new ();
+ GtkMenuTrackerItem *separator;
- item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
- g_ptr_array_add (items, (gpointer) item);
+ separator = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, FALSE, NULL, TRUE);
+ g_ptr_array_add (items, (gpointer) separator);
(* tracker->insert_func) (items, offset, tracker->user_data);
g_ptr_array_unref (items);
- g_object_unref (item);
+ g_object_unref (separator);
section->has_separator = TRUE;
}
@@ -218,6 +223,41 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
return n_items;
}
+static void
+gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkMenuTracker *tracker = user_data;
+ GtkMenuTrackerSection *section;
+ gboolean is_now_visible;
+ gboolean was_visible;
+ gint offset = 0;
+
+ is_now_visible = gtk_menu_tracker_item_get_is_visible (item);
+
+ /* remember: the item is our model */
+ section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset);
+
+ was_visible = section->items != NULL;
+
+ if (is_now_visible == was_visible)
+ return;
+
+ if (is_now_visible)
+ {
+ section->items = g_slist_prepend (NULL, NULL);
+ (* tracker->insert_func) (section->model, offset, tracker->user_data);
+ }
+ else
+ {
+ section->items = g_slist_delete_link (section->items, section->items);
+ (* tracker->remove_func) (offset, 1, tracker->user_data);
+ }
+
+ gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
+}
+
static gint
gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section)
{
@@ -258,6 +298,7 @@ gtk_menu_tracker_remove_items (GtkMenuTracker *tracker,
n = gtk_menu_tracker_section_measure (subsection);
gtk_menu_tracker_section_free (subsection);
+ while (n--)
n_total_items += n;
}
@@ -275,28 +316,32 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
GMenuModel *model,
gint position,
gint n_items,
- GPtrArray *items_already_created
- )
+ GPtrArray *items_already_created)
{
- GPtrArray *items;
- if (items_already_created)
- {
- items = items_already_created;
- }
- else
- {
- items = g_ptr_array_new ();
- }
+ GPtrArray *items;
+ if (items_already_created)
+ {
+ items = items_already_created;
+ }
+ else
+ {
+ items = g_ptr_array_new ();
+ }
while (n_items--)
{
GMenuModel *submenu;
submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
g_assert (submenu != model);
- if (submenu != NULL)
+
+ if (submenu != NULL && tracker->merge_sections)
{
GtkMenuTrackerSection *subsection;
gchar *action_namespace = NULL;
+ gboolean has_label;
+
+ has_label = g_menu_model_get_item_attribute (model, position + n_items,
+ G_MENU_ATTRIBUTE_LABEL, "s", NULL);
g_menu_model_get_item_attribute (model, position + n_items,
G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
@@ -306,11 +351,11 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
gchar *namespace;
namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
- subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace, items_already_created);
+ subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace, items_already_created);
g_free (namespace);
}
else
- subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace, items_already_created);
+ subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace, items_already_created);
*change_point = g_slist_prepend (*change_point, subsection);
g_free (action_namespace);
@@ -321,22 +366,76 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
GtkMenuTrackerItem *item;
item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
- section->action_namespace, FALSE);
- g_ptr_array_insert (items, 0, (gpointer) item);
+ tracker->mac_os_mode,
+ section->action_namespace, submenu != NULL);
+
+ /* In the case that the item may disappear we handle that by
+ * treating the item that we just created as being its own
+ * subsection. This happens as so:
+ *
+ * - the subsection is created without the possibility of
+ * showing a separator
+ *
+ * - the subsection will have either 0 or 1 item in it at all
+ * times: either the shown item or not (in the case it is
+ * hidden)
+ *
+ * - the created item acts as the "model" for this section
+ * and we use its "visiblity-changed" signal in the same
+ * way that we use the "items-changed" signal from a real
+ * GMenuModel
+ *
+ * We almost never use the '->model' stored in the section for
+ * anything other than lookups and for dropped the ref and
+ * disconnecting the signal when we destroy the menu, and we
+ * need to do exactly those things in this case as well.
+ *
+ * The only other thing that '->model' is used for is in the
+ * case that we want to show a separator, but we will never do
+ * that because separators are not shown for this fake section.
+ */
+ if (gtk_menu_tracker_item_may_disappear (item))
+ {
+ GtkMenuTrackerSection *fake_section;
+
+ fake_section = g_slice_new0 (GtkMenuTrackerSection);
+ fake_section->is_fake = TRUE;
+ fake_section->model = g_object_ref (item);
+ fake_section->handler = g_signal_connect (item, "notify::is-visible",
+ G_CALLBACK (gtk_menu_tracker_item_visibility_changed),
+ tracker);
+ *change_point = g_slist_prepend (*change_point, fake_section);
+
+ if (gtk_menu_tracker_item_get_is_visible (item))
+ {
+ (* tracker->insert_func) (items, offset, tracker->user_data);
+ fake_section->items = g_slist_prepend (NULL, NULL);
+ }
+ }
+ else
+ {
+ /* In the normal case, we store NULL in the linked list.
+ * The measurement and lookup code count NULL always as
+ * exactly 1: an item that will always be there.
+ */
+ g_ptr_array_insert (items, 0, (gpointer) item);
+ *change_point = g_slist_prepend (*change_point, NULL);
+ }
- *change_point = g_slist_prepend (*change_point, NULL);
+ g_object_unref (item);
}
}
+
if (!items_already_created)
- {
- if (items->len)
- {
- (* tracker->insert_func) (items, offset, tracker->user_data);
- for (gint i = 0; i < items->len; ++i)
- g_object_unref(g_ptr_array_index(items, i));
- }
- g_ptr_array_unref (items);
- }
+ {
+ if (items->len)
+ {
+ (* tracker->insert_func) (items, offset, tracker->user_data);
+ for (gint i = 0; i < items->len; ++i)
+ g_object_unref(g_ptr_array_index(items, i));
+ }
+ g_ptr_array_unref (items);
+ }
}
static void
@@ -406,6 +505,7 @@ static GtkMenuTrackerSection *
gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
GMenuModel *model,
gboolean with_separators,
+ gboolean separator_label,
gint offset,
const gchar *action_namespace,
GPtrArray *items_already_created)
@@ -416,6 +516,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
section->model = g_object_ref (model);
section->with_separators = with_separators;
section->action_namespace = g_strdup (action_namespace);
+ section->separator_label = separator_label;
gtk_menu_tracker_add_items (tracker, section, &section->items, offset, model, 0, g_menu_model_get_n_items (model), items_already_created);
section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker);
@@ -428,6 +529,10 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
* @model: the model to flatten
* @with_separators: if the toplevel should have separators (ie: TRUE
* for menus, FALSE for menubars)
+ * @merge_sections: if sections should have their items merged in the
+ * usual way or reported only as separators (which can be queried to
+ * manually handle the items)
+ * @mac_os_mode: if this is on behalf of the Mac OS menubar
* @action_namespace: the passed-in action namespace
* @insert_func: insert callback
* @remove_func: remove callback
@@ -458,7 +563,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
* information about the menu item to insert. @action_namespace is the
* action namespace that actions referred to from that item should place
* themselves in. Note that if the item is a submenu and the
- * "action-namespace" attribute is defined on the item, it will _not_ be
+ * “action-namespace” attribute is defined on the item, it will _not_ be
* applied to the @action_namespace argument as it is meant for the
* items inside of the submenu, not the submenu item itself.
*
@@ -466,7 +571,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
* separator. @model and @item_index will still be meaningfully set in
* this case -- to the section menu item corresponding to the separator.
* This is useful if the section specifies a label, for example. If
- * there is an "action-namespace" attribute on this menu item then it
+ * there is an “action-namespace” attribute on this menu item then it
* should be ignored by the consumer because #GtkMenuTracker has already
* handled it.
*
@@ -478,7 +583,10 @@ GtkMenuTracker *
gtk_menu_tracker_new (GtkActionObservable *observable,
GMenuModel *model,
gboolean with_separators,
+ gboolean merge_sections,
+ gboolean mac_os_mode,
const gchar *action_namespace,
+ GPtrArray *items_already_created,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
gpointer user_data)
@@ -486,28 +594,43 @@ gtk_menu_tracker_new (GtkActionObservable *observable,
GtkMenuTracker *tracker;
tracker = g_slice_new (GtkMenuTracker);
+ tracker->merge_sections = merge_sections;
+ tracker->mac_os_mode = mac_os_mode;
tracker->observable = g_object_ref (observable);
tracker->insert_func = insert_func;
tracker->remove_func = remove_func;
tracker->user_data = user_data;
- tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace, NULL);
+ tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace, NULL);
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
return tracker;
}
GtkMenuTracker *
-gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
- GtkMenuTrackerInsertFunc insert_func,
- GtkMenuTrackerRemoveFunc remove_func,
- gpointer user_data)
+gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem *item,
+ const gchar *link_name,
+ gboolean merge_sections,
+ gboolean mac_os_mode,
+ GtkMenuTrackerInsertFunc insert_func,
+ GtkMenuTrackerRemoveFunc remove_func,
+ gpointer user_data)
{
- return gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item),
- _gtk_menu_tracker_item_get_submenu (item),
- TRUE,
- _gtk_menu_tracker_item_get_submenu_namespace (item),
- insert_func, remove_func, user_data);
+ GtkMenuTracker *tracker;
+ GMenuModel *submenu;
+ gchar *namespace;
+
+ submenu = _gtk_menu_tracker_item_get_link (item, link_name);
+ namespace = _gtk_menu_tracker_item_get_link_namespace (item);
+
+ tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu,
+ TRUE, merge_sections, mac_os_mode,
+ namespace, NULL, insert_func, remove_func, user_data);
+
+ g_object_unref (submenu);
+ g_free (namespace);
+
+ return tracker;
}
/*< private >