diff options
author | Lars Uebernickel <lars.uebernickel@canonical.com> | 2013-08-13 21:28:03 +0000 |
---|---|---|
committer | Tarmac <Unknown> | 2013-08-13 21:28:03 +0000 |
commit | 05443b6bfbb93102474afa56082e3b79306fb0fe (patch) | |
tree | fd29a66a62d3656dd90c83727f87782e4ffcb104 | |
parent | 242477ce5bab4fe8634191a7d9163920eb8f4156 (diff) | |
parent | bb86413dd05a38b4879a109fbb52563a56e6ae5f (diff) | |
download | ayatana-ido-05443b6bfbb93102474afa56082e3b79306fb0fe.tar.gz ayatana-ido-05443b6bfbb93102474afa56082e3b79306fb0fe.tar.bz2 ayatana-ido-05443b6bfbb93102474afa56082e3b79306fb0fe.zip |
Add widgets for messaging menu.
Approved by PS Jenkins bot, Ted Gould.
-rw-r--r-- | debian/libido3-0.1-0.symbols | 10 | ||||
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/idoactionhelper.c | 24 | ||||
-rw-r--r-- | src/idoactionhelper.h | 3 | ||||
-rw-r--r-- | src/idoapplicationmenuitem.c | 195 | ||||
-rw-r--r-- | src/idoapplicationmenuitem.h | 36 | ||||
-rw-r--r-- | src/idodetaillabel.c | 401 | ||||
-rw-r--r-- | src/idodetaillabel.h | 59 | ||||
-rw-r--r-- | src/idomenuitemfactory.c | 8 | ||||
-rw-r--r-- | src/idosourcemenuitem.c | 264 | ||||
-rw-r--r-- | src/idosourcemenuitem.h | 36 |
11 files changed, 1044 insertions, 2 deletions
diff --git a/debian/libido3-0.1-0.symbols b/debian/libido3-0.1-0.symbols index e107a8e..8c4af65 100644 --- a/debian/libido3-0.1-0.symbols +++ b/debian/libido3-0.1-0.symbols @@ -1,11 +1,14 @@ libido3-0.1.so.0 libido3-0.1-0 #MINVER# ido_action_helper_activate@Base 13.10.0daily13.06.19 + ido_action_helper_activate_with_parameter@Base 0replaceme ido_action_helper_change_action_state@Base 13.10.0daily13.06.19 ido_action_helper_get_action_target@Base 13.10.0daily13.06.19 ido_action_helper_get_type@Base 13.10.0daily13.06.19 ido_action_helper_get_widget@Base 13.10.0daily13.06.19 ido_action_helper_new@Base 13.10.0daily13.06.19 ido_alarm_menu_item_new_from_model@Base 13.10.0+13.10.20130731 + ido_application_menu_item_get_type@Base 0replaceme + ido_application_menu_item_new_from_model@Base 0replaceme ido_appointment_menu_item_new_from_model@Base 13.10.0daily13.06.19 ido_basic_menu_item_get_type@Base 13.10.0+13.10.20130731 ido_basic_menu_item_new@Base 13.10.0+13.10.20130731 @@ -24,6 +27,11 @@ libido3-0.1.so.0 libido3-0.1-0 #MINVER# ido_calendar_menu_item_set_date@Base 0.2.2 ido_calendar_menu_item_set_display_options@Base 0.2.1 ido_calendar_menu_item_unmark_day@Base 0.2.1 + ido_detail_label_get_text@Base 0replaceme + ido_detail_label_get_type@Base 0replaceme + ido_detail_label_new@Base 0replaceme + ido_detail_label_set_count@Base 0replaceme + ido_detail_label_set_text@Base 0replaceme ido_entry_menu_item_get_entry@Base 0.1.0 ido_entry_menu_item_get_type@Base 0.1.0 ido_entry_menu_item_new@Base 0.1.0 @@ -57,6 +65,8 @@ libido3-0.1.so.0 libido3-0.1-0 #MINVER# ido_scale_menu_item_set_secondary_label@Base 0.1.9 ido_scale_menu_item_set_style@Base 0.1.9 ido_scale_menu_item_style_get_type@Base 0.1.9 + ido_source_menu_item_get_type@Base 0replaceme + ido_source_menu_item_new_from_menu_model@Base 0replaceme ido_switch_menu_item_get_content_area@Base 12.10.0 ido_switch_menu_item_get_type@Base 12.10.0 ido_switch_menu_item_new@Base 12.10.0 diff --git a/src/Makefile.am b/src/Makefile.am index 725652f..a545094 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,10 @@ sources_h = \ libido.h \ idoactionhelper.h \ idomediaplayermenuitem.h \ - idoplaybackmenuitem.h + idoplaybackmenuitem.h \ + idoapplicationmenuitem.h \ + idodetaillabel.h \ + idosourcemenuitem.h EXTRA_DIST = \ ido.list \ @@ -86,7 +89,10 @@ libido_0_1_la_SOURCES = \ idoappointmentmenuitem.c \ idobasicmenuitem.c \ idotimestampmenuitem.c \ - idolocationmenuitem.c + idolocationmenuitem.c \ + idoapplicationmenuitem.c \ + idodetaillabel.c \ + idosourcemenuitem.c libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES) diff --git a/src/idoactionhelper.c b/src/idoactionhelper.c index f0e300b..a4eb51d 100644 --- a/src/idoactionhelper.c +++ b/src/idoactionhelper.c @@ -403,6 +403,30 @@ ido_action_helper_activate (IdoActionHelper *helper) } /** + * ido_action_helper_activate_with_parameter: + * @helper: an #IdoActionHelper + * @parameter: a #GVariant containing the parameter + * + * Activates the action that is associated with this helper passing + * @parameter instead the "target" associated with the menu item this + * helper is bound to. + */ +void +ido_action_helper_activate_with_parameter (IdoActionHelper *helper, + GVariant *parameter) +{ + g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); + g_return_if_fail (parameter != NULL); + + g_variant_ref_sink (parameter); + + if (helper->actions && helper->action_name) + g_action_group_activate_action (helper->actions, helper->action_name, parameter); + + g_variant_unref (parameter); +} + +/** * ido_action_helper_change_action_state: * @helper: an #IdoActionHelper * @state: the proposed new state of the action diff --git a/src/idoactionhelper.h b/src/idoactionhelper.h index 27dafb7..81977fb 100644 --- a/src/idoactionhelper.h +++ b/src/idoactionhelper.h @@ -41,6 +41,9 @@ GVariant * ido_action_helper_get_action_target (IdoActionHelper *helper void ido_action_helper_activate (IdoActionHelper *helper); +void ido_action_helper_activate_with_parameter (IdoActionHelper *helper, + GVariant *parameter); + void ido_action_helper_change_action_state (IdoActionHelper *helper, GVariant *state); diff --git a/src/idoapplicationmenuitem.c b/src/idoapplicationmenuitem.c new file mode 100644 index 0000000..c5910cf --- /dev/null +++ b/src/idoapplicationmenuitem.c @@ -0,0 +1,195 @@ +/* + * Copyright 2013 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 "idoapplicationmenuitem.h" +#include "idoactionhelper.h" + +typedef GtkMenuItemClass IdoApplicationMenuItemClass; + +struct _IdoApplicationMenuItem +{ + GtkMenuItem parent; + + gboolean is_running; + + GtkWidget *icon; + GtkWidget *label; +}; + +G_DEFINE_TYPE (IdoApplicationMenuItem, ido_application_menu_item, GTK_TYPE_MENU_ITEM); + +static void +ido_application_menu_item_constructed (GObject *object) +{ + IdoApplicationMenuItem *item = IDO_APPLICATION_MENU_ITEM (object); + GtkWidget *grid; + + item->icon = g_object_ref (gtk_image_new ()); + gtk_widget_set_margin_right (item->icon, 6); + + item->label = g_object_ref (gtk_label_new ("")); + + grid = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (grid), item->icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), item->label, 1, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (object), grid); + gtk_widget_show_all (grid); + + G_OBJECT_CLASS (ido_application_menu_item_parent_class)->constructed (object); +} + +static void +ido_application_menu_item_dispose (GObject *object) +{ + IdoApplicationMenuItem *self = IDO_APPLICATION_MENU_ITEM (object); + + g_clear_object (&self->icon); + g_clear_object (&self->label); + + G_OBJECT_CLASS (ido_application_menu_item_parent_class)->dispose (object); +} + +static gboolean +ido_application_menu_item_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoApplicationMenuItem *item = IDO_APPLICATION_MENU_ITEM (widget); + + GTK_WIDGET_CLASS (ido_application_menu_item_parent_class)->draw (widget, cr); + + if (item->is_running) + { + const int arrow_width = 5; + const double half_arrow_height = 4.5; + GtkAllocation alloc; + GdkRGBA color; + double center; + + gtk_widget_get_allocation (widget, &alloc); + + 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); + + center = alloc.height / 2 + 0.5; + + cairo_move_to (cr, 0, center - half_arrow_height); + cairo_line_to (cr, 0, center + half_arrow_height); + cairo_line_to (cr, arrow_width, center); + cairo_close_path (cr); + + cairo_fill (cr); + } + + return FALSE; +} + +void +ido_application_menu_item_class_init (IdoApplicationMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = ido_application_menu_item_constructed; + object_class->dispose = ido_application_menu_item_dispose; + + widget_class->draw = ido_application_menu_item_draw; +} + +static void +ido_application_menu_item_init (IdoApplicationMenuItem *self) +{ +} + +static void +ido_application_menu_item_set_label (IdoApplicationMenuItem *item, + const gchar *label) +{ + gtk_label_set_label (GTK_LABEL (item->label), label); +} + +static void +ido_application_menu_item_set_icon (IdoApplicationMenuItem *item, + GIcon *icon) +{ + gtk_image_set_from_gicon (GTK_IMAGE (item->icon), icon, GTK_ICON_SIZE_MENU); +} + +static void +ido_application_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + IdoApplicationMenuItem *item = user_data; + + item->is_running = g_variant_get_boolean (state); + gtk_widget_queue_draw (GTK_WIDGET (item)); +} + +GtkMenuItem * +ido_application_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkMenuItem *item; + gchar *label; + GVariant *serialized_icon; + gchar *action; + + item = g_object_new (IDO_TYPE_APPLICATION_MENU_ITEM, NULL); + + if (g_menu_item_get_attribute (menuitem, "label", "s", &label)) + { + ido_application_menu_item_set_label (IDO_APPLICATION_MENU_ITEM (item), label); + g_free (label); + } + + serialized_icon = g_menu_item_get_attribute_value (menuitem, "icon", NULL); + if (serialized_icon) + { + GIcon *icon; + + icon = g_icon_deserialize (serialized_icon); + if (icon) + { + ido_application_menu_item_set_icon (IDO_APPLICATION_MENU_ITEM (item), icon); + g_object_unref (icon); + } + + g_variant_unref (serialized_icon); + } + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + + helper = ido_action_helper_new (GTK_WIDGET (item), actions, action, NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (ido_application_menu_item_state_changed), item); + g_signal_connect_object (item, "activate", + G_CALLBACK (ido_action_helper_activate), helper, + G_CONNECT_SWAPPED); + g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper); + + g_free (action); + } + + return item; +} diff --git a/src/idoapplicationmenuitem.h b/src/idoapplicationmenuitem.h new file mode 100644 index 0000000..ebd5075 --- /dev/null +++ b/src/idoapplicationmenuitem.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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_APPLICATION_MENU_ITEM_H__ +#define __IDO_APPLICATION_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_APPLICATION_MENU_ITEM (ido_application_menu_item_get_type ()) +#define IDO_APPLICATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_APPLICATION_MENU_ITEM, IdoApplicationMenuItem)) +#define IS_IDO_APPLICATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_APPLICATION_MENU_ITEM)) + +typedef struct _IdoApplicationMenuItem IdoApplicationMenuItem; + +GType ido_application_menu_item_get_type (void); + +GtkMenuItem * ido_application_menu_item_new_from_model (GMenuItem *item, + GActionGroup *actions); + +#endif diff --git a/src/idodetaillabel.c b/src/idodetaillabel.c new file mode 100644 index 0000000..b36b222 --- /dev/null +++ b/src/idodetaillabel.c @@ -0,0 +1,401 @@ +/* + * 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 "idodetaillabel.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) +{ + PangoFontDescription *font; + PangoFontMetrics *metrics; + + gtk_style_context_get (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + "font", &font, NULL); + + metrics = pango_context_get_metrics (context, + font, + pango_context_get_language (context)); + + pango_font_description_free (font); + return metrics; +} + +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 == (gunichar) -1) + 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/idodetaillabel.h b/src/idodetaillabel.h new file mode 100644 index 0000000..1995fee --- /dev/null +++ b/src/idodetaillabel.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/idomenuitemfactory.c b/src/idomenuitemfactory.c index 0b38941..376134e 100644 --- a/src/idomenuitemfactory.c +++ b/src/idomenuitemfactory.c @@ -29,6 +29,8 @@ #include "idousermenuitem.h" #include "idomediaplayermenuitem.h" #include "idoplaybackmenuitem.h" +#include "idoapplicationmenuitem.h" +#include "idosourcemenuitem.h" #define IDO_TYPE_MENU_ITEM_FACTORY (ido_menu_item_factory_get_type ()) #define IDO_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MENU_ITEM_FACTORY, IdoMenuItemFactory)) @@ -83,6 +85,12 @@ ido_menu_item_factory_create_menu_item (UbuntuMenuItemFactory *factory, else if (g_str_equal (type, "com.canonical.unity.playback-item")) item = ido_playback_menu_item_new_from_model (menuitem, actions); + else if (g_str_equal (type, "com.canonical.application")) + item = ido_application_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.indicator.messages.source")) + item = ido_source_menu_item_new_from_menu_model (menuitem, actions); + return item; } diff --git a/src/idosourcemenuitem.c b/src/idosourcemenuitem.c new file mode 100644 index 0000000..6bc2ffa --- /dev/null +++ b/src/idosourcemenuitem.c @@ -0,0 +1,264 @@ +/* + * Copyright 2013 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 "idosourcemenuitem.h" + +#include <libintl.h> +#include "idodetaillabel.h" +#include "idoactionhelper.h" + +typedef GtkMenuItemClass IdoSourceMenuItemClass; + +struct _IdoSourceMenuItem +{ + GtkMenuItem parent; + + GtkWidget *icon; + GtkWidget *label; + GtkWidget *detail; + + gint64 time; + guint timer_id; +}; + +G_DEFINE_TYPE (IdoSourceMenuItem, ido_source_menu_item, GTK_TYPE_MENU_ITEM); + +static void +ido_source_menu_item_constructed (GObject *object) +{ + IdoSourceMenuItem *item = IDO_SOURCE_MENU_ITEM (object); + GtkWidget *grid; + gint icon_width; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL); + + item->icon = g_object_ref (gtk_image_new ()); + gtk_widget_set_margin_left (item->icon, icon_width); + gtk_widget_set_margin_right (item->icon, 6); + + item->label = g_object_ref (gtk_label_new ("")); + gtk_label_set_max_width_chars (GTK_LABEL (item->label), 40); + gtk_label_set_ellipsize (GTK_LABEL (item->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (item->label), 0.0, 0.5); + + item->detail = g_object_ref (ido_detail_label_new ("")); + gtk_widget_set_halign (item->detail, GTK_ALIGN_END); + gtk_widget_set_hexpand (item->detail, TRUE); + gtk_style_context_add_class (gtk_widget_get_style_context (item->detail), "accelerator"); + + grid = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (grid), item->icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), item->label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), item->detail, 2, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (object), grid); + gtk_widget_show_all (grid); + + G_OBJECT_CLASS (ido_source_menu_item_parent_class)->constructed (object); +} + +static gchar * +ido_source_menu_item_time_span_string (gint64 timestamp) +{ + gchar *str; + gint64 span; + gint hours; + gint minutes; + + span = MAX (g_get_real_time () - timestamp, 0) / G_USEC_PER_SEC; + hours = span / 3600; + minutes = (span / 60) % 60; + + if (hours == 0) + { + /* TRANSLATORS: number of minutes that have passed */ + str = g_strdup_printf (ngettext ("%d min", "%d min", minutes), minutes); + } + else + { + /* TRANSLATORS: number of hours that have passed */ + str = g_strdup_printf (ngettext ("%d h", "%d h", hours), hours); + } + + return str; +} + +static void +ido_source_menu_item_set_detail_time (IdoSourceMenuItem *self, + gint64 time) +{ + gchar *str; + + self->time = time; + + str = ido_source_menu_item_time_span_string (self->time); + ido_detail_label_set_text (IDO_DETAIL_LABEL (self->detail), str); + + g_free (str); +} + +static gboolean +ido_source_menu_item_update_time (gpointer data) +{ + IdoSourceMenuItem *self = data; + + ido_source_menu_item_set_detail_time (self, self->time); + + return TRUE; +} + +static void +ido_source_menu_item_dispose (GObject *object) +{ + IdoSourceMenuItem *self = IDO_SOURCE_MENU_ITEM (object); + + if (self->timer_id != 0) + { + g_source_remove (self->timer_id); + self->timer_id = 0; + } + + g_clear_object (&self->icon); + g_clear_object (&self->label); + g_clear_object (&self->detail); + + G_OBJECT_CLASS (ido_source_menu_item_parent_class)->dispose (object); +} + +static void +ido_source_menu_item_class_init (IdoSourceMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = ido_source_menu_item_constructed; + object_class->dispose = ido_source_menu_item_dispose; +} + +static void +ido_source_menu_item_init (IdoSourceMenuItem *self) +{ +} + +static void +ido_source_menu_item_set_label (IdoSourceMenuItem *item, + const gchar *label) +{ + gtk_label_set_label (GTK_LABEL (item->label), label ? label : ""); +} + +static void +ido_source_menu_item_set_icon (IdoSourceMenuItem *item, + GIcon *icon) +{ + if (icon) + gtk_image_set_from_gicon (GTK_IMAGE (item->icon), icon, GTK_ICON_SIZE_MENU); + else + gtk_image_clear (GTK_IMAGE (item->icon)); +} + + +static void +ido_source_menu_item_activate (GtkMenuItem *item, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + + /* The parameter signifies whether this source was activated (TRUE) or + * dismissed (FALSE). Since there's no UI to dismiss a gtkmenuitem, + * this always passes TRUE. */ + ido_action_helper_activate_with_parameter (helper, g_variant_new_boolean (TRUE)); +} + +static void +ido_source_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + IdoSourceMenuItem *item = user_data; + guint32 count; + gint64 time; + const gchar *str; + + if (item->timer_id != 0) + { + g_source_remove (item->timer_id); + item->timer_id = 0; + } + + g_return_val_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")), FALSE); + + g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); + + if (count != 0) + ido_detail_label_set_count (IDO_DETAIL_LABEL (item->detail), count); + else if (time != 0) + { + ido_source_menu_item_set_detail_time (item, time); + item->timer_id = g_timeout_add_seconds (59, ido_source_menu_item_update_time, item); + } + else if (str != NULL && *str) + ido_detail_label_set_text (IDO_DETAIL_LABEL (item->detail), str); +} + +GtkMenuItem * +ido_source_menu_item_new_from_menu_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkMenuItem *item; + GVariant *serialized_icon; + GIcon *icon = NULL; + gchar *label; + gchar *action = NULL; + + item = g_object_new (IDO_TYPE_SOURCE_MENU_ITEM, NULL); + + if (g_menu_item_get_attribute (menuitem, "label", "s", &label)) + { + ido_source_menu_item_set_label (IDO_SOURCE_MENU_ITEM (item), label); + g_free (label); + } + + serialized_icon = g_menu_item_get_attribute_value (menuitem, "icon", NULL); + if (serialized_icon) + { + icon = g_icon_deserialize (serialized_icon); + g_variant_unref (serialized_icon); + } + ido_source_menu_item_set_icon (IDO_SOURCE_MENU_ITEM (item), icon); + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + + helper = ido_action_helper_new (GTK_WIDGET (item), actions, action, NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (ido_source_menu_item_state_changed), item); + g_signal_connect_object (item, "activate", + G_CALLBACK (ido_source_menu_item_activate), helper, + 0); + g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper); + + g_free (action); + } + + if (icon) + g_object_unref (icon); + + return item; +} diff --git a/src/idosourcemenuitem.h b/src/idosourcemenuitem.h new file mode 100644 index 0000000..0a12572 --- /dev/null +++ b/src/idosourcemenuitem.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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_SOURCE_MENU_ITEM_H__ +#define __IDO_SOURCE_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_SOURCE_MENU_ITEM (ido_source_menu_item_get_type ()) +#define IDO_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_SOURCE_MENU_ITEM, IdoSourceMenuItem)) +#define IDO_IS_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_SOURCE_MENU_ITEM)) + +typedef struct _IdoSourceMenuItem IdoSourceMenuItem; + +GType ido_source_menu_item_get_type (void); + +GtkMenuItem * ido_source_menu_item_new_from_menu_model (GMenuItem *menuitem, + GActionGroup *actions); + +#endif |