app: new GimpMenuModel object, which is our own implementation of GMenuModel.

Since GAction don't have labels or visibility, it is up to every application to
fill the GMenuModel with such infos. In my previous implementation, I was simply
handling these in GimpMenuShell subclasses (GimpMenu, GimpToolbar and
GimpMenuBar) since we need them for tooltip support (unavailable from GMenu).

Nevertheless there are cases where we want to use GTK API directly with a
GMenuModel, in particular with gtk_application_set_menubar(). This is necessary
to handle the macOS specific code path, where we don't want our usual menu bar.
This OS has its own display of a software menu bar, directly in the desktop GUI.
See !558 for some screenshots of this.

So this commit moves around some code away from GimpMenuShell subclasses into
the new GimpMenuModel class. Now we use this new class instead of simpler GMenu
objects. It handles syncing with GimpAction-s, auto-updating labels, visibility
and sensitivity, as well as adding custom items (plug-in actions, recently
opened files, opened images, etc.).
This commit is contained in:
Jehan 2023-03-06 21:40:13 +01:00
parent 475bd45d32
commit 1637dd1cd4
16 changed files with 1097 additions and 427 deletions

View File

@ -348,7 +348,7 @@ gimp_image_window_constructed (GObject *object)
GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window);
GimpMenuFactory *menu_factory;
GimpGuiConfig *config;
GMenuModel *model;
GimpMenuModel *model;
G_OBJECT_CLASS (parent_class)->constructed (object);
@ -386,8 +386,7 @@ gimp_image_window_constructed (GObject *object)
gtk_widget_show (private->main_vbox);
/* Create the menubar */
model = gimp_ui_manager_get_model (private->menubar_manager,
"/image-menubar");
model = gimp_ui_manager_get_model (private->menubar_manager, "/image-menubar");
#ifdef GDK_WINDOWING_QUARTZ
/* macOS has its native menubar system, and this should support it. It means
@ -395,7 +394,8 @@ gimp_image_window_constructed (GObject *object)
* TODO: Since the .ui file has no title/labels, I should edit the model to
* extract titles from actions.
*/
gtk_application_set_menubar (private->gimp->app, G_MENU_MODEL (model));
gtk_application_set_menubar (GTK_APPLICATION (private->gimp->app),
G_MENU_MODEL (model));
#else
private->menubar = gimp_menu_bar_new (model, private->menubar_manager);
g_object_unref (model);

View File

@ -428,6 +428,7 @@ gimp_action_set_sensitive (GimpAction *action,
gimp_action_update_proxy_tooltip (action, NULL);
}
g_object_notify (G_OBJECT (action), "enabled");
}
gboolean

View File

