aboutsummaryrefslogtreecommitdiff
path: root/src/idoremovablemenuitem.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/idoremovablemenuitem.c')
-rw-r--r--src/idoremovablemenuitem.c500
1 files changed, 500 insertions, 0 deletions
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 <charles.kerr@canonical.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+
+#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);
+}