Commit 71b0ce7b authored by Alberts Muktupāvels's avatar Alberts Muktupāvels

status-notifier: add items to applet

parent f77b7335
...@@ -17,6 +17,8 @@ libsn_applet_la_CFLAGS = \ ...@@ -17,6 +17,8 @@ libsn_applet_la_CFLAGS = \
libsn_applet_la_SOURCES = \ libsn_applet_la_SOURCES = \
sn-applet.c \ sn-applet.c \
sn-applet.h \ sn-applet.h \
sn-button.c \
sn-button.h \
$(NULL) $(NULL)
libsn_applet_la_LIBADD = \ libsn_applet_la_LIBADD = \
......
...@@ -17,26 +17,131 @@ ...@@ -17,26 +17,131 @@
#include "config.h" #include "config.h"
#include <libstatus-notifier/sn.h>
#include "sn-applet.h" #include "sn-applet.h"
#include "sn-button.h"
struct _SnApplet struct _SnApplet
{ {
PanelApplet parent; PanelApplet parent;
SnHost *host;
GSList *items;
GtkWidget *box;
}; };
G_DEFINE_TYPE (SnApplet, sn_applet, PANEL_TYPE_APPLET) 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 static gboolean
sn_applet_fill (SnApplet *applet) sn_applet_fill (SnApplet *applet)
{ {
GtkWidget *label; applet->host = sn_host_new (SN_HOST_FLAGS_NONE);
label = gtk_label_new ("Status Notifier Host");
gtk_container_add (GTK_CONTAINER (applet), label); g_signal_connect (applet->host, "item-added",
gtk_widget_show (label); 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)); gtk_widget_show (GTK_WIDGET (applet));
...@@ -54,20 +159,62 @@ sn_applet_factory (PanelApplet *applet, ...@@ -54,20 +159,62 @@ sn_applet_factory (PanelApplet *applet,
return FALSE; return FALSE;
} }
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 static void
sn_applet_class_init (SnAppletClass *applet_class) 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 static void
sn_applet_init (SnApplet *applet) sn_applet_init (SnApplet *applet)
{ {
PanelApplet *panel_applet; PanelApplet *panel_applet;
GtkOrientation orientation;
panel_applet = PANEL_APPLET (applet); 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_set_flags (panel_applet, PANEL_APPLET_HAS_HANDLE |
PANEL_APPLET_EXPAND_MINOR); 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, PANEL_APPLET_IN_PROCESS_FACTORY ("SnAppletFactory", SN_TYPE_APPLET,
......
/*
* 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;
}
/*
* 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
...@@ -22,3 +22,7 @@ gp-arrow-button:hover { ...@@ -22,3 +22,7 @@ gp-arrow-button:hover {
gp-arrow-button:active { gp-arrow-button:active {
background-image: linear-gradient(to bottom, #232727, #2d3232); background-image: linear-gradient(to bottom, #232727, #2d3232);
} }
sn-button {
padding: 4px;
}
...@@ -22,3 +22,7 @@ gp-arrow-button:hover { ...@@ -22,3 +22,7 @@ gp-arrow-button:hover {
gp-arrow-button:active { gp-arrow-button:active {
background-image: linear-gradient(to bottom, #c8c8c5, #dcdcda); background-image: linear-gradient(to bottom, #c8c8c5, #dcdcda);
} }
sn-button {
padding: 4px;
}
...@@ -22,3 +22,7 @@ gp-arrow-button:hover { ...@@ -22,3 +22,7 @@ gp-arrow-button:hover {
gp-arrow-button:active {