@ -33,6 +33,7 @@
#include "gimpenumaction.h"
#include "gimphelp-ids.h"
#include "gimpmenu.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpprocedureaction.h"
#include "gimpradioaction.h"
@ -69,7 +70,7 @@ static void gimp_menu_iface_init (GimpMenuShellInterface *ifac
static void gimp_menu_finalize (GObject *object);
static void gimp_menu_append (GimpMenuShell *shell,
GMenuModel *model);
GimpMenuModel *model);
static void gimp_menu_add_ui (GimpMenuShell *shell,
const gchar **paths,
const gchar *action_name,
@ -170,7 +171,7 @@ gimp_menu_finalize (GObject *object)
static void
gimp_menu_append (GimpMenuShell *shell,
GMenuModel *model)
GimpMenuModel *model)
{
static GtkRadioMenuItem *group = NULL;
GimpMenu *menu = GIMP_MENU (shell);
@ -179,7 +180,7 @@ gimp_menu_append (GimpMenuShell *shell,
g_return_if_fail (GTK_IS_CONTAINER (shell));
n_items = g_menu_model_get_n_items (model);
n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
for (gint i = 0; i < n_items; i++)
{
GMenuModel *subsection;
@ -187,10 +188,10 @@ gimp_menu_append (GimpMenuShell *shell,
gchar *label = NULL;
gchar *action_name = NULL;
subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
if (subsection != NULL)
{
@ -202,7 +203,7 @@ gimp_menu_append (GimpMenuShell *shell,
gtk_container_add (GTK_CONTAINER (shell), item);
gtk_widget_show (item);
gimp_menu_append (shell, subsection);
gimp_menu_append (shell, GIMP_MENU_MODEL (subsection));
item = gtk_separator_menu_item_new ();
gtk_container_add (GTK_CONTAINER (shell), item);
@ -240,7 +241,7 @@ gimp_menu_append (GimpMenuShell *shell,
subcontainer = gimp_menu_new (manager);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
gimp_menu_append (GIMP_MENU_SHELL (subcontainer), submenu);
gimp_menu_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu));
gtk_widget_show (subcontainer);
g_tree_insert (menu->priv->submenus,
@ -263,7 +264,7 @@ gimp_menu_append (GimpMenuShell *shell,
subcontainer = gimp_menu_new (manager);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
gimp_menu_append (GIMP_MENU_SHELL (subcontainer), submenu);
gimp_menu_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu));
gtk_widget_show (subcontainer);
g_tree_insert (menu->priv->submenus,

View File

@ -32,6 +32,7 @@
#include "gimpaction.h"
#include "gimpmenu.h"
#include "gimpmenubar.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpradioaction.h"
#include "gimpuimanager.h"
@ -57,9 +58,7 @@ enum
struct _GimpMenuBarPrivate
{
GMenuModel *model;
GTree *menus;
GTree *menus;
};
@ -67,19 +66,10 @@ struct _GimpMenuBarPrivate
static void gimp_menu_bar_iface_init (GimpMenuShellInterface *iface);
static void gimp_menu_bar_constructed (GObject *object);
static void gimp_menu_bar_dispose (GObject *object);
static void gimp_menu_bar_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_menu_bar_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_menu_bar_append (GimpMenuShell *shell,
GMenuModel *model);
GimpMenuModel *model);
static void gimp_menu_bar_add_ui (GimpMenuShell *shell,
const gchar **paths,
const gchar *action_name,
@ -101,19 +91,11 @@ gimp_menu_bar_class_init (GimpMenuBarClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gimp_menu_bar_constructed;
object_class->dispose = gimp_menu_bar_dispose;
object_class->get_property = gimp_menu_bar_get_property;
object_class->set_property = gimp_menu_bar_set_property;
object_class->get_property = gimp_menu_shell_get_property;
object_class->set_property = gimp_menu_shell_set_property;
gimp_menu_shell_install_properties (object_class);
g_object_class_install_property (object_class, PROP_MODEL,
g_param_spec_object ("model",
NULL, NULL,
G_TYPE_MENU_MODEL,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
@ -134,67 +116,19 @@ gimp_menu_bar_init (GimpMenuBar *bar)
gimp_menu_shell_init (GIMP_MENU_SHELL (bar));
}
static void
gimp_menu_bar_constructed (GObject *object)
{
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
gimp_menu_bar_dispose (GObject *object)
{
GimpMenuBar *bar = GIMP_MENU_BAR (object);
g_clear_object (&bar->priv->model);
g_clear_pointer (&bar->priv->menus, g_tree_unref);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_menu_bar_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpMenuBar *bar = GIMP_MENU_BAR (object);
switch (property_id)
{
case PROP_MODEL:
bar->priv->model = g_value_dup_object (value);
gimp_menu_shell_fill (GIMP_MENU_SHELL (bar), bar->priv->model, "ui-added", FALSE);
break;
default:
gimp_menu_shell_set_property (object, property_id, value, pspec);
break;
}
}
static void
gimp_menu_bar_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpMenuBar *bar = GIMP_MENU_BAR (object);
switch (property_id)
{
case PROP_MODEL:
g_value_set_object (value, bar->priv->model);
break;
default:
gimp_menu_shell_get_property (object, property_id, value, pspec);
break;
}
}
static void
gimp_menu_bar_append (GimpMenuShell *shell,
GMenuModel *model)
GimpMenuModel *model)
{
GimpMenuBar *bar = GIMP_MENU_BAR (shell);
GimpUIManager *manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (shell));
@ -202,7 +136,7 @@ gimp_menu_bar_append (GimpMenuShell *shell,
g_return_if_fail (GTK_IS_CONTAINER (shell));
n_items = g_menu_model_get_n_items (model);
n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
for (gint i = 0; i < n_items; i++)
{
GMenuModel *subsection;
@ -210,10 +144,10 @@ gimp_menu_bar_append (GimpMenuShell *shell,
gchar *label = NULL;
gchar *action_name = NULL;
subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
if (submenu != NULL)
{
@ -231,7 +165,7 @@ gimp_menu_bar_append (GimpMenuShell *shell,
subcontainer = gimp_menu_new (manager);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer);
GIMP_MENU_SHELL_GET_INTERFACE (subcontainer)->append (GIMP_MENU_SHELL (subcontainer), submenu);
gimp_menu_shell_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu));
gtk_widget_show (subcontainer);
g_tree_insert (bar->priv->menus,
@ -305,7 +239,7 @@ gimp_menu_bar_add_ui (GimpMenuShell *shell,
/* Public functions */
GtkWidget *
gimp_menu_bar_new (GMenuModel *model,
gimp_menu_bar_new (GimpMenuModel *model,
GimpUIManager *manager)
{
g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager) &&

View File

@ -48,7 +48,7 @@ struct _GimpMenuBarClass
GType gimp_menu_bar_get_type (void) G_GNUC_CONST;
GtkWidget * gimp_menu_bar_new (GMenuModel *model,
GtkWidget * gimp_menu_bar_new (GimpMenuModel *model,
GimpUIManager *manager);

865
app/widgets/gimpmenumodel.c Normal file
View File

@ -0,0 +1,865 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpmenu_model.c
* Copyright (C) 2023 Jehan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include "widgets-types.h"
#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "core/gimp.h"
#include "gimpaction.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpuimanager.h"
/**
* GimpMenuModel:
*
* GimpMenuModel implements GMenuModel. We initialize an object from another
* GMenuModel (usually a GMenu), auto-fill with various data from the
* GimpAction, when they are not in GAction API, e.g. labels, but action
* visibility.
*
* The object will also synchronize automatically with changes from the actions,
* but also GimpUIManager for dynamic contents and will trigger an
* "items-changed" when necessary. This allows for such variant to be used in
* GTK API which has no knowledge of GimpAction or GimpUIManager enhancements.
*/
enum
{
PROP_0,
PROP_MANAGER,
PROP_MODEL,
PROP_PATH,
PROP_IS_SECTION,
};
struct _GimpMenuModelPrivate
{
GimpUIManager *manager;
GMenuModel *model;
gchar *path;
gboolean is_section;
GList *items;
gint last_n_items;
};
static void gimp_menu_model_finalize (GObject *object);
static void gimp_menu_model_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_menu_model_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static GVariant * gimp_menu_model_get_item_attribute_value (GMenuModel *model,
gint item_index,
const gchar *attribute,
const GVariantType *expected_type);
static void gimp_menu_model_get_item_attributes (GMenuModel *model,
gint item_index,
GHashTable **attributes);
static GMenuModel * gimp_menu_model_get_item_link (GMenuModel *model,
gint item_index,
const gchar *link);
static void gimp_menu_model_get_item_links (GMenuModel *model,
gint item_index,
GHashTable **links);
static gint gimp_menu_model_get_n_items (GMenuModel *model);
static gboolean gimp_menu_model_is_mutable (GMenuModel *model);
static void gimp_menu_model_initialize (GimpMenuModel *model,
GMenuModel *gmodel);
static void gimp_menu_model_update (GimpMenuModel *model);
static GMenuItem * gimp_menu_model_get_item (GimpMenuModel *model,
gint idx);
static void gimp_menu_model_action_notify_visible (GimpAction *action,
GParamSpec *pspec,
GimpMenuModel *model);
static void gimp_menu_model_action_notify_label (GimpAction *action,
GParamSpec *pspec,
GMenuItem *item);
static gboolean gimp_menu_model_ui_added (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
const gchar *placeholder_key,
gboolean top,
GimpMenuModel *model);
static gboolean gimp_menu_model_ui_removed (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
GimpMenuModel *model);
G_DEFINE_TYPE_WITH_CODE (GimpMenuModel, gimp_menu_model, G_TYPE_MENU_MODEL, G_ADD_PRIVATE (GimpMenuModel))
#define parent_class gimp_menu_model_parent_class
/* Class functions */
static void
gimp_menu_model_class_init (GimpMenuModelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
object_class->finalize = gimp_menu_model_finalize;
object_class->get_property = gimp_menu_model_get_property;
object_class->set_property = gimp_menu_model_set_property;
model_class->get_item_attribute_value = gimp_menu_model_get_item_attribute_value;
model_class->get_item_attributes = gimp_menu_model_get_item_attributes;
model_class->get_item_link = gimp_menu_model_get_item_link;
model_class->get_item_links = gimp_menu_model_get_item_links;
model_class->get_n_items = gimp_menu_model_get_n_items;
model_class->is_mutable = gimp_menu_model_is_mutable;
g_object_class_install_property (object_class, PROP_MANAGER,
g_param_spec_object ("manager",
NULL, NULL,
GIMP_TYPE_UI_MANAGER,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_MODEL,
g_param_spec_object ("model",
NULL, NULL,
G_TYPE_MENU_MODEL,
GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_PATH,
g_param_spec_string ("path",
NULL, NULL, NULL,
GIMP_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class, PROP_IS_SECTION,
g_param_spec_boolean ("section",
NULL, NULL, FALSE,
GIMP_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
gimp_menu_model_init (GimpMenuModel *model)
{
model->priv = gimp_menu_model_get_instance_private (model);
model->priv->items = NULL;
model->priv->last_n_items = 0;
model->priv->path = 0;
model->priv->is_section = FALSE;
}
static void
gimp_menu_model_finalize (GObject *object)
{
GimpMenuModel *model = GIMP_MENU_MODEL (object);
g_clear_object (&model->priv->model);
g_list_free_full (model->priv->items, g_object_unref);
g_free (model->priv->path);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
void
gimp_menu_model_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpMenuModel *model = GIMP_MENU_MODEL (object);
switch (property_id)
{
case PROP_MANAGER:
g_value_set_object (value, model->priv->manager);
break;
case PROP_MODEL:
g_value_set_object (value, model->priv->model);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
void
gimp_menu_model_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpMenuModel *model = GIMP_MENU_MODEL (object);
switch (property_id)
{
case PROP_MANAGER:
g_set_weak_pointer (&model->priv->manager, g_value_get_object (value));
break;
case PROP_MODEL:
model->priv->model = g_value_dup_object (value);
gimp_menu_model_initialize (model, model->priv->model);
break;
case PROP_PATH:
model->priv->path = g_value_dup_string (value);
break;
case PROP_IS_SECTION:
model->priv->is_section = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static GVariant*
gimp_menu_model_get_item_attribute_value (GMenuModel *model,
gint item_index,
const gchar *attribute,
const GVariantType *expected_type)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GMenuItem *item;
item = gimp_menu_model_get_item (m, item_index);
return g_menu_item_get_attribute_value (item, attribute, expected_type);
}
static void
gimp_menu_model_get_item_attributes (GMenuModel *model,
gint item_index,
GHashTable **attributes)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GMenuItem *item;
GVariant *value;
item = gimp_menu_model_get_item (m, item_index);
*attributes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
(GDestroyNotify) g_variant_unref);
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_LABEL, NULL);
if (value)
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_LABEL, value);
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, NULL);
if (value)
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ACTION, value);
value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ICON, NULL);
if (value)
g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ICON, value);
value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SUBMENU, NULL);
if (value)
g_hash_table_insert (*attributes, G_MENU_LINK_SUBMENU, value);
value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SECTION, NULL);
if (value)
g_hash_table_insert (*attributes, G_MENU_LINK_SECTION, value);
}
static GMenuModel*
gimp_menu_model_get_item_link (GMenuModel *model,
gint item_index,
const gchar *link)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GMenuItem *item;
item = gimp_menu_model_get_item (m, item_index);
return g_menu_item_get_link (item, link);
}
static void
gimp_menu_model_get_item_links (GMenuModel *model,
gint item_index,
GHashTable **links)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GMenuModel *subsection;
GMenuModel *submenu;
GMenuItem *item;
*links = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
(GDestroyNotify) g_object_unref);
item = gimp_menu_model_get_item (m, item_index);
subsection = g_menu_item_get_link (item, G_MENU_LINK_SECTION);
submenu = g_menu_item_get_link (item, G_MENU_LINK_SUBMENU);
if (subsection)
g_hash_table_insert (*links, G_MENU_LINK_SECTION, g_object_ref (subsection));
if (submenu)
g_hash_table_insert (*links, G_MENU_LINK_SUBMENU, g_object_ref (submenu));
g_clear_object (&subsection);
g_clear_object (&submenu);
}
static gint
gimp_menu_model_get_n_items (GMenuModel *model)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GApplication *app = m->priv->manager->gimp->app;
gint len = 0;
for (GList *iter = m->priv->items; iter; iter = iter->next)
{
const gchar *action_name = NULL;
GMenuModel *subsection;
GMenuModel *submenu;
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU);
if (subsection || submenu)
{
len++;
}
/* Count neither placeholders (items with no action_name), nor invisible
* actions.
*/
else if (g_menu_item_get_attribute (iter->data,
G_MENU_ATTRIBUTE_ACTION,
"&s", &action_name))
{
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (app),
action_name + 4);
if (gimp_action_is_visible (GIMP_ACTION (action)))
len++;
}
g_clear_object (&subsection);
g_clear_object (&submenu);
}
m->priv->last_n_items = len;
return len;
}
static gboolean
gimp_menu_model_is_mutable (GMenuModel* model)
{
return TRUE;
}
/* Public functions. */
GimpMenuModel *
gimp_menu_model_new (GimpUIManager *manager,
GMenuModel *model)
{
g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL);
return g_object_new (GIMP_TYPE_MENU_MODEL,
"manager", manager,
"model", model,
NULL);
}
GimpMenuModel *
gimp_menu_model_get_submodel (GimpMenuModel *model,
const gchar *path)
{
GimpMenuModel *submodel;
gchar *submenus;
gchar *submenu;
gchar *subsubmenus;
submodel = g_object_ref (model);
if (path == NULL)
return submodel;
submenus = g_strdup (path);
subsubmenus = submenus;
while (subsubmenus && strlen (subsubmenus) > 0)
{
gint n_items;
gint i;
submenu = subsubmenus;
while (*submenu == '/')
submenu++;
subsubmenus = strstr (submenu, "/");
if (subsubmenus)
*(subsubmenus++) = '\0';
if (strlen (submenu) == 0)
break;
n_items = g_menu_model_get_n_items (G_MENU_MODEL (submodel));
for (i = 0; i < n_items; i++)
{
GMenuModel *subsubmodel;
gchar *label = NULL;
gchar *canon_label = NULL;
subsubmodel = g_menu_model_get_item_link (G_MENU_MODEL (submodel), i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (G_MENU_MODEL (submodel), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
if (label)
canon_label = gimp_menu_shell_make_canonical_path (label);
if (subsubmodel && g_strcmp0 (canon_label, submenu) == 0)
{
g_object_unref (submodel);
submodel = GIMP_MENU_MODEL (subsubmodel);
g_free (label);
g_free (canon_label);
break;
}
g_clear_object (&subsubmodel);
g_free (label);
g_free (canon_label);
}
g_return_val_if_fail (i < n_items, NULL);
}
g_free (submenus);
return submodel;
}
const gchar *
gimp_menu_model_get_path (GimpMenuModel *model)
{
return model->priv->path;
}
/* Private functions. */
static GimpMenuModel *
gimp_menu_model_new_section (GimpUIManager *manager,
GMenuModel *model,
const gchar *path)
{
g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL);
return g_object_new (GIMP_TYPE_MENU_MODEL,
"manager", manager,
"model", model,
"path", path,
"section", TRUE,
NULL);
}
static GimpMenuModel *
gimp_menu_model_new_submenu (GimpUIManager *manager,
GMenuModel *model,
const gchar *path)
{
g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL);
return g_object_new (GIMP_TYPE_MENU_MODEL,
"manager", manager,
"model", model,
"path", path,
NULL);
}
static void
gimp_menu_model_initialize (GimpMenuModel *model,
GMenuModel *gmodel)
{
GApplication *app = model->priv->manager->gimp->app;
gint n_items;
g_return_if_fail (GIMP_IS_MENU_MODEL (model));
g_return_if_fail (G_IS_MENU_MODEL (gmodel));
n_items = g_menu_model_get_n_items (gmodel);
for (int i = 0; i < n_items; i++)
{
GMenuModel *subsection;
GMenuModel *submenu;
gchar *label = NULL;
gchar *action_name = NULL;
GMenuItem *item = NULL;
subsection = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i,
G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i,
G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
if (subsection != NULL)
{
GimpMenuModel *submodel;
submodel = gimp_menu_model_new_section (model->priv->manager, subsection,
model->priv->path);
item = g_menu_item_new_section (label, G_MENU_MODEL (submodel));
g_object_unref (submodel);
}
else if (submenu != NULL)
{
GimpMenuModel *submodel;
gchar *canon_label;
gchar *path;
g_return_if_fail (label != NULL);
canon_label = gimp_menu_shell_make_canonical_path (label);
path = g_strdup_printf ("%s/%s",
model->priv->path ? model->priv->path : "",
canon_label);
g_free (canon_label);
submodel = gimp_menu_model_new_submenu (model->priv->manager, submenu, path);
item = g_menu_item_new_submenu (label, G_MENU_MODEL (submodel));
g_object_unref (submodel);
g_free (path);
}
else
{
item = g_menu_item_new_from_model (G_MENU_MODEL (gmodel), i);
if (action_name)
{
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (app),
action_name + 4);
g_signal_connect_object (action,
"notify::visible",
G_CALLBACK (gimp_menu_model_action_notify_visible),
model, 0);
g_menu_item_set_label (item, gimp_action_get_label (GIMP_ACTION (action)));
g_signal_connect_object (action,
"notify::label",
G_CALLBACK (gimp_menu_model_action_notify_label),
item, 0);
}
}
if (item)
model->priv->items = g_list_append (model->priv->items, item);
g_free (label);
g_free (action_name);
g_clear_object (&subsection);
g_clear_object (&submenu);
}
if (! model->priv->is_section)
{
g_signal_connect_object (model->priv->manager, "ui-added",
G_CALLBACK (gimp_menu_model_ui_added),
model, 0);
g_signal_connect_object (model->priv->manager, "ui-removed",
G_CALLBACK (gimp_menu_model_ui_removed),
model, 0);
gimp_ui_manager_foreach_ui (model->priv->manager,
(GimpUIMenuCallback) gimp_menu_model_ui_added,
model);
}
}
static void
gimp_menu_model_update (GimpMenuModel *model)
{
gint last_n_items;
gint n_items;
g_return_if_fail (GIMP_IS_MENU_MODEL (model));
last_n_items = model->priv->last_n_items;
n_items = gimp_menu_model_get_n_items (G_MENU_MODEL (model));
/* A bit "lazy" but should make sure we don't mess up. */
g_menu_model_items_changed (G_MENU_MODEL (model), 0, last_n_items, n_items);
}
static GMenuItem *
gimp_menu_model_get_item (GimpMenuModel *model,
gint idx)
{
GimpMenuModel *m = GIMP_MENU_MODEL (model);
GApplication *app = m->priv->manager->gimp->app;
gint cur = -1;
for (GList *iter = m->priv->items; iter; iter = iter->next)
{
const gchar *action_name = NULL;
GMenuModel *subsection;
GMenuModel *submenu;
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU);
if (subsection || submenu)
{
cur++;
}
/* Count neither placeholders (items with no action_name), nor invisible
* actions.
*/
else if (g_menu_item_get_attribute (iter->data,
G_MENU_ATTRIBUTE_ACTION,
"&s", &action_name))
{
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (app),
action_name + 4);
if (gimp_action_is_visible (GIMP_ACTION (action)))
cur++;
}
g_clear_object (&subsection);
g_clear_object (&submenu);
if (cur == idx)
return iter->data;
}
return NULL;
}
static void
gimp_menu_model_action_notify_visible (GimpAction *action,
GParamSpec *pspec,
GimpMenuModel *model)
{
gimp_menu_model_update (model);
}
static void
gimp_menu_model_action_notify_label (GimpAction *action,
GParamSpec *pspec,
GMenuItem *item)
{
g_return_if_fail (GIMP_IS_ACTION (action));
g_return_if_fail (G_IS_MENU_ITEM (item));
g_menu_item_set_label (item, gimp_action_get_label (action));
}
static gboolean
gimp_menu_model_ui_added (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
const gchar *placeholder_key,
gboolean top,
GimpMenuModel *model)
{
gboolean added = FALSE;
if (g_strcmp0 (path, model->priv->path) == 0)
{
GApplication *app = model->priv->manager->gimp->app;
GAction *action;
gchar *detailed_action_name;
GMenuItem *item;
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name);
g_return_val_if_fail (action != NULL, FALSE);
added = TRUE;
g_signal_handlers_disconnect_by_func (action,
G_CALLBACK (gimp_menu_model_action_notify_visible),
model);
detailed_action_name = g_strdup_printf ("app.%s", g_action_get_name (action));
item = g_menu_item_new (gimp_action_get_label (GIMP_ACTION (action)), detailed_action_name);
/* TODO: add also G_MENU_ATTRIBUTE_ICON attribute? */
g_free (detailed_action_name);
if (placeholder_key)
{
GList *placeholder = NULL;
GMenuModel *subsection = NULL;
for (GList *iter = model->priv->items; iter; iter = iter->next)
{
const gchar *label;
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
if (subsection != NULL)
{
if (gimp_menu_model_ui_added (manager, path, action_name,
placeholder_key, top,
GIMP_MENU_MODEL (subsection)))
break;
else
g_clear_object (&subsection);
}
else if (g_menu_item_get_attribute (iter->data,
G_MENU_ATTRIBUTE_LABEL,
"&s", &label) &&
g_strcmp0 (label, placeholder_key) == 0)
{
placeholder = iter;
break;
}
}
if (placeholder)
{
if (top)
model->priv->items = g_list_insert_before (model->priv->items,
placeholder, item);
else
model->priv->items = g_list_insert_before (model->priv->items,
placeholder->next, item);
}
else
{
added = FALSE;
if (subsection == NULL && ! model->priv->is_section)
g_warning ("%s: no placeholder item '%s'.", G_STRFUNC, placeholder_key);
}
/* else: added in a subsection. */
g_clear_object (&subsection);
}
else if (top)
{
model->priv->items = g_list_prepend (model->priv->items, item);
}
else
{
model->priv->items = g_list_append (model->priv->items, item);
}
g_signal_connect_object (action,
"notify::visible",
G_CALLBACK (gimp_menu_model_action_notify_visible),
model, 0);
g_signal_connect_object (action,
"notify::label",
G_CALLBACK (gimp_menu_model_action_notify_label),
item, 0);
gimp_menu_model_update (model);
}
return added;
}
static gboolean
gimp_menu_model_ui_removed (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
GimpMenuModel *model)
{
gboolean removed = FALSE;
if (g_strcmp0 (path, model->priv->path) == 0)
{
GApplication *app = model->priv->manager->gimp->app;
GMenuItem *item = NULL;
GMenuModel *subsection = NULL;
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name);
g_return_val_if_fail (action != NULL, FALSE);
removed = TRUE;
for (GList *iter = model->priv->items; iter; iter = iter->next)
{
const gchar *action;
subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION);
if (subsection != NULL)
{
if (gimp_menu_model_ui_removed (manager, path, action_name,
GIMP_MENU_MODEL (subsection)))
break;
else
g_clear_object (&subsection);
}
else if (g_menu_item_get_attribute (iter->data,
G_MENU_ATTRIBUTE_ACTION,
"&s", &action) &&
/* "action" attribute will start with "app." prefix. */
g_strcmp0 (action + 4, action_name) == 0)
{
item = iter->data;
model->priv->items = g_list_delete_link (model->priv->items, iter);
break;
}
}
if (item)
{
g_signal_handlers_disconnect_by_func (action,
G_CALLBACK (gimp_menu_model_action_notify_visible),
model);
g_signal_handlers_disconnect_by_func (action,
G_CALLBACK (gimp_menu_model_action_notify_label),
item);
g_object_unref (item);
}
else
{
removed = FALSE;
if (subsection == NULL && ! model->priv->is_section)
g_warning ("%s: no item for action name '%s'.", G_STRFUNC, action_name);
}
/* else: removed in a subsection. */
g_clear_object (&subsection);
gimp_menu_model_update (model);
}
return removed;
}

