/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikamenushell.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 . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "widgets-types.h" #include "pikamenu.h" #include "pikamenumodel.h" #include "pikamenushell.h" #include "pikauimanager.h" enum { MODEL_DELETED, LAST_SIGNAL }; #define GET_PRIVATE(obj) (pika_menu_shell_get_private ((PikaMenuShell *) (obj))) typedef struct _PikaMenuShellPrivate PikaMenuShellPrivate; struct _PikaMenuShellPrivate { PikaUIManager *manager; PikaMenuModel *model; gchar *path_prefix; GRegex *path_regex; }; static PikaMenuShellPrivate * pika_menu_shell_get_private (PikaMenuShell *menu_shell); static void pika_menu_shell_private_finalize (PikaMenuShellPrivate *priv); static void pika_menu_shell_model_changed (GMenuModel *self, gint position, gint removed, gint added, PikaMenuShell *shell); static void pika_menu_shell_append_model_drop_top (PikaMenuShell *shell, PikaMenuModel *model); G_DEFINE_INTERFACE (PikaMenuShell, pika_menu_shell, GTK_TYPE_CONTAINER) static guint signals[LAST_SIGNAL]; static void pika_menu_shell_default_init (PikaMenuShellInterface *iface) { signals[MODEL_DELETED] = g_signal_new ("model-deleted", G_TYPE_FROM_INTERFACE (iface), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaMenuShellInterface, model_deleted), NULL, NULL, NULL, G_TYPE_NONE, 0); g_object_interface_install_property (iface, g_param_spec_object ("manager", NULL, NULL, PIKA_TYPE_UI_MANAGER, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_interface_install_property (iface, g_param_spec_object ("model", NULL, NULL, PIKA_TYPE_MENU_MODEL, PIKA_PARAM_READWRITE | G_PARAM_READWRITE)); } /* Public functions. */ void pika_menu_shell_fill (PikaMenuShell *shell, PikaMenuModel *model, gboolean drop_top_submenu) { g_return_if_fail (GTK_IS_CONTAINER (shell)); gtk_container_foreach (GTK_CONTAINER (shell), (GtkCallback) gtk_widget_destroy, NULL); if (drop_top_submenu) pika_menu_shell_append_model_drop_top (shell, model); else pika_menu_shell_append (shell, model); } /* Protected functions. */ void pika_menu_shell_append (PikaMenuShell *shell, PikaMenuModel *model) { PikaMenuShellPrivate *priv = GET_PRIVATE (shell); g_free (priv->path_prefix); priv->path_prefix = g_strdup (pika_menu_model_get_path (PIKA_MENU_MODEL (model))); if (priv->model) { g_signal_handlers_disconnect_by_func (priv->model, G_CALLBACK (pika_menu_shell_model_changed), shell); g_signal_emit (shell, signals[MODEL_DELETED], 0); } if (priv->model != model) { g_clear_object (&priv->model); priv->model = g_object_ref (model); } if (model) { PIKA_MENU_SHELL_GET_INTERFACE (shell)->append (shell, model); g_signal_connect_object (priv->model, "items-changed", G_CALLBACK (pika_menu_shell_model_changed), shell, 0); } } void pika_menu_shell_init (PikaMenuShell *shell) { PikaMenuShellPrivate *priv; g_return_if_fail (PIKA_IS_MENU_SHELL (shell)); priv = GET_PRIVATE (shell); priv->path_prefix = NULL; priv->model = NULL; priv->path_regex = g_regex_new ("/+", 0, 0, NULL); } /** * pika_menu_shell_install_properties: * @klass: the class structure for a type deriving from #GObject * * Installs the necessary properties for a class implementing * #PikaMenuShell. Please call this function in the *_class_init() * function of the child class. **/ void pika_menu_shell_install_properties (GObjectClass *klass) { g_object_class_override_property (klass, PIKA_MENU_SHELL_PROP_MANAGER, "manager"); g_object_class_override_property (klass, PIKA_MENU_SHELL_PROP_MODEL, "model"); } void pika_menu_shell_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaMenuShellPrivate *priv; priv = GET_PRIVATE (object); switch (property_id) { case PIKA_MENU_SHELL_PROP_MANAGER: g_value_set_object (value, priv->manager); break; case PIKA_MENU_SHELL_PROP_MODEL: g_value_set_object (value, priv->model); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } void pika_menu_shell_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaMenuShellPrivate *priv = GET_PRIVATE (object); PikaMenuShell *shell = PIKA_MENU_SHELL (object); switch (property_id) { case PIKA_MENU_SHELL_PROP_MANAGER: g_set_weak_pointer (&priv->manager, g_value_get_object (value)); break; case PIKA_MENU_SHELL_PROP_MODEL: pika_menu_shell_append (shell, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } PikaUIManager * pika_menu_shell_get_manager (PikaMenuShell *shell) { return GET_PRIVATE (shell)->manager; } /* Private functions */ static PikaMenuShellPrivate * pika_menu_shell_get_private (PikaMenuShell *menu_shell) { PikaMenuShellPrivate *priv; static GQuark private_key = 0; g_return_val_if_fail (PIKA_IS_MENU_SHELL (menu_shell), NULL); if (! private_key) private_key = g_quark_from_static_string ("pika-menu_shell-priv"); priv = g_object_get_qdata ((GObject *) menu_shell, private_key); if (! priv) { priv = g_slice_new0 (PikaMenuShellPrivate); g_object_set_qdata_full ((GObject *) menu_shell, private_key, priv, (GDestroyNotify) pika_menu_shell_private_finalize); } return priv; } static void pika_menu_shell_private_finalize (PikaMenuShellPrivate *priv) { g_clear_weak_pointer (&priv->manager); g_free (priv->path_prefix); g_clear_pointer (&priv->path_regex, g_regex_unref); g_clear_object (&priv->model); g_slice_free (PikaMenuShellPrivate, priv); } static void pika_menu_shell_model_changed (GMenuModel *model, gint position, gint removed, gint added, PikaMenuShell *shell) { PikaMenuShellPrivate *priv = GET_PRIVATE (shell); /* Lazy approach to handle model changes. A better implementation would only * remove or delete the changed items using: * PIKA_MENU_SHELL_GET_INTERFACE (shell)->add_ui() * PIKA_MENU_SHELL_GET_INTERFACE (shell)->remove_ui * TODO */ gtk_container_foreach (GTK_CONTAINER (shell), (GtkCallback) gtk_widget_destroy, NULL); PIKA_MENU_SHELL_GET_INTERFACE (shell)->append (shell, priv->model); } static void pika_menu_shell_append_model_drop_top (PikaMenuShell *shell, PikaMenuModel *model) { PikaMenuShellPrivate *priv = GET_PRIVATE (shell); GMenuModel *submenu = NULL; g_return_if_fail (GTK_IS_CONTAINER (shell)); if (g_menu_model_get_n_items (G_MENU_MODEL (model)) == 1) { gchar *label = NULL; submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), 0, G_MENU_LINK_SUBMENU); if (submenu) { gchar *path_prefix; g_menu_model_get_item_attribute (submenu, 0, G_MENU_ATTRIBUTE_LABEL, "s", &label); path_prefix = g_strdup_printf ("%s/%s", priv->path_prefix, label); g_free (priv->path_prefix); priv->path_prefix = path_prefix; } g_free (label); } pika_menu_shell_append (shell, submenu != NULL ? PIKA_MENU_MODEL (submenu) : PIKA_MENU_MODEL (model)); g_clear_object (&submenu); }