[gnome-panel] status-notifier: add items to applet



commit 71b0ce7b0c71d32b40ef92b8aed3bf3a88f4f535
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Sat Jan 9 19:13:39 2016 +0200

    status-notifier: add items to applet

 applets/status-notifier/Makefile.am     |    2 +
 applets/status-notifier/sn-applet.c     |  163 +++++++++++-
 applets/status-notifier/sn-button.c     |  444 +++++++++++++++++++++++++++++++
 applets/status-notifier/sn-button.h     |   35 +++
 data/theme/Adwaita/gnome-panel-dark.css |    4 +
 data/theme/Adwaita/gnome-panel.css      |    4 +
 data/theme/HighContrast/gnome-panel.css |    4 +
 7 files changed, 648 insertions(+), 8 deletions(-)
---
diff --git a/applets/status-notifier/Makefile.am b/applets/status-notifier/Makefile.am
index c8821dd..211b65a 100644
--- a/applets/status-notifier/Makefile.am
+++ b/applets/status-notifier/Makefile.am
@@ -17,6 +17,8 @@ libsn_applet_la_CFLAGS = \
 libsn_applet_la_SOURCES = \
        sn-applet.c \
        sn-applet.h \
+       sn-button.c \
+       sn-button.h \
        $(NULL)
 
 libsn_applet_la_LIBADD = \
diff --git a/applets/status-notifier/sn-applet.c b/applets/status-notifier/sn-applet.c
index d668803..18b891b 100644
--- a/applets/status-notifier/sn-applet.c
+++ b/applets/status-notifier/sn-applet.c
@@ -17,26 +17,131 @@
 
 #include "config.h"
 
-#include <libstatus-notifier/sn.h>
-
 #include "sn-applet.h"
+#include "sn-button.h"
 
 struct _SnApplet
 {
-  PanelApplet parent;
+  PanelApplet  parent;
+
+  SnHost      *host;
+  GSList      *items;
+
+  GtkWidget   *box;
 };
 
 G_DEFINE_TYPE (SnApplet, sn_applet, PANEL_TYPE_APPLET)
 