View File

@ -0,0 +1,60 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpmenu_model.h
* Copyright (C) 2023 Jehan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __GIMP_MENU_MODEL_H__
#define __GIMP_MENU_MODEL_H__
#define GIMP_TYPE_MENU_MODEL (gimp_menu_model_get_type ())
#define GIMP_MENU_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MENU_MODEL, GimpMenuModel))
#define GIMP_MENU_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MENU_MODEL, GimpMenuModelClass))
#define GIMP_IS_MENU_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_MENU_MODEL))
#define GIMP_IS_MENU_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MENU_MODEL))
#define GIMP_MENU_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MENU_MODEL, GimpMenuModelClass))
typedef struct _GimpMenuModelPrivate GimpMenuModelPrivate;
typedef struct _GimpMenuModelClass GimpMenuModelClass;
struct _GimpMenuModel
{
GMenuModel parent_instance;
GimpMenuModelPrivate *priv;
};
struct _GimpMenuModelClass
{
GMenuModelClass parent_class;
};
GType gimp_menu_model_get_type (void) G_GNUC_CONST;
GimpMenuModel * gimp_menu_model_new (GimpUIManager *manager,
GMenuModel *model);
GimpMenuModel * gimp_menu_model_get_submodel (GimpMenuModel *model,
const gchar *path);
const gchar * gimp_menu_model_get_path (GimpMenuModel *model);
#endif /* __GIMP_MENU_MODEL_H__ */

