diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/ido-detail-label.c | 396 | ||||
-rw-r--r-- | src/ido-detail-label.h | 59 | ||||
-rw-r--r-- | src/im-source-menu-item.c | 83 |
4 files changed, 481 insertions, 59 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 4d86730..1df80e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,8 @@ libmessaging_la_SOURCES = \ im-app-menu-item.h \ im-source-menu-item.c \ im-source-menu-item.h \ + ido-detail-label.c \ + ido-detail-label.h \ indicator-messages-service.c \ indicator-messages-service.h dbus-data.h diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c new file mode 100644 index 0000000..780a2dd --- /dev/null +++ b/src/ido-detail-label.c @@ -0,0 +1,396 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "ido-detail-label.h" + +#include <math.h> + +G_DEFINE_TYPE (IdoDetailLabel, ido_detail_label, GTK_TYPE_WIDGET) + +struct _IdoDetailLabelPrivate +{ + gchar *text; + PangoLayout *layout; + gboolean draw_lozenge; +}; + +enum +{ + PROP_0, + PROP_TEXT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +static void +ido_detail_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + g_value_set_string (value, self->priv->text); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ido_detail_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + ido_detail_label_set_text (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + + +static void +ido_detail_label_finalize (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->finalize (object); +} + +static void +ido_detail_label_dispose (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_clear_object (&priv->layout); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->dispose (object); +} + +static void +ido_detail_label_ensure_layout (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + + if (priv->layout == NULL) + { + priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), priv->text); + pango_layout_set_alignment (priv->layout, PANGO_ALIGN_CENTER); + pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END); + pango_layout_set_height (priv->layout, -1); + + // TODO update layout on "style-updated" and "direction-changed" + } +} + +static void +cairo_lozenge (cairo_t *cr, + double x, + double y, + double w, + double h, + double radius) +{ + double x1 = x + w - radius; + double x2 = x + radius; + double y1 = y + radius; + double y2 = y + h - radius; + + cairo_move_to (cr, x + radius, y); + cairo_arc (cr, x1, y1, radius, G_PI * 1.5, G_PI * 2); + cairo_arc (cr, x1, y2, radius, 0, G_PI * 0.5); + cairo_arc (cr, x2, y2, radius, G_PI * 0.5, G_PI); + cairo_arc (cr, x2, y1, radius, G_PI, G_PI * 1.5); +} + +static PangoFontMetrics * +gtk_widget_get_font_metrics (GtkWidget *widget, + PangoContext *context) +{ + const PangoFontDescription *font; + + font = gtk_style_context_get_font (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget)); + + return pango_context_get_metrics (context, + font, + pango_context_get_language (context)); +} + +static gint +ido_detail_label_get_minimum_text_width (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + PangoContext *context; + PangoFontMetrics *metrics; + gint char_width; + gint w; + + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (GTK_WIDGET (label), context); + char_width = pango_font_metrics_get_approximate_digit_width (metrics); + + w = 2 * char_width / PANGO_SCALE; + pango_font_metrics_unref (metrics); + return w; +} + +static gboolean +ido_detail_label_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoDetailLabel *label = IDO_DETAIL_LABEL (widget); + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + GtkAllocation allocation; + double x, w, h, radius; + GdkRGBA color; + + if (!priv->text || !*priv->text) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + h = MIN (allocation.height, extents.height); + radius = floor (h / 2.0); + w = MAX (ido_detail_label_get_minimum_text_width (label), extents.width) + 2.0 * radius; + x = allocation.width - w; + + pango_layout_set_width (priv->layout, (allocation.width - 2 * radius) * PANGO_SCALE); + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + &color); + gdk_cairo_set_source_rgba (cr, &color); + + cairo_set_line_width (cr, 1.0); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + + if (priv->draw_lozenge) + cairo_lozenge (cr, x, 0.0, w, h, radius); + + cairo_move_to (cr, x + radius, (allocation.height - extents.height) / 2.0); + pango_cairo_layout_path (cr, priv->layout); + cairo_fill (cr); + + return TRUE; +} + +static void +ido_detail_label_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + double radius; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + radius = floor (extents.height / 2.0); + + *minimum = ido_detail_label_get_minimum_text_width (IDO_DETAIL_LABEL (widget)) + 2.0 * radius; + *natural = MAX (*minimum, extents.width + 2.0 * radius); +} + +static void +ido_detail_label_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoContext *context; + PangoFontMetrics *metrics; + PangoRectangle extents; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (widget, context); + + *minimum = *natural = (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)) / PANGO_SCALE; + + pango_font_metrics_unref (metrics); +} + +static void +ido_detail_label_class_init (IdoDetailLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = ido_detail_label_get_property; + object_class->set_property = ido_detail_label_set_property; + object_class->finalize = ido_detail_label_finalize; + object_class->dispose = ido_detail_label_dispose; + + widget_class->draw = ido_detail_label_draw; + widget_class->get_preferred_width = ido_detail_label_get_preferred_width; + widget_class->get_preferred_height = ido_detail_label_get_preferred_height; + + g_type_class_add_private (klass, sizeof (IdoDetailLabelPrivate)); + + properties[PROP_TEXT] = g_param_spec_string ("text", + "Text", + "The text of the label", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +ido_detail_label_init (IdoDetailLabel *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_TYPE_DETAIL_LABEL, + IdoDetailLabelPrivate); + + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); +} + +GtkWidget * +ido_detail_label_new (const gchar *label) +{ + return g_object_new (IDO_TYPE_DETAIL_LABEL, + "text", label, + NULL); +} + +const gchar * +ido_detail_label_get_text (IdoDetailLabel *label) +{ + g_return_val_if_fail (IDO_IS_DETAIL_LABEL (label), NULL); + return label->priv->text; +} + +/* collapse_whitespace: + * @str: the source string + * + * Collapses all occurences of consecutive whitespace charactes in @str + * into a single space. + * + * Returns: (transfer full): a newly-allocated string + */ +static gchar * +collapse_whitespace (const gchar *str) +{ + GString *result; + gboolean in_space = FALSE; + + if (str == NULL) + return NULL; + + result = g_string_new (""); + + while (*str) + { + gunichar c = g_utf8_get_char_validated (str, -1); + + if (c < 0) + break; + + if (!g_unichar_isspace (c)) + { + g_string_append_unichar (result, c); + in_space = FALSE; + } + else if (!in_space) + { + g_string_append_c (result, ' '); + in_space = TRUE; + } + + str = g_utf8_next_char (str); + } + + return g_string_free (result, FALSE); +} + +static void +ido_detail_label_set_text_impl (IdoDetailLabel *label, + const gchar *text, + gboolean draw_lozenge) +{ + IdoDetailLabelPrivate * priv = label->priv; + + g_clear_object (&priv->layout); + g_free (priv->text); + + priv->text = g_strdup (text); + priv->draw_lozenge = draw_lozenge; + + g_object_notify_by_pspec (G_OBJECT (label), properties[PROP_TEXT]); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + +void +ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text) +{ + gchar *str; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + str = collapse_whitespace (text); + ido_detail_label_set_text_impl (label, str, FALSE); + g_free (str); +} + +void +ido_detail_label_set_count (IdoDetailLabel *label, + gint count) +{ + gchar *text; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + text = g_strdup_printf ("%d", count); + ido_detail_label_set_text_impl (label, text, TRUE); + g_free (text); +} diff --git a/src/ido-detail-label.h b/src/ido-detail-label.h new file mode 100644 index 0000000..1995fee --- /dev/null +++ b/src/ido-detail-label.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_DETAIL_LABEL_H__ +#define __IDO_DETAIL_LABEL_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_DETAIL_LABEL (ido_detail_label_get_type()) +#define IDO_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabel)) +#define IDO_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) +#define IDO_IS_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_DETAIL_LABEL)) +#define IDO_IS_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_DETAIL_LABEL)) +#define IDO_DETAIL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) + +typedef struct _IdoDetailLabel IdoDetailLabel; +typedef struct _IdoDetailLabelClass IdoDetailLabelClass; +typedef struct _IdoDetailLabelPrivate IdoDetailLabelPrivate; + +struct _IdoDetailLabel +{ + GtkWidget parent; + IdoDetailLabelPrivate *priv; +}; + +struct _IdoDetailLabelClass +{ + GtkWidgetClass parent_class; +}; + +GType ido_detail_label_get_type (void) G_GNUC_CONST; + +GtkWidget * ido_detail_label_new (const gchar *str); + +const gchar * ido_detail_label_get_text (IdoDetailLabel *label); + +void ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text); + +void ido_detail_label_set_count (IdoDetailLabel *label, + gint count); + +#endif diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c index 5aebb68..775fa91 100644 --- a/src/im-source-menu-item.c +++ b/src/im-source-menu-item.c @@ -20,6 +20,7 @@ #include "im-source-menu-item.h" #include <libintl.h> +#include "ido-detail-label.h" struct _ImSourceMenuItemPrivate { @@ -59,11 +60,13 @@ im_source_menu_item_constructed (GObject *object) gtk_widget_set_margin_left (priv->icon, icon_width + 6); priv->label = g_object_ref (gtk_label_new ("")); + gtk_label_set_max_width_chars (GTK_LABEL (priv->label), 40); + gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); - priv->detail = g_object_ref (gtk_label_new ("")); + priv->detail = g_object_ref (ido_detail_label_new ("")); gtk_widget_set_halign (priv->detail, GTK_ALIGN_END); gtk_widget_set_hexpand (priv->detail, TRUE); - gtk_misc_set_alignment (GTK_MISC (priv->label), 1.0, 0.5); gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator"); grid = gtk_grid_new (); @@ -77,49 +80,6 @@ im_source_menu_item_constructed (GObject *object) G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object); } -/* collapse_whitespace: - * @str: the source string - * - * Collapses all occurences of consecutive whitespace charactes in @str - * into a single space. - * - * Returns: (transfer full): a newly-allocated string - */ -static gchar * -collapse_whitespace (const gchar *str) -{ - GString *result; - gboolean in_space = FALSE; - - if (str == NULL) - return NULL; - - result = g_string_new (""); - - while (*str) - { - gunichar c = g_utf8_get_char_validated (str, -1); - - if (c < 0) - break; - - if (!g_unichar_isspace (c)) - { - g_string_append_unichar (result, c); - in_space = FALSE; - } - else if (!in_space) - { - g_string_append_c (result, ' '); - in_space = TRUE; - } - - str = g_utf8_next_char (str); - } - - return g_string_free (result, FALSE); -} - static gchar * im_source_menu_item_time_span_string (gint64 timestamp) { @@ -146,16 +106,28 @@ im_source_menu_item_time_span_string (gint64 timestamp) return str; } +static void +im_source_menu_item_set_detail_time (ImSourceMenuItem *self, + gint64 time) +{ + ImSourceMenuItemPrivate *priv = self->priv; + gchar *str; + + priv->time = time; + + str = im_source_menu_item_time_span_string (priv->time); + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); + + g_free (str); +} + static gboolean im_source_menu_item_update_time (gpointer data) { ImSourceMenuItem *self = data; - gchar *str; - str = im_source_menu_item_time_span_string (self->priv->time); - gtk_label_set_text (GTK_LABEL (self->priv->detail), str); + im_source_menu_item_set_detail_time (self, self->priv->time); - g_free (str); return TRUE; } @@ -167,7 +139,6 @@ im_source_menu_item_set_state (ImSourceMenuItem *self, guint32 count; gint64 time; const gchar *str; - gchar *detail; if (priv->timer_id != 0) { @@ -180,21 +151,15 @@ im_source_menu_item_set_state (ImSourceMenuItem *self, g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); if (count != 0) - detail = g_strdup_printf ("%d", count); + ido_detail_label_set_count (IDO_DETAIL_LABEL (priv->detail), count); else if (time != 0) { - priv->time = time; - detail = im_source_menu_item_time_span_string (time); + im_source_menu_item_set_detail_time (self, time); priv->timer_id = g_timeout_add_seconds (59, im_source_menu_item_update_time, self); } else if (str != NULL && *str) - detail = collapse_whitespace (str); - else - detail = NULL; - - gtk_label_set_text (GTK_LABEL (priv->detail), detail ? detail : ""); + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); - g_free (detail); return TRUE; } |