/* * Copyright (C) 2002 Red Hat, Inc. * Copyright (C) 2003-2006 Vincent Untz * Copyright (C) 2007 Christian Persch * Copyright (C) 2017 Colomban Wendling * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. */ /* Well, actuall'y is the Tray itself, the container for the items. But * NaTray is already taken for the XEMBED part, so for now it's called NaGrid, * don't make a big deal out of it. */ #include #include #include "na-grid.h" #include "system-tray/na-tray.h" #include "status-notifier/sn-host-v0.h" #define MIN_ICON_SIZE_DEFAULT 24 typedef struct { GtkOrientation orientation; gint index; NaGrid *grid; } SortData; struct _NaGrid { GtkGrid parent; gint icon_padding; gint icon_size; gint min_icon_size; gint cols; gint rows; gint length; GSList *hosts; GSList *items; }; enum { PROP_0, PROP_ICON_PADDING, PROP_ICON_SIZE }; G_DEFINE_TYPE (NaGrid, na_grid, GTK_TYPE_GRID) static gint compare_items (gconstpointer a, gconstpointer b) { NaItem *item1; NaItem *item2; NaItemCategory c1; NaItemCategory c2; const gchar *id1; const gchar *id2; item1 = (NaItem *) a; item2 = (NaItem *) b; c1 = na_item_get_category (item1); c2 = na_item_get_category (item2); if (c1 < c2) return -1; else if (c1 > c2) return 1; id1 = na_item_get_id (item1); id2 = na_item_get_id (item2); return g_strcmp0 (id1, id2); } static void sort_items (GtkWidget *item, SortData *data) { gint col, row, left_attach, top_attach; /* row / col number depends on whether we are horizontal or vertical */ if (data->orientation == GTK_ORIENTATION_HORIZONTAL) { col = data->index / data->grid->rows; row = data->index % data->grid->rows; } else { row = data->index / data->grid->cols; col = data->index % data->grid->cols; } /* only update item position if it has changed from current */ gtk_container_child_get (GTK_CONTAINER (data->grid), item, "left-attach", &left_attach, "top-attach", &top_attach, NULL); if (left_attach != col || top_attach != row) { gtk_container_child_set (GTK_CONTAINER (data->grid), item, "left-attach", col, "top-attach", row, NULL); } /* increment to index of next item */ data->index++; } static void refresh_grid (NaGrid *self) { GtkOrientation orientation; GtkAllocation allocation; gint rows, cols, length; orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); length = g_slist_length (self->items); if (orientation == GTK_ORIENTATION_HORIZONTAL) { gtk_grid_set_row_homogeneous (GTK_GRID (self), TRUE); gtk_grid_set_column_homogeneous (GTK_GRID (self), FALSE); rows = MAX (1, allocation.height / self->min_icon_size); cols = MAX (1, length / rows); if (length % rows) cols++; } else { gtk_grid_set_row_homogeneous (GTK_GRID (self), FALSE); gtk_grid_set_column_homogeneous (GTK_GRID (self), TRUE); cols = MAX (1, allocation.width / self->min_icon_size); rows = MAX (1, length / cols); if (length % cols) rows++; } if (self->cols != cols || self->rows != rows || self->length != length) { self->cols = cols; self->rows = rows; self->length = length; SortData data; data.orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); data.index = 0; data.grid = self; g_slist_foreach (self->items, (GFunc) sort_items, &data); } } void na_grid_set_min_icon_size (NaGrid *grid, gint min_icon_size) { g_return_if_fail (NA_IS_GRID (grid)); grid->min_icon_size = min_icon_size; refresh_grid (grid); } static void item_added_cb (NaHost *host, NaItem *item, NaGrid *self) { g_return_if_fail (NA_IS_HOST (host)); g_return_if_fail (NA_IS_ITEM (item)); g_return_if_fail (NA_IS_GRID (self)); g_object_bind_property (self, "orientation", item, "orientation", G_BINDING_SYNC_CREATE); self->items = g_slist_prepend (self->items, item); gtk_widget_set_hexpand (GTK_WIDGET (item), TRUE); gtk_widget_set_vexpand (GTK_WIDGET (item), TRUE); gtk_grid_attach (GTK_GRID (self), GTK_WIDGET (item), self->cols - 1, self->rows - 1, 1, 1); self->items = g_slist_sort (self->items, compare_items); refresh_grid (self); } static void item_removed_cb (NaHost *host, NaItem *item, NaGrid *self) { g_return_if_fail (NA_IS_HOST (host)); g_return_if_fail (NA_IS_ITEM (item)); g_return_if_fail (NA_IS_GRID (self)); gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (item)); self->items = g_slist_remove (self->items, item); refresh_grid (self); } static void na_grid_init (NaGrid *self) { self->icon_padding = 0; self->icon_size = 0; self->min_icon_size = MIN_ICON_SIZE_DEFAULT; self->cols = 1; self->rows = 1; self->length = 0; self->hosts = NULL; self->items = NULL; gtk_grid_set_row_homogeneous (GTK_GRID (self), TRUE); gtk_grid_set_column_homogeneous (GTK_GRID (self), TRUE); } static void add_host (NaGrid *self, NaHost *host) { self->hosts = g_slist_prepend (self->hosts, host); g_object_bind_property (self, "icon-padding", host, "icon-padding", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_object_bind_property (self, "icon-size", host, "icon-size", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_signal_connect_object (host, "item-added", G_CALLBACK (item_added_cb), self, 0); g_signal_connect_object (host, "item-removed", G_CALLBACK (item_removed_cb), self, 0); } static void na_grid_style_updated (GtkWidget *widget) { NaGrid *self = NA_GRID (widget); GtkStyleContext *context; GSList *node; if (GTK_WIDGET_CLASS (na_grid_parent_class)->style_updated) GTK_WIDGET_CLASS (na_grid_parent_class)->style_updated (widget); context = gtk_widget_get_style_context (widget); for (node = self->hosts; node; node = node->next) { gtk_style_context_save (context); na_host_style_updated (node->data, context); gtk_style_context_restore (context); } } /* Custom drawing because system-tray items need weird stuff. */ static gboolean na_grid_draw (GtkWidget *grid, cairo_t *cr) { GList *child; GList *children = gtk_container_get_children (GTK_CONTAINER (grid)); for (child = children; child; child = child->next) { if (! NA_IS_ITEM (child->data) || ! na_item_draw_on_parent (child->data, grid, cr)) { if (gtk_widget_is_drawable (child->data) && gtk_cairo_should_draw_window (cr, gtk_widget_get_window (child->data))) gtk_container_propagate_draw (GTK_CONTAINER (grid), child->data, cr); } } g_list_free (children); return TRUE; } static void na_grid_realize (GtkWidget *widget) { NaGrid *self = NA_GRID (widget); GdkScreen *screen; GtkOrientation orientation; NaHost *tray_host; GTK_WIDGET_CLASS (na_grid_parent_class)->realize (widget); /* Instantiate the hosts now we have a screen */ screen = gtk_widget_get_screen (GTK_WIDGET (self)); orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); tray_host = na_tray_new_for_screen (screen, orientation); g_object_bind_property (self, "orientation", tray_host, "orientation", G_BINDING_DEFAULT); add_host (self, tray_host); add_host (self, sn_host_v0_new ()); } static void na_grid_unrealize (GtkWidget *widget) { NaGrid *self = NA_GRID (widget); if (self->hosts != NULL) { g_slist_free_full (self->hosts, g_object_unref); self->hosts = NULL; } g_clear_pointer (&self->items, g_slist_free); GTK_WIDGET_CLASS (na_grid_parent_class)->unrealize (widget); } static void na_grid_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GTK_WIDGET_CLASS (na_grid_parent_class)->size_allocate (widget, allocation); refresh_grid (NA_GRID (widget)); } static void na_grid_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { NaGrid *self = NA_GRID (object); switch (property_id) { case PROP_ICON_PADDING: g_value_set_int (value, self->icon_padding); break; case PROP_ICON_SIZE: g_value_set_int (value, self->icon_size); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void na_grid_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { NaGrid *self = NA_GRID (object); switch (property_id) { case PROP_ICON_PADDING: self->icon_padding = g_value_get_int (value); break; case PROP_ICON_SIZE: self->icon_size = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void na_grid_class_init (NaGridClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); gobject_class->get_property = na_grid_get_property; gobject_class->set_property = na_grid_set_property; widget_class->draw = na_grid_draw; widget_class->realize = na_grid_realize; widget_class->unrealize = na_grid_unrealize; widget_class->style_updated = na_grid_style_updated; widget_class->size_allocate = na_grid_size_allocate; g_object_class_install_property (gobject_class, PROP_ICON_PADDING, g_param_spec_int ("icon-padding", "Padding around icons", "Padding that should be put around icons, in pixels", 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ICON_SIZE, g_param_spec_int ("icon-size", "Icon size", "If non-zero, hardcodes the size of the icons in pixels", 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } GtkWidget * na_grid_new (GtkOrientation orientation) { return g_object_new (NA_TYPE_GRID, "orientation", orientation, NULL); } void na_grid_force_redraw (NaGrid *grid) { GSList *node; for (node = grid->hosts; node; node = node->next) na_host_force_redraw (node->data); }