From 02f9598aadefffcd9e0370ecc2665d3f626e4afc Mon Sep 17 00:00:00 2001 From: Robert Tari Date: Tue, 24 Nov 2020 22:42:18 +0100 Subject: Add a new menu item type: IDO Removable --- src/Makefile.am | 2 + src/idomenuitemfactory.c | 4 + src/idoremovablemenuitem.c | 500 +++++++++++++++++++++++++++++++++++++++++++++ src/idoremovablemenuitem.h | 37 ++++ 4 files changed, 543 insertions(+) create mode 100644 src/idoremovablemenuitem.c create mode 100644 src/idoremovablemenuitem.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 718c59e..12a11c3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,7 @@ sources_h = \ idousermenuitem.h \ idoappointmentmenuitem.h \ idobasicmenuitem.h \ + idoremovablemenuitem.h \ idoprogressmenuitem.h \ idotimestampmenuitem.h \ idolocationmenuitem.h \ @@ -88,6 +89,7 @@ libayatana_ido_0_4_la_SOURCES = \ idoplaybackmenuitem.c \ idoappointmentmenuitem.c \ idobasicmenuitem.c \ + idoremovablemenuitem.c \ idoprogressmenuitem.c \ idotimestampmenuitem.c \ idolocationmenuitem.c \ diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c index a248b42..e259664 100644 --- a/src/idomenuitemfactory.c +++ b/src/idomenuitemfactory.c @@ -32,6 +32,7 @@ #include "idosourcemenuitem.h" #include "idoswitchmenuitem.h" #include "idoprogressmenuitem.h" +#include "idoremovablemenuitem.h" #define IDO_TYPE_MENU_ITEM_FACTORY (ido_menu_item_factory_get_type ()) #define IDO_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MENU_ITEM_FACTORY, IdoMenuItemFactory)) @@ -98,6 +99,9 @@ ido_menu_item_factory_create_menu_item (AyatanaMenuItemFactory *factory, else if (g_str_equal (type, "org.ayatana.indicator.switch")) item = ido_switch_menu_item_new_from_menu_model (menuitem, actions); + else if (g_str_equal (type, "org.ayatana.indicator.removable")) + item = ido_removable_menu_item_new_from_model (menuitem, actions); + return item; } diff --git a/src/idoremovablemenuitem.c b/src/idoremovablemenuitem.c new file mode 100644 index 0000000..dc6b60f --- /dev/null +++ b/src/idoremovablemenuitem.c @@ -0,0 +1,500 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include + +#include "idoactionhelper.h" +#include "idoremovablemenuitem.h" + +enum +{ + PROP_0, + PROP_ICON, + PROP_TEXT, + PROP_LAST +}; + +static GParamSpec *lProperties[PROP_LAST]; + +typedef struct +{ + GIcon *pIcon; + char *sText; + GtkWidget *pImage; + GtkWidget *pLabel; + GtkWidget *pButton; + gboolean bClosePressed; + IdoActionHelper *pHelper; + +} IdoRemovableMenuItemPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE(IdoRemovableMenuItem, ido_removable_menu_item, GTK_TYPE_MENU_ITEM); + +static void onGetProperty(GObject *pObject, guint nProperty, GValue *pValue, GParamSpec *pParamSpec) +{ + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pObject); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + switch (nProperty) + { + case PROP_ICON: + { + g_value_set_object(pValue, pPrivate->pIcon); + + break; + } + case PROP_TEXT: + { + g_value_set_string(pValue, pPrivate->sText); + + break; + } + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(pObject, nProperty, pParamSpec); + + break; + } + } +} + +static void onSetProperty(GObject *pObject, guint nProperty, const GValue *pValue, GParamSpec *pParamSpec) +{ + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pObject); + + switch (nProperty) + { + case PROP_ICON: + { + idoRemovableMenuItemSetIcon(self, g_value_get_object(pValue)); + + break; + } + case PROP_TEXT: + { + idoRemovableMenuItemSetText(self, g_value_get_string(pValue)); + + break; + } + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(pObject, nProperty, pParamSpec); + + break; + } + } +} + +static void onDispose(GObject *pObject) +{ + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pObject); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + g_clear_object (&pPrivate->pIcon); + G_OBJECT_CLASS(ido_removable_menu_item_parent_class)->dispose(pObject); +} + +static void onFinalize(GObject * pObject) +{ + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pObject); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + g_free(pPrivate->sText); + G_OBJECT_CLASS (ido_removable_menu_item_parent_class)->finalize(pObject); +} + +static void idoRemovableMenuItemStyleUpdateImage(IdoRemovableMenuItem *self) +{ + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + gtk_image_clear(GTK_IMAGE(pPrivate->pImage)); + + if (pPrivate->pIcon == NULL) + { + gtk_widget_set_visible(pPrivate->pImage, FALSE); + } + else + { + GtkIconInfo *pInfo; + const gchar *sFilename; + + pInfo = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), pPrivate->pIcon, 16, 0); + sFilename = gtk_icon_info_get_filename(pInfo); + + if (sFilename) + { + GdkPixbuf *pPixbuf; + + pPixbuf = gdk_pixbuf_new_from_file_at_scale(sFilename, -1, 16, TRUE, NULL); + gtk_image_set_from_pixbuf(GTK_IMAGE(pPrivate->pImage), pPixbuf); + g_object_unref (pPixbuf); + } + + gtk_widget_set_visible(pPrivate->pImage, sFilename != NULL); + g_object_unref(pInfo); + } +} + +static void onStyleUpdated(GtkWidget *pWidget) +{ + GTK_WIDGET_CLASS(ido_removable_menu_item_parent_class)->style_updated(pWidget); + idoRemovableMenuItemStyleUpdateImage(IDO_REMOVABLE_MENU_ITEM(pWidget)); + gtk_widget_queue_draw(pWidget); +} + +static gboolean idoRemovableMenuItemIsWidget(GtkWidget *pWidget, GdkEventButton *pEventButton) +{ + if (gtk_widget_get_window(pWidget) == NULL) + { + return FALSE; + } + + GtkAllocation cAllocation; + gtk_widget_get_allocation(pWidget, &cAllocation); + + GdkWindow *window = gtk_widget_get_window(pWidget); + + int nXWin, nYWin; + + gdk_window_get_origin(window, &nXWin, &nYWin); + + int nXMin = cAllocation.x; + int nXMax = cAllocation.x + cAllocation.width; + int nYMin = cAllocation.y; + int nYMax = cAllocation.y + cAllocation.height; + int nX = pEventButton->x_root - nXWin; + int nY = pEventButton->y_root - nYWin; + + return nX >= nXMin && nX <= nXMax && nY >= nYMin && nY <= nYMax; +} + +static gboolean onButtonPress(GtkWidget *pWidget, GdkEventButton *pEventButton) +{ + g_return_val_if_fail(IDO_IS_REMOVABLE_MENU_ITEM(pWidget), FALSE); + + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pWidget); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + if (pEventButton->button == GDK_BUTTON_PRIMARY) + { + if (idoRemovableMenuItemIsWidget(pPrivate->pLabel, pEventButton)) + { + gtk_widget_event(pPrivate->pLabel, (GdkEvent *)pEventButton); + } + else if (idoRemovableMenuItemIsWidget(pPrivate->pButton, pEventButton)) + { + gtk_widget_set_state_flags(pPrivate->pButton, GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE, TRUE); + pPrivate->bClosePressed = TRUE; + } + } + + return TRUE; +} + +static gboolean onButtonRelease(GtkWidget *pWidget, GdkEventButton *pEventButton) +{ + g_return_val_if_fail(IDO_IS_REMOVABLE_MENU_ITEM(pWidget), FALSE); + + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pWidget); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + if (pEventButton->button == GDK_BUTTON_PRIMARY) + { + if (idoRemovableMenuItemIsWidget(pPrivate->pButton, pEventButton)) + { + if (pPrivate->bClosePressed) + { + GtkWidget *pParent = gtk_widget_get_parent(GTK_WIDGET(self)); + GList *lMenuItems = gtk_container_get_children(GTK_CONTAINER(pParent)); + guint nRemovables = 0; + + for(GList *pElem = lMenuItems; pElem; pElem = pElem->next) + { + if (IDO_IS_REMOVABLE_MENU_ITEM(pElem->data)) + { + nRemovables++; + } + } + + g_list_free(lMenuItems); + + if (pPrivate->pHelper) + { + ido_action_helper_activate(pPrivate->pHelper); + } + else + { + gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(self)); + } + + if (GTK_IS_MENU_SHELL(pParent) && nRemovables == 1) + { + gtk_menu_shell_deactivate(GTK_MENU_SHELL(pParent)); + } + } + } + else if (idoRemovableMenuItemIsWidget(pPrivate->pLabel, pEventButton)) + { + gtk_widget_event(pPrivate->pLabel, (GdkEvent *)pEventButton); + } + } + + gtk_widget_set_state_flags(pPrivate->pButton, GTK_STATE_FLAG_NORMAL, TRUE); + pPrivate->bClosePressed = FALSE; + + return TRUE; +} + +static gboolean onMotionNotify(GtkWidget *pMenuItem, GdkEventMotion *pEventMotion) +{ + g_return_val_if_fail(IDO_IS_REMOVABLE_MENU_ITEM(pMenuItem), FALSE); + + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pMenuItem); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + GtkAllocation cAllocationSelf; + + gtk_widget_get_allocation(GTK_WIDGET(self), &cAllocationSelf); + + GtkWidget *pWidget; + + if (idoRemovableMenuItemIsWidget(pPrivate->pButton, (GdkEventButton*)pEventMotion)) + { + pWidget = pPrivate->pButton; + + if (!pPrivate->bClosePressed) + { + gtk_widget_set_state_flags(pPrivate->pButton, GTK_STATE_FLAG_FOCUSED, TRUE); + } + else + { + gtk_widget_set_state_flags(pPrivate->pButton, GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE, TRUE); + } + } + else + { + pWidget = pPrivate->pLabel; + gtk_widget_set_state_flags(pPrivate->pButton, GTK_STATE_FLAG_NORMAL, TRUE); + } + + GtkAllocation cAllocationLabel; + + gtk_widget_get_allocation(pWidget, &cAllocationLabel); + + GdkEventMotion *pEventMotionNew = (GdkEventMotion *)gdk_event_copy((GdkEvent *)pEventMotion); + pEventMotionNew->x = pEventMotion->x - (cAllocationLabel.x - cAllocationSelf.x); + pEventMotionNew->y = pEventMotion->y - (cAllocationLabel.y - cAllocationSelf.y); + + gtk_widget_event(pWidget, (GdkEvent *)pEventMotionNew); + gdk_event_free((GdkEvent *)pEventMotionNew); + + return FALSE; +} + +static gboolean onLeaveNotify(GtkWidget *pWidget, GdkEventCrossing *pEventCrossing) +{ + g_return_val_if_fail(IDO_IS_REMOVABLE_MENU_ITEM(pWidget), FALSE); + + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pWidget); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + gtk_widget_event(pPrivate->pLabel, (GdkEvent *)pEventCrossing); + + return FALSE; +} + +static void ido_removable_menu_item_class_init(IdoRemovableMenuItemClass *klass) +{ + GObjectClass *pObject = G_OBJECT_CLASS(klass); + GtkWidgetClass *pWidget = GTK_WIDGET_CLASS(klass); + GtkMenuItemClass *pMenuItem = GTK_MENU_ITEM_CLASS(klass); + pObject->get_property = onGetProperty; + pObject->set_property = onSetProperty; + pObject->dispose = onDispose; + pObject->finalize = onFinalize; + pWidget->style_updated = onStyleUpdated; + pWidget->leave_notify_event = onLeaveNotify; + pWidget->motion_notify_event = onMotionNotify; + pWidget->button_press_event = onButtonPress; + pWidget->button_release_event = onButtonRelease; + pMenuItem->select = NULL; + + GParamFlags nParamFlags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS; + lProperties[PROP_ICON] = g_param_spec_object("icon", "Icon", "The menuitem's GIcon", G_TYPE_OBJECT, nParamFlags); + lProperties[PROP_TEXT] = g_param_spec_string ("text", "Text", "The menuitem's text", "", nParamFlags); + + g_object_class_install_properties(pObject, PROP_LAST, lProperties); +} + +static gboolean onActivateLink(GtkLabel *pLabel, gchar *sUri, gpointer pUserData) +{ + g_return_val_if_fail(IDO_IS_REMOVABLE_MENU_ITEM(pUserData), FALSE); + + IdoRemovableMenuItem *self = IDO_REMOVABLE_MENU_ITEM(pUserData); + GError *pError = NULL; + + if (!gtk_show_uri_on_window(NULL, sUri, gtk_get_current_event_time(), &pError)) + { + g_warning("Unable to show '%s': %s", sUri, pError->message); + g_error_free(pError); + } + + GtkWidget *pParent = gtk_widget_get_parent(GTK_WIDGET(self)); + + if (GTK_IS_MENU_SHELL(pParent)) + { + gtk_menu_shell_deactivate(GTK_MENU_SHELL(pParent)); + } + + return TRUE; +} + +static void ido_removable_menu_item_init(IdoRemovableMenuItem *self) +{ + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + pPrivate->bClosePressed = FALSE; + pPrivate->pHelper = NULL; + pPrivate->pImage = gtk_image_new(); + + gtk_widget_set_halign(pPrivate->pImage, GTK_ALIGN_START); + gtk_widget_set_valign(pPrivate->pImage, GTK_ALIGN_START); + + pPrivate->pLabel = gtk_label_new(""); + + gtk_widget_set_halign(pPrivate->pLabel, GTK_ALIGN_START); + gtk_widget_set_valign(pPrivate->pLabel, GTK_ALIGN_CENTER); + + pPrivate->pButton = gtk_button_new(); + + gtk_button_set_label(GTK_BUTTON(pPrivate->pButton), "X"); + gtk_widget_set_halign(pPrivate->pButton, GTK_ALIGN_CENTER); + gtk_widget_set_valign(pPrivate->pButton, GTK_ALIGN_CENTER); + + GtkWidget *pWidget = gtk_grid_new(); + GtkGrid *pGrid = GTK_GRID(pWidget); + + gtk_grid_attach(pGrid, pPrivate->pImage, 0, 0, 1, 1); + gtk_grid_attach(pGrid, pPrivate->pLabel, 1, 0, 1, 1); + gtk_grid_attach(pGrid, pPrivate->pButton, 2, 0, 1, 1); + g_object_set(pPrivate->pImage, "halign", GTK_ALIGN_START, "hexpand", FALSE, "valign", GTK_ALIGN_CENTER, "margin-right", 6, NULL); + g_object_set(pPrivate->pLabel, "halign", GTK_ALIGN_START, "hexpand", TRUE, "margin-right", 6, "valign", GTK_ALIGN_CENTER, NULL); + gtk_widget_show (pWidget); + gtk_container_add(GTK_CONTAINER(self), pWidget); + g_signal_connect(pPrivate->pLabel, "activate-link", G_CALLBACK(onActivateLink), self); +} + +GtkWidget *ido_removable_menu_item_new (void) +{ + return GTK_WIDGET(g_object_new(IDO_TYPE_REMOVABLE_MENU_ITEM, NULL)); +} + +void idoRemovableMenuItemSetIcon(IdoRemovableMenuItem *self, GIcon *pIcon) +{ + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + if (pPrivate->pIcon != pIcon) + { + if (pPrivate->pIcon) + { + g_object_unref(pPrivate->pIcon); + } + + pPrivate->pIcon = pIcon ? g_object_ref(pIcon) : NULL; + idoRemovableMenuItemStyleUpdateImage(self); + } +} + +void idoRemovableMenuItemSetIconFromFile(IdoRemovableMenuItem *self, const char *sFilename) +{ + GFile *pFile = sFilename ? g_file_new_for_path(sFilename) : NULL; + GIcon *pIcon = pFile ? g_file_icon_new(pFile) : NULL; + + idoRemovableMenuItemSetIcon(self, pIcon); + + g_clear_object(&pIcon); + g_clear_object(&pFile); +} + +void idoRemovableMenuItemSetText(IdoRemovableMenuItem *self, const char *sText) +{ + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(self); + + if (g_strcmp0(pPrivate->sText, sText)) + { + g_free(pPrivate->sText); + pPrivate->sText = g_strdup(sText); + + g_object_set (G_OBJECT(pPrivate->pLabel), "label", pPrivate->sText, "visible", (gboolean)(pPrivate->sText && *pPrivate->sText), NULL); + } +} + +GtkMenuItem *ido_removable_menu_item_new_from_model(GMenuItem *pMenuItem, GActionGroup *pActionGroup) +{ + GtkWidget *pItem = ido_removable_menu_item_new(); + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(IDO_REMOVABLE_MENU_ITEM(pItem)); + gboolean bUseMarkup = FALSE; + g_menu_item_get_attribute(pMenuItem, "x-ayatana-use-markup", "b", &bUseMarkup); + idoRemovableMenuItemUseMarkup(IDO_REMOVABLE_MENU_ITEM(pItem), bUseMarkup); + + gchar *sLabel; + + if (g_menu_item_get_attribute(pMenuItem, "label", "s", &sLabel)) + { + idoRemovableMenuItemSetText(IDO_REMOVABLE_MENU_ITEM(pItem), sLabel); + g_free(sLabel); + } + + GVariant *sIcon = g_menu_item_get_attribute_value(pMenuItem, "icon", NULL); + + if (sIcon) + { + GIcon *pIcon = g_icon_deserialize(sIcon); + idoRemovableMenuItemSetIcon(IDO_REMOVABLE_MENU_ITEM(pItem), pIcon); + g_object_unref(pIcon); + g_variant_unref(sIcon); + } + + gchar *sAction; + + if (g_menu_item_get_attribute(pMenuItem, "action", "s", &sAction)) + { + GVariant *sTarget = g_menu_item_get_attribute_value(pMenuItem, "target", NULL); + pPrivate->pHelper = ido_action_helper_new(pItem, pActionGroup, sAction, sTarget); + g_signal_connect_swapped(pItem, "destroy", G_CALLBACK (g_object_unref), pPrivate->pHelper); + + if (sTarget) + { + g_variant_unref(sTarget); + } + + g_free(sAction); + } + + return GTK_MENU_ITEM(pItem); +} + +void idoRemovableMenuItemUseMarkup(IdoRemovableMenuItem *self, gboolean bUse) +{ + IdoRemovableMenuItemPrivate *pPrivate = ido_removable_menu_item_get_instance_private(IDO_REMOVABLE_MENU_ITEM(self)); + g_object_set(pPrivate->pLabel, "use-markup", bUse, NULL); +} diff --git a/src/idoremovablemenuitem.h b/src/idoremovablemenuitem.h new file mode 100644 index 0000000..045d579 --- /dev/null +++ b/src/idoremovablemenuitem.h @@ -0,0 +1,37 @@ +#ifndef __IDO_REMOVABLE_MENU_ITEM_H__ +#define __IDO_REMOVABLE_MENU_ITEM_H__ + +#include + +G_BEGIN_DECLS + +#define IDO_TYPE_REMOVABLE_MENU_ITEM (ido_removable_menu_item_get_type()) +#define IDO_REMOVABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), IDO_TYPE_REMOVABLE_MENU_ITEM, IdoRemovableMenuItem)) +#define IDO_IS_REMOVABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDO_TYPE_REMOVABLE_MENU_ITEM)) + +typedef struct _IdoRemovableMenuItem IdoRemovableMenuItem; +typedef struct _IdoRemovableMenuItemClass IdoRemovableMenuItemClass; + +struct _IdoRemovableMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + + +struct _IdoRemovableMenuItem +{ + GtkMenuItem parent; +}; + + +GType ido_removable_menu_item_get_type()G_GNUC_CONST; +GtkWidget * ido_removable_menu_item_new(); +void idoRemovableMenuItemSetIcon(IdoRemovableMenuItem *self, GIcon *pIcon); +void idoRemovableMenuItemSetIconFromFile(IdoRemovableMenuItem *self, const char *sFilename); +void idoRemovableMenuItemSetText(IdoRemovableMenuItem *self, const char *sText); +void idoRemovableMenuItemUseMarkup(IdoRemovableMenuItem *self, gboolean bUse); +GtkMenuItem* ido_removable_menu_item_new_from_model(GMenuItem *pMenuItem, GActionGroup *pActionGroup); + +G_END_DECLS + +#endif -- cgit v1.2.3