+static gint
+compare_items (gconstpointer a,
+               gconstpointer b)
+{
+  SnItem *item1;
+  SnItemCategory c1;
+  const gchar *id1;
+  SnItem *item2;
+  SnItemCategory c2;
+  const gchar *id2;
+
+  item1 = SN_ITEM (a);
+  c1 = sn_item_get_category (item1);
+
+  item2 = SN_ITEM (b);
+  c2 = sn_item_get_category (item2);
+
+  if (c1 < c2)
+    return -1;
+
+  if (c2 < c1)
+    return 1;
+
+  id1 = sn_item_get_id (item1);
+  id2 = sn_item_get_id (item2);
+
+  return g_strcmp0 (id1, id2);
+}
+
+static void
+reorder_items (GtkWidget *widget,
+               gpointer   data)
+{
+  SnApplet *applet;
+  SnButton *button;
+  gint position;
+
+  applet = SN_APPLET (data);
+  button = SN_BUTTON (widget);
+
+  position = g_slist_index (applet->items, sn_button_get_item (button));
+
+  gtk_box_reorder_child (GTK_BOX (applet->box), widget, position);
+}
+
+static void
+item_added_cb (SnHost   *host,
+               SnItem   *item,
+               SnApplet *applet)
+{
+  GtkWidget *button;
+
+  applet->items = g_slist_prepend (applet->items, item);
+  applet->items = g_slist_sort (applet->items, compare_items);
+
+  button = sn_button_new (item);
+  gtk_box_pack_start (GTK_BOX (applet->box), button, FALSE, FALSE, 0);
+  gtk_widget_show (button);
+
+  g_object_bind_property (applet->box, "orientation", item, "orientation",
+                          G_BINDING_DEFAULT);
+
+  gtk_container_foreach (GTK_CONTAINER (applet->box), reorder_items, applet);
+}
+
+static void
+item_remove (GtkWidget *widget,
+             gpointer   data)
+{
+  SnButton *button;
+  SnItem *item;
+
+  button = SN_BUTTON (widget);
+  item = SN_ITEM (data);
+
+  if (sn_button_get_item (button) == item)
+    gtk_widget_destroy (widget);
+}
+
+static void
+item_removed_cb (SnHost   *host,
+                 SnItem   *item,
+                 SnApplet *applet)
+{
+  GSList *l;
+
+  for (l = applet->items; l != NULL; l = g_slist_next (l))
+    {
+      SnItem *tmp;
+
+      tmp = SN_ITEM (l->data);
+
+      if (tmp != item)
+        continue;
+
+      applet->items = g_slist_remove (applet->items, l->data);
+      gtk_container_foreach (GTK_CONTAINER (applet->box),
+                             item_remove, item);
+    }
+}
+
 static gboolean
 sn_applet_fill (SnApplet *applet)
 {
-  GtkWidget *label;
-
-  label = gtk_label_new ("Status Notifier Host");
+  applet->host = sn_host_new (SN_HOST_FLAGS_NONE);
 
-  gtk_container_add (GTK_CONTAINER (applet), label);
-  gtk_widget_show (label);
+  g_signal_connect (applet->host, "item-added",
+                    G_CALLBACK (item_added_cb), applet);
+  g_signal_connect (applet->host, "item-removed",
+                    G_CALLBACK (item_removed_cb), applet);
 
   gtk_widget_show (GTK_WIDGET (applet));
 
@@ -55,19 +160,61 @@ sn_applet_factory (PanelApplet *applet,
 }
 
 static void
+sn_applet_dispose (GObject *object)
+{
+  SnApplet *applet;
+
+  applet = SN_APPLET (object);
+
+  g_clear_object (&applet->host);
+  g_clear_pointer (&applet->items, g_slist_free);
+
+  G_OBJECT_CLASS (sn_applet_parent_class)->dispose (object);
+}
+
+static void
+sn_applet_change_orient (PanelApplet       *applet,
+                         PanelAppletOrient  orient)
+{
+  SnApplet *sn_applet;
+  GtkOrientation orientation;
+
+  sn_applet = SN_APPLET (applet);
+  orientation = panel_applet_get_gtk_orientation (applet);
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (sn_applet->box),
+                                  orientation);
+}
+
+static void
 sn_applet_class_init (SnAppletClass *applet_class)
 {
+  GObjectClass *object_class;
+  PanelAppletClass *panel_applet_class;
+
+  object_class = G_OBJECT_CLASS (applet_class);
+  panel_applet_class = PANEL_APPLET_CLASS (applet_class);
+
+  object_class->dispose = sn_applet_dispose;
+
+  panel_applet_class->change_orient = sn_applet_change_orient;
 }
 
 static void
 sn_applet_init (SnApplet *applet)
 {
   PanelApplet *panel_applet;
+  GtkOrientation orientation;
 
   panel_applet = PANEL_APPLET (applet);
+  orientation = panel_applet_get_gtk_orientation (panel_applet);
 
   panel_applet_set_flags (panel_applet, PANEL_APPLET_HAS_HANDLE |
                           PANEL_APPLET_EXPAND_MINOR);
+
+  applet->box = gtk_box_new (orientation, 0);
+  gtk_container_add (GTK_CONTAINER (applet), applet->box);
+  gtk_widget_show (applet->box);
 }
 
 PANEL_APPLET_IN_PROCESS_FACTORY ("SnAppletFactory", SN_TYPE_APPLET,
diff --git a/applets/status-notifier/sn-button.c b/applets/status-notifier/sn-button.c
new file mode 100644
index 0000000..4962486
--- /dev/null
+++ b/applets/status-notifier/sn-button.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2015 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-button.h"
+
+struct _SnButton
+{
+  GtkButton       parent;
+
+  SnItem         *item;
+  GtkOrientation  orientation;
+
+  gint            size;
+  GtkWidget      *image;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_ITEM,
+  PROP_ORIENTATION,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (SnButton, sn_button, GTK_TYPE_BUTTON)
+
+static gint
+get_pixel_size (SnButton *button)
+{
+  GtkStyleContext *context;
+  GtkStateFlags state;
+  GtkBorder padding;
+  guint pixel_size;
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (button));
+  state = gtk_style_context_get_state (context);
+
+  gtk_style_context_get_padding (context, state, &padding);
+
+  if (button->orientation == GTK_ORIENTATION_HORIZONTAL)
+    pixel_size = button->size - padding.top - padding.bottom;
+  else
+    pixel_size = button->size - padding.left - padding.right;
+
+  return MAX (pixel_size, 0);
+}
+
+static void
+sn_button_update_icon (SnButton *button)
+{
+  const gchar *icon_theme_path;
+  const gchar *icon_name;
+  GtkIconSize icon_size;
+  gint pixel_size;
+
+  if (button->size <= 0)
+    return;
+
+  icon_theme_path = sn_item_get_icon_theme_path (button->item);
+  if (icon_theme_path != NULL)
+    gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
+                                       icon_theme_path);
+
+  icon_name = sn_item_get_icon_name (button->item);
+  if (icon_name == NULL)
+    icon_name = "image-missing";
+
+  icon_size = GTK_ICON_SIZE_MENU;
+  pixel_size = get_pixel_size (button);
+
+  gtk_image_set_from_icon_name (GTK_IMAGE (button->image), icon_name, icon_size);
+  gtk_image_set_pixel_size (GTK_IMAGE (button->image), pixel_size);
+}
+
+static void
+changed_cb (SnItem   *item,
+            SnButton *button)
+{
+  sn_button_update_icon (button);
+}
+
+static void
+sn_button_constructed (GObject *object)
+{
+  SnButton *button;
+
+  button = SN_BUTTON (object);
+
+  G_OBJECT_CLASS (sn_button_parent_class)->constructed (object);
+
+  g_signal_connect (button->item, "changed",
+                    G_CALLBACK (changed_cb), button);
+
+  changed_cb (button->item, button);
+}
+
+static void
+sn_button_set_property (GObject      *object,
+                        guint         property_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  SnButton *button;
+
+  button = SN_BUTTON (object);
+
+  switch (property_id)
+    {
+      case PROP_ITEM:
+        button->item = g_value_get_object (value);
+        break;
+
+      case PROP_ORIENTATION:
+        button->orientation = g_value_get_enum (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+typedef enum
+{
+  SN_EVENT_TYPE_NONE,
+  SN_EVENT_TYPE_ACTIVATE,
+  SN_EVENT_TYPE_SECONDARY_ACTIVATE,
+  SN_EVENT_TYPE_CONTEXT_MENU
+} SnEventType;
+
+typedef struct
+{
+  SnButton    *button;
+
+  SnEventType  type;
+
+  gint         x;
+  gint         y;
+} SnEventData;
+
+static SnEventData *
+sn_event_data_new (SnButton    *button,
+                   SnEventType  type,
+                   gint         x,
+                   gint         y)
+{
+  SnEventData *data;
+
+  data = g_new0 (SnEventData, 1);
+
+  data->button = g_object_ref (button);
+  data->type = type;
+  data->x = x;
+  data->y = y;
+
+  return data;
+}
+
+static void
+sn_event_data_free (SnEventData *data)
+{
+  g_object_unref (data->button);
+  g_free (data);
+}
+
+static void
+position_menu (GtkMenu  *menu,
+               gint     *x,
+               gint     *y,
+               gboolean *push_in,
+               gpointer  user_data)
+{
+  SnEventData *data;
+
+  data = (SnEventData *) user_data;
+
+  *x = data->x;
+  *y = data->y;
+
+  sn_event_data_free (data);
+}
+
+static gboolean
+handle_event (gpointer user_data)
+{
+  SnEventData *data;
+
+  data = (SnEventData *) user_data;
+
+  if (data->type == SN_EVENT_TYPE_ACTIVATE)
+    sn_item_activate (data->button->item, data->x, data->y);
+  else if (data->type == SN_EVENT_TYPE_SECONDARY_ACTIVATE)
+    sn_item_secondary_activate (data->button->item, data->x, data->y);
+  else if (data->type == SN_EVENT_TYPE_CONTEXT_MENU)
+    sn_item_context_menu (data->button->item, data->x, data->y);
+
+  sn_event_data_free (data);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+sn_button_button_press_event (GtkWidget      *widget,
+                              GdkEventButton *event)
+{
+  SnButton *button;
+  GdkWindow *window;
+  GtkWidget *toplevel;
+  gint x;
+  gint y;
+  gint width;
+  gint height;
+  GtkMenu *menu;
+  SnEventData *data;
+
+  if (event->button < 1 || event->button > 3)
+    return GTK_WIDGET_CLASS (sn_button_parent_class)->button_press_event (widget, event);
+
+  button = SN_BUTTON (widget);
+
+  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 (button->orientation == GTK_ORIENTATION_HORIZONTAL)
+    y += height;
+  else
+    x += width;
+
+  menu = sn_item_get_menu (button->item);
+
+  if (menu != NULL)
+    {
+      data = sn_event_data_new (button, SN_EVENT_TYPE_NONE, x, y);
+      gtk_menu_popup (GTK_MENU (menu), NULL, NULL, position_menu, data,
+                      event->button, event->time);
+    }
+  else
+    {
+      SnEventType type;
+
+      if (event->button == 1)
+        type = SN_EVENT_TYPE_ACTIVATE;
+      else if (event->button == 2)
+        type = SN_EVENT_TYPE_SECONDARY_ACTIVATE;
+      else if (event->button == 3)
+        type = SN_EVENT_TYPE_CONTEXT_MENU;
+      else
+        type = SN_EVENT_TYPE_NONE;
+
+      if (type != SN_EVENT_TYPE_NONE)
+        {
+          data = sn_event_data_new (button, type, x, y);
+          g_timeout_add (200, handle_event, data);
+
+          return GDK_EVENT_STOP;
+        }
+    }
+
+  return GTK_WIDGET_CLASS (sn_button_parent_class)->button_press_event (widget, event);;
+}
+
+static gboolean
+sn_button_scroll_event (GtkWidget      *widget,
+                        GdkEventScroll *event)
+{
+  SnButton *button;
+  GdkScrollDirection direction;
+  SnItemOrientation orientation;
+  gdouble dx;
+  gdouble dy;
+  gint delta;
+
+  button = SN_BUTTON (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_scroll (button->item, delta, orientation);
+
+  return GDK_EVENT_STOP;
+}
+
+static void
+sn_button_size_allocate (GtkWidget     *widget,
+                         GtkAllocation *allocation)
+{
+  SnButton *button;
+  gint size;
+
+  button = SN_BUTTON (widget);
+
+  GTK_WIDGET_CLASS (sn_button_parent_class)->size_allocate (widget, allocation);
+
+  if (button->orientation == GTK_ORIENTATION_HORIZONTAL)
+    size = allocation->height;
+  else
+    size = allocation->width;
+
+  if (button->size == size)
+    return;
+
+  button->size = size;
+
+  sn_button_update_icon (button);
+}
+
+static void
+sn_button_style_updated (GtkWidget *widget)
+{
+  GTK_WIDGET_CLASS (sn_button_parent_class)->style_updated (widget);
+}
+
+static void
+sn_button_install_properties (GObjectClass *object_class)
+{
+  properties[PROP_ITEM] =
+    g_param_spec_object ("item", "item", "item", SN_TYPE_ITEM,
+                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                         G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_ORIENTATION] =
+    g_param_spec_enum ("orientation", "orientation", "orientation",
+                       GTK_TYPE_ORIENTATION, GTK_ORIENTATION_HORIZONTAL,
+                       G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+sn_button_class_init (SnButtonClass *button_class)
+{
+  GObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+
+  object_class = G_OBJECT_CLASS (button_class);
+  widget_class = GTK_WIDGET_CLASS (button_class);
+
+  object_class->constructed = sn_button_constructed;
+  object_class->set_property = sn_button_set_property;
+
+  widget_class->button_press_event = sn_button_button_press_event;
+  widget_class->scroll_event = sn_button_scroll_event;
+  widget_class->size_allocate = sn_button_size_allocate;
+  widget_class->style_updated = sn_button_style_updated;
+
+  sn_button_install_properties (object_class);
+
+  gtk_widget_class_set_css_name (widget_class, "sn-button");
+}
+
+static void
+sn_button_init (SnButton *button)
+{
+  button->image = gtk_image_new ();
+  gtk_container_add (GTK_CONTAINER (button), button->image);
+  gtk_widget_show (button->image);
+
+  gtk_widget_add_events (GTK_WIDGET (button), GDK_SCROLL_MASK);
+}
+
+GtkWidget *
+sn_button_new (SnItem *item)
+{
+  return g_object_new (SN_TYPE_BUTTON, "item", item, NULL);
+}
+
+SnItem *
+sn_button_get_item (SnButton *button)
+{
+  return button->item;
+}
diff --git a/applets/status-notifier/sn-button.h b/applets/status-notifier/sn-button.h
new file mode 100644
index 0000000..daeec97
--- /dev/null
+++ b/applets/status-notifier/sn-button.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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/>.
+ */
+
+#ifndef SN_BUTTON_H
+#define SN_BUTTON_H
+
+#include <gtk/gtk.h>
+#include <libstatus-notifier/sn.h>
+
+G_BEGIN_DECLS
+
+#define SN_TYPE_BUTTON sn_button_get_type ()
+G_DECLARE_FINAL_TYPE (SnButton, sn_button, SN, BUTTON, GtkButton)
+
+GtkWidget *sn_button_new      (SnItem   *item);
+
+SnItem    *sn_button_get_item (SnButton *button);
+
+G_END_DECLS
+
+#endif
diff --git a/data/theme/Adwaita/gnome-panel-dark.css b/data/theme/Adwaita/gnome-panel-dark.css
index 018c8fb..bbfd517 100644
--- a/data/theme/Adwaita/gnome-panel-dark.css
+++ b/data/theme/Adwaita/gnome-panel-dark.css
@@ -22,3 +22,7 @@ gp-arrow-button:hover {
 gp-arrow-button:active {
   background-image: linear-gradient(to bottom, #232727, #2d3232);
 }
+
+sn-button {
+  padding: 4px;
+}
diff --git a/data/theme/Adwaita/gnome-panel.css b/data/theme/Adwaita/gnome-panel.css
index 5b465a0..ef98033 100644
--- a/data/theme/Adwaita/gnome-panel.css
+++ b/data/theme/Adwaita/gnome-panel.css
@@ -22,3 +22,7 @@ gp-arrow-button:hover {
 gp-arrow-button:active {
   background-image: linear-gradient(to bottom, #c8c8c5, #dcdcda);
 }
+
+sn-button {
+  padding: 4px;
+}
diff --git a/data/theme/HighContrast/gnome-panel.css b/data/theme/HighContrast/gnome-panel.css
index be097e1..c18d628 100644
--- a/data/theme/HighContrast/gnome-panel.css
+++ b/data/theme/HighContrast/gnome-panel.css
@@ -22,3 +22,7 @@ gp-arrow-button:hover {
 gp-arrow-button:active {
   background-color: #000000;
 }
+
+sn-button {
+  padding: 4px;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]