summaryrefslogtreecommitdiff
path: root/applets/notification_area/status-notifier/sn-item.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/notification_area/status-notifier/sn-item.c')
-rw-r--r--applets/notification_area/status-notifier/sn-item.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/applets/notification_area/status-notifier/sn-item.c b/applets/notification_area/status-notifier/sn-item.c
new file mode 100644
index 00000000..13ee68f3
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-item.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "sn-dbus-menu.h"
+#include "sn-item.h"
+
+#include "na-item.h"
+
+typedef struct
+{
+ gchar *bus_name;
+ gchar *object_path;
+
+ GtkOrientation orientation;
+
+ GtkMenu *menu;
+} SnItemPrivate;
+
+enum
+{
+ PROP_0,
+
+ PROP_BUS_NAME,
+ PROP_OBJECT_PATH,
+
+ PROP_ORIENTATION,
+
+ LAST_PROP
+};
+
+enum
+{
+ SIGNAL_READY,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void na_item_init (NaItemInterface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SnItem, sn_item, GTK_TYPE_BUTTON,
+ //~ G_ADD_PRIVATE (SnItem);
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
+ NULL)
+ G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM,
+ na_item_init))
+
+/* FIXME: apparently if requesting < 2.38, G_ADD_PRIVATE() or the default
+ * sn_item_get_instance_private() does NOT work, and returns some garbage
+ * (either get_instance_private() works but G_ADD_PRIVATE() didn't do nothing,
+ * or the other way around, but it leads to using incorrect memory) */
+#define sn_item_get_instance_private(i) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((i), SN_TYPE_ITEM, SnItemPrivate))
+
+static void
+sn_item_dispose (GObject *object)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ g_clear_object (&priv->menu);
+
+ G_OBJECT_CLASS (sn_item_parent_class)->dispose (object);
+}
+
+static void
+sn_item_finalize (GObject *object)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ g_clear_pointer (&priv->bus_name, g_free);
+ g_clear_pointer (&priv->object_path, g_free);
+
+ G_OBJECT_CLASS (sn_item_parent_class)->finalize (object);
+}
+
+static void
+sn_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ g_value_set_string (value, priv->bus_name);
+ break;
+
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, priv->orientation);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (object);
+ priv = sn_item_get_instance_private (item);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ priv->bus_name = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_ORIENTATION:
+ priv->orientation = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sn_item_get_action_coordinates (SnItem *item,
+ gint *x,
+ gint *y)
+{
+ GtkWidget *widget;
+ SnItemPrivate *priv;
+ GdkWindow *window;
+ GtkWidget *toplevel;
+ gint width;
+ gint height;
+
+ priv = sn_item_get_instance_private (item);
+ widget = GTK_WIDGET (item);
+ window = gtk_widget_get_window (widget);
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ gdk_window_get_geometry (window, x, y, &width, &height);
+ gtk_widget_translate_coordinates (widget, toplevel, *x, *y, x, y);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ *y += height;
+ else
+ *x += width;
+}
+
+static gboolean
+sn_item_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ gint x;
+ gint y;
+
+ if (event->button < 2 || event->button > 3)
+ return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event);
+
+ item = SN_ITEM (widget);
+ priv = sn_item_get_instance_private (item);
+ display = gdk_display_get_default ();
+ seat = gdk_display_get_default_seat (display);
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ if (event->button == 2)
+ {
+ gdk_seat_ungrab (seat);
+ SN_ITEM_GET_CLASS (item)->secondary_activate (item, x, y);
+ }
+ else if (event->button == 3)
+ {
+ if (priv->menu != NULL)
+ {
+ gtk_menu_popup_at_widget (priv->menu, widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ (GdkEvent *) event);
+ }
+ else
+ {
+ gdk_seat_ungrab (seat);
+ SN_ITEM_GET_CLASS (item)->context_menu (item, x, y);
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+sn_item_popup_menu (GtkWidget *widget)
+{
+ SnItem *item;
+ SnItemPrivate *priv;
+
+ item = SN_ITEM (widget);
+ priv = sn_item_get_instance_private (item);
+
+ if (priv->menu != NULL)
+ {
+ gtk_menu_popup_at_widget (priv->menu, widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ NULL);
+ }
+ else
+ {
+ gint x;
+ gint y;
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ SN_ITEM_GET_CLASS (item)->context_menu (item, x, y);
+ }
+
+ return TRUE;
+}
+
+static void
+sn_item_clicked (GtkButton *button)
+{
+ SnItem *item;
+ gint x;
+ gint y;
+
+ item = SN_ITEM (button);
+
+ sn_item_get_action_coordinates (item, &x, &y);
+
+ SN_ITEM_GET_CLASS (item)->activate (item, x, y);
+}
+
+static gboolean
+sn_item_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ SnItem *item;
+ GdkScrollDirection direction;
+ SnItemOrientation orientation;
+ gdouble dx;
+ gdouble dy;
+ gint delta;
+
+ item = SN_ITEM (widget);
+
+ if (!gdk_event_get_scroll_direction ((GdkEvent *) event, &direction))
+ {
+ g_assert_not_reached ();
+ }
+ else
+ {
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_DOWN:
+ orientation = SN_ITEM_ORIENTATION_VERTICAL;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ orientation = SN_ITEM_ORIENTATION_HORIZONTAL;
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy))
+ {
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_LEFT:
+ delta = 1;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_RIGHT:
+ delta = -1;
+ break;
+
+ case GDK_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else
+ {
+ if (dy != 0)
+ delta = (gint) dy;
+ else
+ delta = (gint) dx;
+ }
+
+ SN_ITEM_GET_CLASS (item)->scroll (item, delta, orientation);
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+sn_item_ready (SnItem *item)
+{
+ const gchar *menu;
+ SnItemPrivate *priv;
+
+ menu = SN_ITEM_GET_CLASS (item)->get_menu (item);
+ if (menu == NULL)
+ return;
+
+ if (menu == NULL || *menu == '\0' || g_strcmp0 (menu, "/") == 0)
+ return;
+
+ priv = sn_item_get_instance_private (item);
+ priv->menu = sn_dbus_menu_new (priv->bus_name, menu);
+ g_object_ref_sink (priv->menu);
+}
+
+static const gchar *
+sn_item_get_id (NaItem *item)
+{
+ return SN_ITEM_GET_CLASS (item)->get_id (SN_ITEM (item));
+}
+
+static NaItemCategory
+sn_item_get_category (NaItem *item)
+{
+ const gchar *string;
+ NaItemCategory category;
+
+ string = SN_ITEM_GET_CLASS (item)->get_category (SN_ITEM (item));
+
+ if (g_strcmp0 (string, "Hardware") == 0)
+ category = NA_ITEM_CATEGORY_HARDWARE;
+ else if (g_strcmp0 (string, "SystemServices") == 0)
+ category = NA_ITEM_CATEGORY_SYSTEM_SERVICES;
+ else if (g_strcmp0 (string, "Communications") == 0)
+ category = NA_ITEM_CATEGORY_COMMUNICATIONS;
+ else
+ category = NA_ITEM_CATEGORY_APPLICATION_STATUS;
+
+ return category;
+}
+
+static void
+na_item_init (NaItemInterface *iface)
+{
+ iface->get_id = sn_item_get_id;
+ iface->get_category = sn_item_get_category;
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+ g_object_class_install_property (object_class, PROP_BUS_NAME,
+ g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path", "object-path", "object-path", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_override_property (object_class, PROP_ORIENTATION,
+ "orientation");
+}
+
+static void
+install_signals (SnItemClass *item_class)
+{
+ signals[SIGNAL_READY] =
+ g_signal_new ("ready", G_TYPE_FROM_CLASS (item_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (SnItemClass, ready), NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+sn_item_class_init (SnItemClass *item_class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkButtonClass *button_class;
+
+ object_class = G_OBJECT_CLASS (item_class);
+ widget_class = GTK_WIDGET_CLASS (item_class);
+ button_class = GTK_BUTTON_CLASS (item_class);
+
+ object_class->dispose = sn_item_dispose;
+ object_class->finalize = sn_item_finalize;
+ object_class->get_property = sn_item_get_property;
+ object_class->set_property = sn_item_set_property;
+
+ widget_class->button_press_event = sn_item_button_press_event;
+ widget_class->popup_menu = sn_item_popup_menu;
+ widget_class->scroll_event = sn_item_scroll_event;
+
+ button_class->clicked = sn_item_clicked;
+
+ item_class->ready = sn_item_ready;
+
+ install_properties (object_class);
+ install_signals (item_class);
+
+ g_type_class_add_private (item_class, sizeof (SnItemPrivate));
+}
+
+static void
+sn_item_init (SnItem *item)
+{
+ gtk_widget_add_events (GTK_WIDGET (item), GDK_SCROLL_MASK);
+}
+
+const gchar *
+sn_item_get_bus_name (SnItem *item)
+{
+ SnItemPrivate *priv;
+
+ priv = sn_item_get_instance_private (item);
+
+ return priv->bus_name;
+}
+
+const gchar *
+sn_item_get_object_path (SnItem *item)
+{
+ SnItemPrivate *priv;
+
+ priv = sn_item_get_instance_private (item);
+
+ return priv->object_path;
+}
+
+void
+sn_item_emit_ready (SnItem *item)
+{
+ g_signal_emit (item, signals[SIGNAL_READY], 0);
+}