View File

@ -28,6 +28,7 @@
#include "widgets-types.h"
#include "gimpmenu.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpuimanager.h"
@ -39,8 +40,8 @@ typedef struct _GimpMenuShellPrivate GimpMenuShellPrivate;
struct _GimpMenuShellPrivate
{
GimpUIManager *manager;
gchar *update_signal;
gchar **path_prefix;
GimpMenuModel *model;
gchar *path_prefix;
GRegex *path_regex;
};
@ -50,19 +51,14 @@ static GimpMenuShellPrivate *
gimp_menu_shell_get_private (GimpMenuShell *menu_shell);
static void gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv);
static void gimp_menu_shell_ui_added (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
const gchar *placeholder_key,
gboolean top,
GimpMenuShell *shell);
static void gimp_menu_shell_ui_removed (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
static void gimp_menu_shell_model_changed (GMenuModel *self,
gint position,
gint removed,
gint added,
GimpMenuShell *shell);
static void gimp_menu_shell_append_model_drop_top (GimpMenuShell *shell,
GMenuModel *model);
GimpMenuModel *model);
static gchar ** gimp_menu_shell_break_path (GimpMenuShell *shell,
const gchar *path);
@ -84,65 +80,67 @@ gimp_menu_shell_default_init (GimpMenuShellInterface *iface)
GIMP_TYPE_UI_MANAGER,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_interface_install_property (iface,
g_param_spec_object ("model",
NULL, NULL,
GIMP_TYPE_MENU_MODEL,
GIMP_PARAM_READWRITE |
G_PARAM_READWRITE));
}
/* public functions */
/* Public functions. */
void
gimp_menu_shell_fill (GimpMenuShell *shell,
GMenuModel *model,
const gchar *update_signal,
GimpMenuModel *model,
gboolean drop_top_submenu)
{
GimpMenuShellPrivate *priv;
gchar **path_prefix;
g_return_if_fail (GTK_IS_CONTAINER (shell));
gtk_container_foreach (GTK_CONTAINER (shell),
(GtkCallback) gtk_widget_destroy,
NULL);
priv = GET_PRIVATE (shell);
g_clear_pointer (&priv->path_prefix, g_strfreev);
path_prefix = g_object_get_data (G_OBJECT (model), "gimp-ui-manager-model-paths");
priv->path_prefix = g_strdupv (path_prefix);
if (drop_top_submenu)
gimp_menu_shell_append_model_drop_top (shell, model);
else
GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, model);
if (update_signal != NULL)
{
if (priv->update_signal != NULL)
{
g_free (priv->update_signal);
g_signal_handlers_disconnect_by_func (priv->manager,
G_CALLBACK (gimp_menu_shell_ui_added),
shell);
g_signal_handlers_disconnect_by_func (priv->manager,
G_CALLBACK (gimp_menu_shell_ui_removed),
shell);
}
priv->update_signal = g_strdup (update_signal);
g_signal_connect_object (priv->manager, update_signal,
G_CALLBACK (gimp_menu_shell_ui_added),
shell, 0);
g_signal_connect_object (priv->manager, "ui-removed",
G_CALLBACK (gimp_menu_shell_ui_removed),
shell, 0);
gimp_ui_manager_foreach_ui (priv->manager,
(GimpUIMenuCallback) gimp_menu_shell_ui_added,
shell);
}
gimp_menu_shell_append (shell, model);
}
/* Protected functions. */
void
gimp_menu_shell_append (GimpMenuShell *shell,
GimpMenuModel *model)
{
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
g_free (priv->path_prefix);
priv->path_prefix = g_strdup (gimp_menu_model_get_path (GIMP_MENU_MODEL (model)));
if (priv->model)
g_signal_handlers_disconnect_by_func (priv->model,
G_CALLBACK (gimp_menu_shell_model_changed),
shell);
if (priv->model != model)
{
g_clear_object (&priv->model);
priv->model = g_object_ref (model);
}
if (model)
{
GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, model);
g_signal_connect_object (priv->model, "items-changed",
G_CALLBACK (gimp_menu_shell_model_changed),
shell, 0);
}
}
void
gimp_menu_shell_init (GimpMenuShell *shell)
{
@ -152,8 +150,8 @@ gimp_menu_shell_init (GimpMenuShell *shell)
priv = GET_PRIVATE (shell);
priv->update_signal = NULL;
priv->path_prefix = NULL;
priv->model = NULL;
priv->path_regex = g_regex_new ("/+", 0, 0, NULL);
}
@ -169,6 +167,7 @@ void
gimp_menu_shell_install_properties (GObjectClass *klass)
{
g_object_class_override_property (klass, GIMP_MENU_SHELL_PROP_MANAGER, "manager");
g_object_class_override_property (klass, GIMP_MENU_SHELL_PROP_MODEL, "model");
}
void
@ -186,6 +185,9 @@ gimp_menu_shell_get_property (GObject *object,
case GIMP_MENU_SHELL_PROP_MANAGER:
g_value_set_object (value, priv->manager);
break;
case GIMP_MENU_SHELL_PROP_MODEL:
g_value_set_object (value, priv->model);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@ -199,15 +201,18 @@ gimp_menu_shell_set_property (GObject *object,
const GValue *value,
GParamSpec *pspec)
{
GimpMenuShellPrivate *priv;
GimpMenuShellPrivate *priv = GET_PRIVATE (object);
GimpMenuShell *shell = GIMP_MENU_SHELL (object);
priv = GET_PRIVATE (object);
switch (property_id)
{
case GIMP_MENU_SHELL_PROP_MANAGER:
g_set_weak_pointer (&priv->manager, g_value_get_object (value));
break;
case GIMP_MENU_SHELL_PROP_MODEL:
gimp_menu_shell_append (shell, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@ -265,84 +270,64 @@ gimp_menu_shell_get_private (GimpMenuShell *menu_shell)
static void
gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv)
{
g_free (priv->update_signal);
g_clear_pointer (&priv->path_prefix, g_strfreev);
g_free (priv->path_prefix);
g_clear_pointer (&priv->path_regex, g_regex_unref);
g_clear_object (&priv->model);
g_slice_free (GimpMenuShellPrivate, priv);
}
static void
gimp_menu_shell_ui_added (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
const gchar *placeholder_key,
gboolean top,
GimpMenuShell *shell)
gimp_menu_shell_model_changed (GMenuModel *model,
gint position,
gint removed,
gint added,
GimpMenuShell *shell)
{
gchar **paths = NULL;
gint paths_idx = 0;
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
if (GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui &&
gimp_menu_shell_is_subpath (shell, path, &paths, &paths_idx))
GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui (shell,
(const gchar **) (paths + paths_idx),
action_name,
placeholder_key, top);
g_strfreev (paths);
}
static void
gimp_menu_shell_ui_removed (GimpUIManager *manager,
const gchar *path,
const gchar *action_name,
GimpMenuShell *shell)
{
gchar **paths = NULL;
gint paths_idx = 0;
if (GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui &&
gimp_menu_shell_is_subpath (shell, path, &paths, &paths_idx))
GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui (shell,
(const gchar **) paths + paths_idx,
action_name);
g_strfreev (paths);
/* Lazy approach to handle model changes. A better implementation would only
* remove or delete the changed items using:
* GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui()
* GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui
* TODO
*/
gtk_container_foreach (GTK_CONTAINER (shell),
(GtkCallback) gtk_widget_destroy,
NULL);
GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, priv->model);
}
static void
gimp_menu_shell_append_model_drop_top (GimpMenuShell *shell,
GMenuModel *model)
GimpMenuModel *model)
{
GMenuModel *submenu = NULL;
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
GMenuModel *submenu = NULL;
g_return_if_fail (GTK_IS_CONTAINER (shell));
if (g_menu_model_get_n_items (model) == 1)
if (g_menu_model_get_n_items (G_MENU_MODEL (model)) == 1)
{
GimpMenuShellPrivate *priv = GET_PRIVATE (shell);
gchar *label = NULL;
gchar *label = NULL;
submenu = g_menu_model_get_item_link (model, 0, G_MENU_LINK_SUBMENU);
submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), 0, G_MENU_LINK_SUBMENU);
if (submenu)
{
GStrvBuilder *paths_builder = g_strv_builder_new ();
gchar *path_prefix;
g_menu_model_get_item_attribute (submenu, 0, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_strv_builder_addv (paths_builder, (const char **) priv->path_prefix);
g_strv_builder_add (paths_builder, label);
g_strfreev (priv->path_prefix);
priv->path_prefix = g_strv_builder_end (paths_builder);
g_strv_builder_unref (paths_builder);
path_prefix = g_strdup_printf ("%s/%s", priv->path_prefix, label);
g_free (priv->path_prefix);
priv->path_prefix = path_prefix;
}
g_free (label);
}
GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, submenu != NULL ? submenu : model);
gimp_menu_shell_append (shell, submenu != NULL ? submenu : model);
g_clear_object (&submenu);
}
@ -390,7 +375,7 @@ gimp_menu_shell_is_subpath (GimpMenuShell *shell,
gint *index)
{
GimpMenuShellPrivate *priv;
gboolean is_subpath = TRUE;
gboolean is_subpath = TRUE;
*index = 0;
*paths = gimp_menu_shell_break_path (shell, path);
@ -398,18 +383,23 @@ gimp_menu_shell_is_subpath (GimpMenuShell *shell,
if (priv->path_prefix)
{
*index = g_strv_length (priv->path_prefix);
gchar **path_prefix;
path_prefix = gimp_menu_shell_break_path (shell, priv->path_prefix);
*index = g_strv_length (path_prefix);
is_subpath = FALSE;
if (*index <= g_strv_length (*paths))
{
gint i;
for (i = 0; i < *index; i ++)
if (g_strcmp0 (priv->path_prefix[i], (*paths)[i]) != 0)
if (g_strcmp0 (path_prefix[i], (*paths)[i]) != 0)
break;
is_subpath = (i == *index);
}
g_strfreev (path_prefix);
}
return is_subpath;

View File

@ -32,8 +32,9 @@ enum
{
GIMP_MENU_SHELL_PROP_0,
GIMP_MENU_SHELL_PROP_MANAGER,
GIMP_MENU_SHELL_PROP_MODEL,
GIMP_MENU_SHELL_PROP_LAST = GIMP_MENU_SHELL_PROP_MANAGER,
GIMP_MENU_SHELL_PROP_LAST = GIMP_MENU_SHELL_PROP_MODEL,
};
typedef struct _GimpMenuShellInterface GimpMenuShellInterface;
@ -44,8 +45,8 @@ struct _GimpMenuShellInterface
/* Virtual functions. */
void (* append) (GimpMenuShell *shell,
GMenuModel *model);
void (* append) (GimpMenuShell *shell,
GimpMenuModel *model);
void (* add_ui) (GimpMenuShell *shell,
const gchar **paths,
const gchar *action_name,
@ -60,13 +61,15 @@ struct _GimpMenuShellInterface
GType gimp_menu_shell_get_type (void) G_GNUC_CONST;
void gimp_menu_shell_fill (GimpMenuShell *shell,
GMenuModel *model,
const gchar *update_signal,
GimpMenuModel *model,
gboolean drop_top_submenu);
/* Protected functions. */
void gimp_menu_shell_append (GimpMenuShell *shell,
GimpMenuModel *model);
void gimp_menu_shell_init (GimpMenuShell *shell);
void gimp_menu_shell_install_properties (GObjectClass *klass);
void gimp_menu_shell_get_property (GObject *object,

View File

@ -139,7 +139,7 @@ gimp_text_editor_new (const gchar *title,
GimpTextEditor *editor;
GtkWidget *content_area;
GtkWidget *toolbar;
GMenuModel *toolbar_model;
GimpMenuModel *toolbar_model;
GtkWidget *style_editor;
GtkWidget *scrolled_window;
gboolean use_header_bar;

View File

@ -35,6 +35,7 @@
#include "gimpradioaction.h"
#include "gimptoggleaction.h"
#include "gimptoolbar.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimpuimanager.h"
@ -53,36 +54,13 @@
* actions as buttons).
*/
enum
{
PROP_0 = GIMP_MENU_SHELL_PROP_LAST,
PROP_MODEL
};
struct _GimpToolbarPrivate
{
GMenuModel *model;
};
/* local function prototypes */
static void gimp_toolbar_iface_init (GimpMenuShellInterface *iface);
static void gimp_toolbar_constructed (GObject *object);
static void gimp_toolbar_dispose (GObject *object);
static void gimp_toolbar_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_toolbar_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_toolbar_append (GimpMenuShell *shell,
GMenuModel *model);
GimpMenuModel *model);
static void gimp_toolbar_add_ui (GimpMenuShell *shell,
const gchar **paths,
const gchar *action_name,
@ -108,7 +86,6 @@ static void gimp_toolbar_action_notify_visible (GimpAction *act
G_DEFINE_TYPE_WITH_CODE (GimpToolbar, gimp_toolbar, GTK_TYPE_TOOLBAR,
G_ADD_PRIVATE (GimpToolbar)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_MENU_SHELL, gimp_toolbar_iface_init))
#define parent_class gimp_toolbar_parent_class
@ -121,19 +98,10 @@ gimp_toolbar_class_init (GimpToolbarClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gimp_toolbar_constructed;
object_class->dispose = gimp_toolbar_dispose;
object_class->get_property = gimp_toolbar_get_property;
object_class->set_property = gimp_toolbar_set_property;
object_class->get_property = gimp_menu_shell_get_property;
object_class->set_property = gimp_menu_shell_set_property;
gimp_menu_shell_install_properties (object_class);
g_object_class_install_property (object_class, PROP_MODEL,
g_param_spec_object ("model",
NULL, NULL,
G_TYPE_MENU_MODEL,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
@ -146,78 +114,19 @@ gimp_toolbar_iface_init (GimpMenuShellInterface *iface)
static void
gimp_toolbar_init (GimpToolbar *bar)
{
bar->priv = gimp_toolbar_get_instance_private (bar);
gimp_menu_shell_init (GIMP_MENU_SHELL (bar));
}
static void
gimp_toolbar_constructed (GObject *object)
{
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
gimp_toolbar_dispose (GObject *object)
{
GimpToolbar *bar = GIMP_TOOLBAR (object);
g_clear_object (&bar->priv->model);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_toolbar_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpToolbar *bar = GIMP_TOOLBAR (object);
switch (property_id)
{
case PROP_MODEL:
bar->priv->model = g_value_dup_object (value);
gimp_menu_shell_fill (GIMP_MENU_SHELL (bar), bar->priv->model, "ui-added", FALSE);
break;
default:
gimp_menu_shell_set_property (object, property_id, value, pspec);
break;
}
}
static void
gimp_toolbar_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpToolbar *bar = GIMP_TOOLBAR (object);
switch (property_id)
{
case PROP_MODEL:
g_value_set_object (value, bar->priv->model);
break;
default:
gimp_menu_shell_get_property (object, property_id, value, pspec);
break;
}
}
static void
gimp_toolbar_append (GimpMenuShell *shell,
GMenuModel *model)
GimpMenuModel *model)
{
GimpToolbar *toolbar = GIMP_TOOLBAR (shell);
gint n_items;
g_return_if_fail (GTK_IS_CONTAINER (shell));
n_items = g_menu_model_get_n_items (model);
n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
for (gint i = 0; i < n_items; i++)
{
GMenuModel *subsection;
@ -225,10 +134,10 @@ gimp_toolbar_append (GimpMenuShell *shell,
gchar *label = NULL;
gchar *action_name = NULL;
subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
if (subsection != NULL)
{
@ -238,7 +147,7 @@ gimp_toolbar_append (GimpMenuShell *shell,
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
gtk_widget_show (GTK_WIDGET (item));
gimp_toolbar_append (shell, subsection);
gimp_toolbar_append (shell, GIMP_MENU_MODEL (subsection));
item = gtk_separator_tool_item_new ();
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
@ -290,7 +199,7 @@ gimp_toolbar_add_ui (GimpMenuShell *shell,
/* Public functions */
GtkWidget *
gimp_toolbar_new (GMenuModel *model,
gimp_toolbar_new (GimpMenuModel *model,
GimpUIManager *manager)
{
g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager) &&

View File

@ -48,7 +48,7 @@ struct _GimpToolbarClass
GType gimp_toolbar_get_type (void) G_GNUC_CONST;
GtkWidget * gimp_toolbar_new (GMenuModel *model,
GtkWidget * gimp_toolbar_new (GimpMenuModel *model,
GimpUIManager *manager);

View File

@ -38,6 +38,7 @@
#include "gimphelp.h"
#include "gimphelp-ids.h"
#include "gimpmenu.h"
#include "gimpmenumodel.h"
#include "gimpmenushell.h"
#include "gimptoggleaction.h"
#include "gimpuimanager.h"
@ -108,8 +109,6 @@ static GtkWidget *find_widget_under_pointer (GdkWindow *window,
gint *x,
gint *y);
static void gimp_ui_manager_fill_model (GimpUIManager *manager,
GMenuModel *model);
static void gimp_ui_manager_menu_item_free (GimpUIManagerMenuItem *item);
static void gimp_ui_manager_popup_hidden (GtkMenuShell *popup,
@ -478,16 +477,16 @@ gimp_ui_manager_get_widget (GimpUIManager *manager,
return gtk_ui_manager_get_widget ((GtkUIManager *) manager, path);
}
GMenuModel *
GimpMenuModel *
gimp_ui_manager_get_model (GimpUIManager *manager,
const gchar *path)
{
GimpUIManagerUIEntry *entry;
GMenuModel *model;
GimpMenuModel *model;
GMenuModel *gmodel;
GimpMenuModel *submodel;
gchar *root;
gchar *submenus;
GStrvBuilder *paths_builder = g_strv_builder_new ();
GStrv paths;
root = g_strdup (path);
submenus = strstr (root + 1, "/");
@ -545,66 +544,21 @@ gimp_ui_manager_get_model (GimpUIManager *manager,
/* The model is owned by the builder which I have to keep around. */
entry->builder = gtk_builder_new_from_file (filename);
gimp_ui_manager_fill_model (manager,
G_MENU_MODEL (gtk_builder_get_object (entry->builder, root)));
g_free (filename);
g_free (full_basename);
}
model = G_MENU_MODEL (gtk_builder_get_object (entry->builder, root));
gmodel = G_MENU_MODEL (gtk_builder_get_object (entry->builder, root));
g_return_val_if_fail (G_IS_MENU (model), NULL);
g_return_val_if_fail (G_IS_MENU (gmodel), NULL);
g_object_ref (model);
while (submenus != NULL)
{
const gchar *submenu = submenus;
gint n_items;
gint i;
model = gimp_menu_model_new (manager, gmodel);
submenus = strstr (submenu + 1, "/");
if (submenus != NULL)
{
*submenus = '\0';
if (*(++submenus) == '\0')
submenus = NULL;
}
submodel = gimp_menu_model_get_submodel (model, submenus);
n_items = g_menu_model_get_n_items (model);
for (i = 0; i < n_items; i++)
{
GMenuModel *submodel;
gchar *label = NULL;
gchar *canon_label = NULL;
submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
if (label)
canon_label = gimp_menu_shell_make_canonical_path (label);
if (submodel && g_strcmp0 (canon_label, submenu) == 0)
{
g_strv_builder_add (paths_builder, canon_label);
g_object_unref (model);
model = submodel;
g_free (label);
break;
}
g_clear_object (&submodel);
g_free (label);
g_free (canon_label);
}
g_return_val_if_fail (i < n_items, NULL);
}
paths = g_strv_builder_end (paths_builder);
g_strv_builder_unref (paths_builder);
g_object_set_data_full (G_OBJECT (model), "gimp-ui-manager-model-paths",
paths, (GDestroyNotify) g_strfreev);
g_free (root);
return model;
return submodel;
}
void
@ -801,19 +755,19 @@ gimp_ui_manager_ui_popup_at_widget (GimpUIManager *manager,
GDestroyNotify popdown_func,
gpointer popdown_data)
{
GMenuModel *model;
GtkWidget *menu;
GimpMenuModel *model;
GtkWidget *menu;
g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
g_return_if_fail (ui_path != NULL);
g_return_if_fail (GTK_IS_WIDGET (widget));
menu = gimp_menu_new (manager);
menu = gimp_menu_new (manager);
gtk_menu_attach_to_widget (GTK_MENU (menu), widget, NULL);
model = gimp_ui_manager_get_model (manager, ui_path);
g_return_if_fail (model != NULL);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE);
g_object_unref (model);
if (! menu)
@ -821,13 +775,13 @@ gimp_ui_manager_ui_popup_at_widget (GimpUIManager *manager,
if (child_ui_manager != NULL && child_ui_path != NULL)
{
GMenuModel *child_model;
GtkWidget *child_menu;
GimpMenuModel *child_model;
GtkWidget *child_menu;
/* TODO GMenu: the "icon" attribute set in the .ui file should be visible. */
child_model = gimp_ui_manager_get_model (child_ui_manager, child_ui_path);
child_menu = gimp_menu_new (child_ui_manager);
gimp_menu_shell_fill (GIMP_MENU_SHELL (child_menu), child_model, "ui-added", FALSE);
gimp_menu_shell_fill (GIMP_MENU_SHELL (child_menu), child_model, FALSE);
g_object_unref (child_model);
gimp_menu_merge (GIMP_MENU (menu), GIMP_MENU (child_menu), TRUE);
@ -859,8 +813,8 @@ gimp_ui_manager_ui_popup_at_pointer (GimpUIManager *manager,
GDestroyNotify popdown_func,
gpointer popdown_data)
{
GMenuModel *model;
GtkWidget *menu;
GimpMenuModel *model;
GtkWidget *menu;
g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
g_return_if_fail (ui_path != NULL);
@ -868,7 +822,7 @@ gimp_ui_manager_ui_popup_at_pointer (GimpUIManager *manager,
model = gimp_ui_manager_get_model (manager, ui_path);
menu = gimp_menu_new (manager);
gtk_menu_attach_to_widget (GTK_MENU (menu), attached_widget, NULL);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE);
g_object_unref (model);
if (! menu)
@ -901,8 +855,8 @@ gimp_ui_manager_ui_popup_at_rect (GimpUIManager *manager,
GDestroyNotify popdown_func,
gpointer popdown_data)
{
GMenuModel *model;
GtkWidget *menu;
GimpMenuModel *model;
GtkWidget *menu;
g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
g_return_if_fail (ui_path != NULL);
@ -910,7 +864,7 @@ gimp_ui_manager_ui_popup_at_rect (GimpUIManager *manager,
model = gimp_ui_manager_get_model (manager, ui_path);
menu = gimp_menu_new (manager);
gtk_menu_attach_to_widget (GTK_MENU (menu), attached_widget, NULL);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE);
gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE);
g_object_unref (model);
if (! menu)
@ -1401,55 +1355,6 @@ find_widget_under_pointer (GdkWindow *window,
return event_widget;
}
static void
gimp_ui_manager_fill_model (GimpUIManager *manager,
GMenuModel *model)
{
gint n_items;
g_return_if_fail (G_IS_MENU_MODEL (model));
n_items = g_menu_model_get_n_items (model);
for (int i = 0; i < n_items; i++)
{
GMenuModel *subsection;
GMenuModel *submenu;
gchar *label = NULL;
gchar *action_name = NULL;
subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);
if (subsection != NULL)
{
gimp_ui_manager_fill_model (manager, subsection);
}
else if (submenu != NULL)
{
gimp_ui_manager_fill_model (manager, submenu);
}
else if (action_name != NULL)
{
/* Update the label, unless it's a placeholder (no action name). */
GApplication *app;
GAction *action;
app = manager->gimp->app;
action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name + 4);
g_menu_remove (G_MENU (model), i);
g_menu_insert (G_MENU (model), i,
gimp_action_get_label (GIMP_ACTION (action)),
action_name);
}
g_free (label);
g_free (action_name);
g_clear_object (&subsection);
g_clear_object (&submenu);
}
}
static void
gimp_ui_manager_menu_item_free (GimpUIManagerMenuItem *item)
{

View File

@ -110,7 +110,7 @@ GtkAccelGroup * gimp_ui_manager_get_accel_group (GimpUIManager *manager);
GtkWidget * gimp_ui_manager_get_widget (GimpUIManager *manager,
const gchar *path);
GMenuModel * gimp_ui_manager_get_model (GimpUIManager *manager,
GimpMenuModel * gimp_ui_manager_get_model (GimpUIManager *manager,
const gchar *path);
void gimp_ui_manager_remove_ui (GimpUIManager *manager,

View File

@ -142,6 +142,7 @@ libappwidgets_sources = [
'gimpmenushell.c',
'gimpmenudock.c',
'gimpmenufactory.c',
'gimpmenumodel.c',
'gimpmessagebox.c',
'gimpmessagedialog.c',
'gimpmeter.c',

View File

@ -212,6 +212,7 @@ typedef struct _GimpLayerModeComboBox GimpLayerModeComboBox;
typedef struct _GimpMessageBox GimpMessageBox;
typedef struct _GimpMenu GimpMenu;
typedef struct _GimpMenuBar GimpMenuBar;
typedef struct _GimpMenuModel GimpMenuModel;
typedef struct _GimpMenuShell GimpMenuShell;
typedef struct _GimpMeter GimpMeter;
typedef struct _GimpModifiersEditor GimpModifiersEditor;