diff options
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/idomediaplayermenuitem.c | 326 | ||||
-rw-r--r-- | src/idomediaplayermenuitem.h | 42 | ||||
-rw-r--r-- | src/idomenuitemfactory.c | 8 | ||||
-rw-r--r-- | src/idoplaybackmenuitem.c | 1669 | ||||
-rw-r--r-- | src/idoplaybackmenuitem.h | 37 |
6 files changed, 2089 insertions, 3 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 7b2e51e..2791964 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,7 +22,9 @@ sources_h = \ idolocationmenuitem.h \ idotimeline.h \ libido.h \ - idoactionhelper.h + idoactionhelper.h \ + idomediaplayermenuitem.h \ + idoplaybackmenuitem.h EXTRA_DIST = \ ido.list \ @@ -74,9 +76,11 @@ libido_0_1_la_SOURCES = \ idotimeline.c \ idomenuitemfactory.c \ idoactionhelper.c \ + idousermenuitem.c \ + idomediaplayermenuitem.c \ + idoplaybackmenuitem.c \ idoappointmentmenuitem.c \ - idolocationmenuitem.c \ - idousermenuitem.c + idolocationmenuitem.c libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES) diff --git a/src/idomediaplayermenuitem.c b/src/idomediaplayermenuitem.c new file mode 100644 index 0000000..7eef456 --- /dev/null +++ b/src/idomediaplayermenuitem.c @@ -0,0 +1,326 @@ +/* + * 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: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "config.h" + +#include "idomediaplayermenuitem.h" +#include "idoactionhelper.h" + +typedef GtkMenuItemClass IdoMediaPlayerMenuItemClass; + +struct _IdoMediaPlayerMenuItem +{ + GtkMenuItem parent; + + GtkWidget* player_label; + GtkWidget* player_icon; + GtkWidget* metadata_widget; + GtkWidget* album_art; + GtkWidget* artist_label; + GtkWidget* piece_label; + GtkWidget* container_label; + + gboolean running; +}; + +G_DEFINE_TYPE (IdoMediaPlayerMenuItem, ido_media_player_menu_item, GTK_TYPE_MENU_ITEM); + +static gboolean +ido_media_player_menu_item_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (widget); + + GTK_WIDGET_CLASS (ido_media_player_menu_item_parent_class)->draw (widget, cr); + + /* draw a triangle next to the application name if the app is running */ + if (self->running) + { + const int arrow_width = 5; + const int half_arrow_height = 4; + + GdkRGBA color; + GtkAllocation allocation; + GtkAllocation label_allocation; + int x; + int y; + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state (widget), + &color); + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_allocation (self->player_label, &label_allocation); + x = allocation.x; + y = label_allocation.y - allocation.y + label_allocation.height / 2; + + cairo_move_to (cr, x, y - half_arrow_height); + cairo_line_to (cr, x, y + half_arrow_height); + cairo_line_to (cr, x + arrow_width, y); + cairo_close_path (cr); + + gdk_cairo_set_source_rgba (cr, &color); + cairo_fill (cr); + } + + return FALSE; +} + +static void +ido_media_player_menu_item_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + *minimum = *natural = 200; +} + +static void +ido_media_player_menu_item_class_init (IdoMediaPlayerMenuItemClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->get_preferred_width = ido_media_player_menu_item_get_preferred_width; + widget_class->draw = ido_media_player_menu_item_draw; +} + +static void +ido_media_player_menu_item_init (IdoMediaPlayerMenuItem *self) +{ + GtkWidget *grid; + + self->player_icon = gtk_image_new(); + gtk_widget_set_margin_right (self->player_icon, 6); + gtk_widget_set_halign (self->player_icon, GTK_ALIGN_START); + + self->player_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->player_label, GTK_ALIGN_START); + gtk_widget_set_hexpand (self->player_label, TRUE); + + self->album_art = gtk_image_new(); + gtk_widget_set_margin_right (self->album_art, 8); + + self->artist_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->artist_label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (self->artist_label), PANGO_ELLIPSIZE_MIDDLE); + + self->piece_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->piece_label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (self->piece_label), PANGO_ELLIPSIZE_MIDDLE); + + self->container_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->container_label, GTK_ALIGN_START); + gtk_widget_set_valign (self->container_label, GTK_ALIGN_START); + gtk_widget_set_vexpand (self->container_label, TRUE); + gtk_label_set_ellipsize (GTK_LABEL (self->container_label), PANGO_ELLIPSIZE_MIDDLE); + + self->metadata_widget = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->album_art, 0, 0, 1, 4); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->piece_label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->artist_label, 1, 1, 1, 1); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->container_label, 1, 2, 1, 1); + + grid = gtk_grid_new (); + gtk_grid_set_row_spacing (GTK_GRID (grid), 8); + gtk_grid_attach (GTK_GRID (grid), self->player_icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), self->player_label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), self->metadata_widget, 0, 1, 2, 1); + + gtk_container_add (GTK_CONTAINER (self), grid); + gtk_widget_show_all (grid); + + /* hide metadata by defalut (player is not running) */ + gtk_widget_hide (self->metadata_widget); +} + +static void +ido_media_player_menu_item_set_player_name (IdoMediaPlayerMenuItem *self, + const gchar *name) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + gtk_label_set_label (GTK_LABEL (self->player_label), name); +} + +static void +ido_media_player_menu_item_set_player_icon (IdoMediaPlayerMenuItem *self, + GIcon *icon) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + gtk_image_set_from_gicon (GTK_IMAGE (self->player_icon), icon, GTK_ICON_SIZE_MENU); +} + +static void +ido_media_player_menu_item_set_is_running (IdoMediaPlayerMenuItem *self, + gboolean running) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + if (self->running != running) + { + self->running = running; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +ido_media_player_menu_item_set_album_art (IdoMediaPlayerMenuItem *self, + const gchar *url) +{ + GFile *file; + + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + if (url == NULL) + { + gtk_image_clear (GTK_IMAGE (self->album_art)); + return; + } + + file = g_file_new_for_uri (url); + if (g_file_is_native (file)) + { + gchar *path; + GdkPixbuf *img; + GError *error = NULL; + + path = g_file_get_path (file); + img = gdk_pixbuf_new_from_file_at_size (path, 60, 60, &error); + if (img) + { + gtk_image_set_from_pixbuf (GTK_IMAGE (self->album_art), img); + g_object_unref (img); + } + else + { + g_warning ("unable to load image: %s", error->message); + g_error_free (error); + } + + g_free (path); + } + else + gtk_image_clear (GTK_IMAGE (self->album_art)); + + g_object_unref (file); +} + +static void +ido_media_player_menu_item_set_metadata (IdoMediaPlayerMenuItem *self, + const gchar *title, + const gchar *artist, + const gchar *album, + const gchar *art_url) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + /* hide if there's no metadata */ + if (title == NULL || *title == '\0') + { + gtk_label_set_label (GTK_LABEL (self->piece_label), NULL); + gtk_label_set_label (GTK_LABEL (self->artist_label), NULL); + gtk_label_set_label (GTK_LABEL (self->container_label), NULL); + ido_media_player_menu_item_set_album_art (self, NULL); + gtk_widget_hide (self->metadata_widget); + } + else + { + gtk_label_set_label (GTK_LABEL (self->piece_label), title); + gtk_label_set_label (GTK_LABEL (self->artist_label), artist); + gtk_label_set_label (GTK_LABEL (self->container_label), album); + ido_media_player_menu_item_set_album_art (self, art_url); + gtk_widget_show (self->metadata_widget); + } +} + +static void +ido_media_player_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + IdoMediaPlayerMenuItem *widget; + gboolean running = FALSE; + gchar *title = NULL; + gchar *artist = NULL; + gchar *album = NULL; + gchar *art_url = NULL; + + g_variant_lookup (state, "running", "b", &running); + g_variant_lookup (state, "title", "&s", &title); + g_variant_lookup (state, "artist", "&s", &artist); + g_variant_lookup (state, "album", "&s", &album); + g_variant_lookup (state, "art-url", "&s", &art_url); + + widget = IDO_MEDIA_PLAYER_MENU_ITEM (ido_action_helper_get_widget (helper)); + ido_media_player_menu_item_set_is_running (widget, running); + ido_media_player_menu_item_set_metadata (widget, title, artist, album, art_url); +} + +GtkMenuItem * +ido_media_player_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkMenuItem *widget; + gchar *label; + gchar *action; + GVariant *v; + + widget = g_object_new (IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, NULL); + + if (g_menu_item_get_attribute (menuitem, "label", "s", &label)) + { + ido_media_player_menu_item_set_player_name (IDO_MEDIA_PLAYER_MENU_ITEM (widget), label); + g_free (label); + } + + if ((v = g_menu_item_get_attribute_value (menuitem, "icon", NULL))) + { + GIcon *icon; + + icon = g_icon_deserialize (v); + if (icon) + { + ido_media_player_menu_item_set_player_icon (IDO_MEDIA_PLAYER_MENU_ITEM (widget), icon); + g_object_unref (icon); + } + + g_variant_unref (v); + } + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + + helper = ido_action_helper_new (GTK_WIDGET (widget), actions, action, NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (ido_media_player_menu_item_state_changed), NULL); + + g_signal_connect_object (widget, "activate", + G_CALLBACK (ido_action_helper_activate), + helper, G_CONNECT_SWAPPED); + + g_signal_connect_swapped (widget, "destroy", G_CALLBACK (g_object_unref), helper); + + g_free (action); + } + + return widget; +} diff --git a/src/idomediaplayermenuitem.h b/src/idomediaplayermenuitem.h new file mode 100644 index 0000000..e4a7e8e --- /dev/null +++ b/src/idomediaplayermenuitem.h @@ -0,0 +1,42 @@ +/* + * 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: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_MEDIA_PLAYER_MENU_ITEM_H__ +#define __IDO_MEDIA_PLAYER_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define IDO_TYPE_MEDIA_PLAYER_MENU_ITEM (ido_media_player_menu_item_get_type ()) +#define IDO_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, IdoMediaPlayerMenuItem)) +#define IDO_IS_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM)) + +typedef struct _IdoMediaPlayerMenuItem IdoMediaPlayerMenuItem; + +GType ido_media_player_menu_item_get_type (void); + +GtkMenuItem * ido_media_player_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions); + +G_END_DECLS + +#endif diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c index aef08e6..650c95f 100644 --- a/src/idomenuitemfactory.c +++ b/src/idomenuitemfactory.c @@ -25,6 +25,8 @@ #include "idolocationmenuitem.h" #include "idoscalemenuitem.h" #include "idousermenuitem.h" +#include "idomediaplayermenuitem.h" +#include "idoplaybackmenuitem.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)) @@ -64,6 +66,12 @@ ido_menu_item_factory_create_menu_item (UbuntuMenuItemFactory *factory, else if (g_str_equal (type, "com.canonical.unity.slider")) item = ido_scale_menu_item_new_from_model (menuitem, actions); + else if (g_str_equal (type, "com.canonical.unity.media-player")) + item = ido_media_player_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.unity.playback-item")) + item = ido_playback_menu_item_new_from_model (menuitem, actions); + return item; } diff --git a/src/idoplaybackmenuitem.c b/src/idoplaybackmenuitem.c new file mode 100644 index 0000000..3a9a196 --- /dev/null +++ b/src/idoplaybackmenuitem.c @@ -0,0 +1,1669 @@ +/* + * 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: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Andrea Cimitan <andrea.cimitan@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "config.h" + +#include "idoplaybackmenuitem.h" + +#include <gdk/gdkkeysyms.h> +#include <math.h> + +typedef enum +{ + STATE_PAUSED, + STATE_PLAYING, + STATE_LAUNCHING +} State; + +typedef enum +{ + BUTTON_NONE, + BUTTON_PREVIOUS, + BUTTON_PLAYPAUSE, + BUTTON_NEXT +} Button; + +typedef GtkMenuItemClass IdoPlaybackMenuItemClass; + +struct _IdoPlaybackMenuItem +{ + GtkMenuItem parent; + + State current_state; + Button cur_pushed_button; + Button cur_hover_button; + gboolean has_focus; + gboolean keyboard_activated; /* TRUE if the current button was activated with a key */ + + GActionGroup *action_group; + gchar *play_action; + gchar *next_action; + gchar *prev_action; +}; + +G_DEFINE_TYPE (IdoPlaybackMenuItem, ido_playback_menu_item, GTK_TYPE_MENU_ITEM); + +static gboolean ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr); + +static void +ido_playback_menu_item_dispose (GObject *object) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); + + if (item->action_group) + { + g_signal_handlers_disconnect_by_data (item->action_group, item); + g_clear_object (&item->action_group); + } + + G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->dispose (object); +} + +static void +ido_playback_menu_item_finalize (GObject *object) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); + + g_free (item->play_action); + g_free (item->next_action); + g_free (item->prev_action); + + G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->finalize (object); +} + +static Button +ido_playback_menu_item_get_button_at_pos (gint x, + gint y) +{ + if (x > 57 && x < 102 && y > 12 && y < 40) + return BUTTON_PREVIOUS; + else if (x > 101 && x < 143 && y > 5 && y < 47) + return BUTTON_PLAYPAUSE; + else if (x > 142 && x < 187 && y > 12 && y < 40) + return BUTTON_NEXT; + + return BUTTON_NONE; +} + +static gboolean +ido_playback_menu_item_parent_key_press_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + /* only listen to events when the playback menu item is selected */ + if (!self->has_focus) + return FALSE; + + switch (event->keyval) + { + case GDK_KEY_Left: + self->cur_pushed_button = BUTTON_PREVIOUS; + if (self->action_group && self->prev_action) + g_action_group_activate_action (self->action_group, self->prev_action, NULL); + break; + + case GDK_KEY_Right: + self->cur_pushed_button = BUTTON_NEXT; + if (self->action_group && self->next_action) + g_action_group_activate_action (self->action_group, self->next_action, NULL); + break; + + case GDK_KEY_space: + self->cur_pushed_button = BUTTON_PLAYPAUSE; + if (self->action_group && self->play_action) + g_action_group_activate_action (self->action_group, self->play_action, NULL); + break; + + default: + self->cur_pushed_button = BUTTON_NONE; + } + + if (self->cur_pushed_button) + { + self->keyboard_activated = TRUE; + gtk_widget_queue_draw (widget); + return TRUE; + } + + return FALSE; +} + +static gboolean +ido_playback_menu_item_parent_key_release_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + switch (event->keyval) + { + case GDK_KEY_Left: + case GDK_KEY_Right: + case GDK_KEY_space: + self->cur_pushed_button = BUTTON_NONE; + self->keyboard_activated = FALSE; + gtk_widget_queue_draw (widget); + return TRUE; + } + + return FALSE; +} + +static void +ido_playback_menu_item_parent_set (GtkWidget *widget, + GtkWidget *old_parent) +{ + GtkWidget *parent; + + /* Menus don't pass key events to their children. This works around + * that by listening to key events on the parent widget. */ + + if (old_parent) + { + g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_press_event, widget); + g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_release_event, widget); + } + + parent = gtk_widget_get_parent (widget); + if (parent) + { + g_signal_connect (parent, "key-press-event", + G_CALLBACK (ido_playback_menu_item_parent_key_press_event), widget); + g_signal_connect (parent, "key-release-event", + G_CALLBACK (ido_playback_menu_item_parent_key_release_event), widget); + } +} + +static void +ido_playback_menu_item_select (GtkMenuItem *item) +{ + IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); + + self->has_focus = TRUE; + + GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->select (item); +} + +static void +ido_playback_menu_item_deselect (GtkMenuItem *item) +{ + IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); + + self->has_focus = FALSE; + + GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->deselect (item); +} + +static gboolean +ido_playback_menu_item_button_press_event (GtkWidget *menuitem, + GdkEventButton *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_pushed_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_button_release_event (GtkWidget *menuitem, + GdkEventButton *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + Button button; + + button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + if (button != item->cur_pushed_button) + button = BUTTON_NONE; + + switch (button) + { + case BUTTON_NONE: + break; + + case BUTTON_PREVIOUS: + if (item->action_group && item->prev_action) + g_action_group_activate_action (item->action_group, item->prev_action, NULL); + break; + + case BUTTON_NEXT: + if (item->action_group && item->next_action) + g_action_group_activate_action (item->action_group, item->next_action, NULL); + break; + + case BUTTON_PLAYPAUSE: + if (item->action_group && item->play_action) + g_action_group_activate_action (item->action_group, item->play_action, NULL); + break; + } + + item->cur_pushed_button = BUTTON_NONE; + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_motion_notify_event (GtkWidget *menuitem, + GdkEventMotion *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_hover_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_leave_notify_event (GtkWidget *menuitem, + GdkEventCrossing *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_pushed_button = BUTTON_NONE; + item->cur_hover_button = BUTTON_NONE; + gtk_widget_queue_draw (GTK_WIDGET(menuitem)); + + return TRUE; +} + +static void +ido_playback_menu_item_class_init (IdoPlaybackMenuItemClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkMenuItemClass *menuitem_class = GTK_MENU_ITEM_CLASS (klass); + + gobject_class->dispose = ido_playback_menu_item_dispose; + gobject_class->finalize = ido_playback_menu_item_finalize; + + widget_class->button_press_event = ido_playback_menu_item_button_press_event; + widget_class->button_release_event = ido_playback_menu_item_button_release_event; + widget_class->motion_notify_event = ido_playback_menu_item_motion_notify_event; + widget_class->leave_notify_event = ido_playback_menu_item_leave_notify_event; + widget_class->parent_set = ido_playback_menu_item_parent_set; + widget_class->draw = ido_playback_menu_item_draw; + + menuitem_class->select = ido_playback_menu_item_select; + menuitem_class->deselect = ido_playback_menu_item_deselect; +} + +static void +ido_playback_menu_item_init (IdoPlaybackMenuItem *self) +{ + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_CLASS_SPINNER); + + gtk_widget_set_size_request (GTK_WIDGET (self), 200, 43); +} + +static void +ido_playback_menu_item_set_state_from_string (IdoPlaybackMenuItem *self, + const gchar *state) +{ + if (g_str_equal (state, "Playing")) + self->current_state = STATE_PLAYING; + else if (g_str_equal (state, "Launching")) + self->current_state = STATE_LAUNCHING; + else /* "Paused" and fallback */ + self->current_state = STATE_PAUSED; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +ido_playback_menu_item_action_added (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + GVariant *state; + + state = g_action_group_get_action_state (action_group, self->play_action); + if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) + ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (state, NULL)); + + g_variant_unref (state); + } +} + +static void +ido_playback_menu_item_action_removed (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + self->current_state = STATE_PAUSED; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +ido_playback_menu_item_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *value, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (value, NULL)); + } +} + +GtkMenuItem * +ido_playback_menu_item_new_from_model (GMenuItem *item, + GActionGroup *actions) +{ + IdoPlaybackMenuItem *widget; + + widget = g_object_new (IDO_TYPE_PLAYBACK_MENU_ITEM, NULL); + + widget->action_group = g_object_ref (actions); + g_signal_connect (actions, "action-state-changed", G_CALLBACK (ido_playback_menu_item_action_state_changed), widget); + g_signal_connect (actions, "action-added", G_CALLBACK (ido_playback_menu_item_action_added), widget); + g_signal_connect (actions, "action-removed", G_CALLBACK (ido_playback_menu_item_action_removed), widget); + + g_menu_item_get_attribute (item, "x-canonical-play-action", "s", &widget->play_action); + g_menu_item_get_attribute (item, "x-canonical-next-action", "s", &widget->next_action); + g_menu_item_get_attribute (item, "x-canonical-previous-action", "s", &widget->prev_action); + + if (widget->play_action && g_action_group_has_action (actions, widget->play_action)) + ido_playback_menu_item_action_added (actions, widget->play_action, widget); + + return GTK_MENU_ITEM (widget); +} + + +/* + * Drawing + */ + +#define RECT_WIDTH 130.0f +#define Y 7.0f +#define X 70.0f +#define INNER_RADIUS 12.5 +#define MIDDLE_RADIUS 13.0f +#define OUTER_RADIUS 14.5f +#define CIRCLE_RADIUS 21.0f +#define PREV_WIDTH 25.0f +#define PREV_HEIGHT 17.0f +#define NEXT_WIDTH 25.0f //PREV_WIDTH +#define NEXT_HEIGHT 17.0f //PREV_HEIGHT +#define TRI_WIDTH 11.0f +#define TRI_HEIGHT 13.0f +#define TRI_OFFSET 6.0f +#define PREV_X 68.0f +#define PREV_Y 13.0f +#define NEXT_X 146.0f +#define NEXT_Y 13.0f //prev_y +#define PAUSE_WIDTH 21.0f +#define PAUSE_HEIGHT 27.0f +#define BAR_WIDTH 4.5f +#define BAR_HEIGHT 24.0f +#define BAR_OFFSET 10.0f +#define PAUSE_X 111.0f +#define PAUSE_Y 7.0f +#define PLAY_WIDTH 28.0f +#define PLAY_HEIGHT 29.0f +#define PLAY_PADDING 5.0f +#define INNER_START_SHADE 0.98 +#define INNER_END_SHADE 0.98 +#define MIDDLE_START_SHADE 1.0 +#define MIDDLE_END_SHADE 1.0 +#define OUTER_START_SHADE 0.75 +#define OUTER_END_SHADE 1.3 +#define SHADOW_BUTTON_SHADE 0.8 +#define OUTER_PLAY_START_SHADE 0.7 +#define OUTER_PLAY_END_SHADE 1.38 +#define BUTTON_START_SHADE 1.1 +#define BUTTON_END_SHADE 0.9 +#define BUTTON_SHADOW_SHADE 0.8 +#define INNER_COMPRESSED_START_SHADE 1.0 +#define INNER_COMPRESSED_END_SHADE 1.0 + +typedef struct +{ + double r; + double g; + double b; +} CairoColorRGB; + +void _color_shade (const CairoColorRGB *a, float k, CairoColorRGB *b); + + +static void +draw_gradient (cairo_t* cr, + double x, + double y, + double w, + double r, + double* rgba_start, + double* rgba_end) +{ + cairo_pattern_t* pattern = NULL; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + w - 2.0f * r, y); + cairo_arc (cr, + x + w - 2.0f * r, + y + r, + r, + -90.0f * G_PI / 180.0f, + 90.0f * G_PI / 180.0f); + cairo_line_to (cr, x, y + 2.0f * r); + cairo_arc (cr, + x, + y + r, + r, + 90.0f * G_PI / 180.0f, + 270.0f * G_PI / 180.0f); + cairo_close_path (cr); + + pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +draw_circle (cairo_t* cr, + double x, + double y, + double r, + double* rgba_start, + double* rgba_end) +{ + cairo_pattern_t* pattern = NULL; + + cairo_move_to (cr, x, y); + cairo_arc (cr, + x + r, + y + r, + r, + 0.0f * G_PI / 180.0f, + 360.0f * G_PI / 180.0f); + + pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +_setup (cairo_t** cr, + cairo_surface_t** surf, + gint width, + gint height) +{ + if (!cr || !surf) + return; + + *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + *cr = cairo_create (*surf); + cairo_scale (*cr, 1.0f, 1.0f); + cairo_set_operator (*cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (*cr); + cairo_set_operator (*cr, CAIRO_OPERATOR_OVER); +} + +static void +_mask_prev (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height, + double tri_offset) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y + tri_height / 2.0f); + cairo_line_to (cr, x + tri_width, y); + cairo_line_to (cr, x + tri_width, y + tri_height); + x += tri_offset; + cairo_move_to (cr, x, y + tri_height / 2.0f); + cairo_line_to (cr, x + tri_width, y); + cairo_line_to (cr, x + tri_width, y + tri_height); + x -= tri_offset; + cairo_rectangle (cr, x, y, 2.5f, tri_height); + cairo_close_path (cr); +} + +static void +_mask_next (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height, + double tri_offset) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + x += tri_offset; + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + x -= tri_offset; + x += 2.0f * tri_width - tri_offset - 1.0f; + cairo_rectangle (cr, x, y, 2.5f, tri_height); + + cairo_close_path (cr); +} + +static void +_mask_pause (cairo_t* cr, + double x, + double y, + double bar_width, + double bar_height, + double bar_offset) +{ + if (!cr) + return; + + cairo_set_line_width (cr, bar_width); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + + x += bar_width; + y += bar_width; + cairo_move_to (cr, x, y); + cairo_line_to (cr, x, y + bar_height); + cairo_move_to (cr, x + bar_offset, y); + cairo_line_to (cr, x + bar_offset, y + bar_height); + +} + +static void +_mask_play (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + cairo_close_path (cr); + +} + +static void +_fill (cairo_t* cr, + double x_start, + double y_start, + double x_end, + double y_end, + double* rgba_start, + double* rgba_end, + gboolean stroke) +{ + cairo_pattern_t* pattern = NULL; + + if (!cr || !rgba_start || !rgba_end) + return; + + pattern = cairo_pattern_create_linear (x_start, y_start, x_end, y_end); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + if (stroke) + cairo_stroke (cr); + else + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +_finalize (cairo_t* cr, + cairo_t** cr_surf, + cairo_surface_t** surf, + double x, + double y) +{ + if (!cr || !cr_surf || !surf) + return; + + cairo_set_source_surface (cr, *surf, x, y); + cairo_paint (cr); + cairo_surface_destroy (*surf); + cairo_destroy (*cr_surf); +} + +static void +_finalize_repaint (cairo_t* cr, + cairo_t** cr_surf, + cairo_surface_t** surf, + double x, + double y, + int repaints) +{ + if (!cr || !cr_surf || !surf) + return; + + while (repaints > 0) + { + cairo_set_source_surface (cr, *surf, x, y); + cairo_paint (cr); + repaints--; + } + + cairo_surface_destroy (*surf); + cairo_destroy (*cr_surf); +} + +static void +_color_rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h = 0; + gdouble l; + gdouble s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + l = (max+min)/2; + if (fabs (max-min) < 0.0001) + { + h = 0; + s = 0; + } + else + { + if (l <= 0.5) + s = (max-min)/(max+min); + else + s = (max-min)/(2-max-min); + + delta = (max -min) != 0 ? (max -min) : 1; + + if(delta == 0) + delta = 1; + if (red == max) + h = (green-blue)/delta; + else if (green == max) + h = 2+(blue-red)/delta; + else if (blue == max) + h = 4+(red-green)/delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +static void +_color_hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness*(1+saturation); + else + m2 = lightness+saturation-lightness*saturation; + + m1 = 2*lightness-m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h+120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1+(m2-m1)*hue/60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1+(m2-m1)*(240-hue)/60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1+(m2-m1)*hue/60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1+(m2-m1)*(240-hue)/60; + else + g = m1; + + hue = *h-120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1+(m2-m1)*hue/60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1+(m2-m1)*(240-hue)/60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} + +void +_color_shade (const CairoColorRGB *a, float k, CairoColorRGB *b) +{ + double red; + double green; + double blue; + + red = a->r; + green = a->g; + blue = a->b; + + if (k == 1.0) + { + b->r = red; + b->g = green; + b->b = blue; + return; + } + + _color_rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + _color_hls_to_rgb (&red, &green, &blue); + + b->r = red; + b->g = green; + b->b = blue; +} + +static inline void +_blurinner (guchar* pixel, + gint* zR, + gint* zG, + gint* zB, + gint* zA, + gint alpha, + gint aprec, + gint zprec) +{ + gint R; + gint G; + gint B; + guchar A; + + R = *pixel; + G = *(pixel + 1); + B = *(pixel + 2); + A = *(pixel + 3); + + *zR += (alpha * ((R << zprec) - *zR)) >> aprec; + *zG += (alpha * ((G << zprec) - *zG)) >> aprec; + *zB += (alpha * ((B << zprec) - *zB)) >> aprec; + *zA += (alpha * ((A << zprec) - *zA)) >> aprec; + + *pixel = *zR >> zprec; + *(pixel + 1) = *zG >> zprec; + *(pixel + 2) = *zB >> zprec; + *(pixel + 3) = *zA >> zprec; +} + +static inline void +_blurrow (guchar* pixels, + gint width, + gint height, + gint channels, + gint line, + gint alpha, + gint aprec, + gint zprec) +{ + gint zR; + gint zG; + gint zB; + gint zA; + gint index; + guchar* scanline; + + scanline = &(pixels[line * width * channels]); + + zR = *scanline << zprec; + zG = *(scanline + 1) << zprec; + zB = *(scanline + 2) << zprec; + zA = *(scanline + 3) << zprec; + + for (index = 0; index < width; index ++) + _blurinner (&scanline[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); + + for (index = width - 2; index >= 0; index--) + _blurinner (&scanline[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); +} + +static inline void +_blurcol (guchar* pixels, + gint width, + gint height, + gint channels, + gint x, + gint alpha, + gint aprec, + gint zprec) +{ + gint zR; + gint zG; + gint zB; + gint zA; + gint index; + guchar* ptr; + + ptr = pixels; + + ptr += x * channels; + + zR = *((guchar*) ptr ) << zprec; + zG = *((guchar*) ptr + 1) << zprec; + zB = *((guchar*) ptr + 2) << zprec; + zA = *((guchar*) ptr + 3) << zprec; + + for (index = width; index < (height - 1) * width; index += width) + _blurinner ((guchar*) &ptr[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); + + for (index = (height - 2) * width; index >= 0; index -= width) + _blurinner ((guchar*) &ptr[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); +} + +void +_expblur (guchar* pixels, + gint width, + gint height, + gint channels, + gint radius, + gint aprec, + gint zprec) +{ + gint alpha; + gint row = 0; + gint col = 0; + + if (radius < 1) + return; + + // calculate the alpha such that 90% of + // the kernel is within the radius. + // (Kernel extends to infinity) + alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f)))); + + for (; row < height; row++) + _blurrow (pixels, + width, + height, + channels, + row, + alpha, + aprec, + zprec); + + for(; col < width; col++) + _blurcol (pixels, + width, + height, + channels, + col, + alpha, + aprec, + zprec); + + return; +} + +void +_surface_blur (cairo_surface_t* surface, + guint radius) +{ + guchar* pixels; + guint width; + guint height; + cairo_format_t format; + + // before we mess with the surface execute any pending drawing + cairo_surface_flush (surface); + + pixels = cairo_image_surface_get_data (surface); + width = cairo_image_surface_get_width (surface); + height = cairo_image_surface_get_height (surface); + format = cairo_image_surface_get_format (surface); + + switch (format) + { + case CAIRO_FORMAT_ARGB32: + _expblur (pixels, width, height, 4, radius, 16, 7); + break; + + case CAIRO_FORMAT_RGB24: + _expblur (pixels, width, height, 3, radius, 16, 7); + break; + + case CAIRO_FORMAT_A8: + _expblur (pixels, width, height, 1, radius, 16, 7); + break; + + default : + // do nothing + break; + } + + // inform cairo we altered the surfaces contents + cairo_surface_mark_dirty (surface); +} + +static gboolean +ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (button); + + g_return_val_if_fail(IDO_IS_PLAYBACK_MENU_ITEM (button), FALSE); + g_return_val_if_fail(cr != NULL, FALSE); + + cairo_surface_t* surf = NULL; + cairo_t* cr_surf = NULL; + + GtkStyle *style; + + gtk_style_context_add_class (gtk_widget_get_style_context (button), + GTK_STYLE_CLASS_MENU); + CairoColorRGB bg_color, fg_color, bg_selected, bg_prelight; + CairoColorRGB color_middle[2], color_middle_prelight[2], color_outer[2], color_outer_prelight[2], + color_play_outer[2], color_play_outer_prelight[2], + color_button[4], color_button_shadow, color_inner[2], color_inner_compressed[2]; + + /* Use the menu's style instead of that of the menuitem ('button' is a + * menuitem that is packed in a menu directly). The menuitem's style + * can't be used due to a change in light-themes (lp #1130183). + * Menuitems now have a transparent background, which confuses + * GtkStyle. + * + * This is a workaround until this code gets refactored to use + * GtkStyleContext. + */ + style = gtk_widget_get_style (gtk_widget_get_parent (button)); + + bg_color.r = style->bg[0].red/65535.0; + bg_color.g = style->bg[0].green/65535.0; + bg_color.b = style->bg[0].blue/65535.0; + + bg_prelight.r = style->bg[GTK_STATE_PRELIGHT].red/65535.0; + bg_prelight.g = style->bg[GTK_STATE_PRELIGHT].green/65535.0; + bg_prelight.b = style->bg[GTK_STATE_PRELIGHT].blue/65535.0; + + bg_selected.r = style->bg[GTK_STATE_SELECTED].red/65535.0; + bg_selected.g = style->bg[GTK_STATE_SELECTED].green/65535.0; + bg_selected.b = style->bg[GTK_STATE_SELECTED].blue/65535.0; + + fg_color.r = style->fg[0].red/65535.0; + fg_color.g = style->fg[0].green/65535.0; + fg_color.b = style->fg[0].blue/65535.0; + + _color_shade (&bg_color, MIDDLE_START_SHADE, &color_middle[0]); + _color_shade (&bg_color, MIDDLE_END_SHADE, &color_middle[1]); + _color_shade (&bg_prelight, MIDDLE_START_SHADE, &color_middle_prelight[0]); + _color_shade (&bg_prelight, MIDDLE_END_SHADE, &color_middle_prelight[1]); + _color_shade (&bg_color, OUTER_START_SHADE, &color_outer[0]); + _color_shade (&bg_color, OUTER_END_SHADE, &color_outer[1]); + _color_shade (&bg_prelight, OUTER_START_SHADE, &color_outer_prelight[0]); + _color_shade (&bg_prelight, OUTER_END_SHADE, &color_outer_prelight[1]); + _color_shade (&bg_color, OUTER_PLAY_START_SHADE, &color_play_outer[0]); + _color_shade (&bg_color, OUTER_PLAY_END_SHADE, &color_play_outer[1]); + _color_shade (&bg_prelight, OUTER_PLAY_START_SHADE, &color_play_outer_prelight[0]); + _color_shade (&bg_prelight, OUTER_PLAY_END_SHADE, &color_play_outer_prelight[1]); + _color_shade (&bg_color, INNER_START_SHADE, &color_inner[0]); + _color_shade (&bg_color, INNER_END_SHADE, &color_inner[1]); + _color_shade (&fg_color, BUTTON_START_SHADE, &color_button[0]); + _color_shade (&fg_color, BUTTON_END_SHADE, &color_button[1]); + _color_shade (&bg_color, BUTTON_SHADOW_SHADE, &color_button[2]); + _color_shade (&bg_color, SHADOW_BUTTON_SHADE, &color_button_shadow); + _color_shade (&bg_selected, 1.0, &color_button[3]); + _color_shade (&bg_color, INNER_COMPRESSED_START_SHADE, &color_inner_compressed[0]); + _color_shade (&bg_color, INNER_COMPRESSED_END_SHADE, &color_inner_compressed[1]); + + double MIDDLE_END[] = {color_middle[0].r, color_middle[0].g, color_middle[0].b, 1.0f}; + double MIDDLE_START[] = {color_middle[1].r, color_middle[1].g, color_middle[1].b, 1.0f}; + double MIDDLE_END_PRELIGHT[] = {color_middle_prelight[0].r, color_middle_prelight[0].g, color_middle_prelight[0].b, 1.0f}; + double MIDDLE_START_PRELIGHT[] = {color_middle_prelight[1].r, color_middle_prelight[1].g, color_middle_prelight[1].b, 1.0f}; + double OUTER_END[] = {color_outer[0].r, color_outer[0].g, color_outer[0].b, 1.0f}; + double OUTER_START[] = {color_outer[1].r, color_outer[1].g, color_outer[1].b, 1.0f}; + double OUTER_END_PRELIGHT[] = {color_outer_prelight[0].r, color_outer_prelight[0].g, color_outer_prelight[0].b, 1.0f}; + double OUTER_START_PRELIGHT[] = {color_outer_prelight[1].r, color_outer_prelight[1].g, color_outer_prelight[1].b, 1.0f}; + double SHADOW_BUTTON[] = {color_button_shadow.r, color_button_shadow.g, color_button_shadow.b, 0.3f}; + double OUTER_PLAY_END[] = {color_play_outer[0].r, color_play_outer[0].g, color_play_outer[0].b, 1.0f}; + double OUTER_PLAY_START[] = {color_play_outer[1].r, color_play_outer[1].g, color_play_outer[1].b, 1.0f}; + double OUTER_PLAY_END_PRELIGHT[] = {color_play_outer_prelight[0].r, color_play_outer_prelight[0].g, color_play_outer_prelight[0].b, 1.0f}; + double OUTER_PLAY_START_PRELIGHT[] = {color_play_outer_prelight[1].r, color_play_outer_prelight[1].g, color_play_outer_prelight[1].b, 1.0f}; + double BUTTON_END[] = {color_button[0].r, color_button[0].g, color_button[0].b, 1.0f}; + double BUTTON_START[] = {color_button[1].r, color_button[1].g, color_button[1].b, 1.0f}; + double BUTTON_SHADOW[] = {color_button[2].r, color_button[2].g, color_button[2].b, 0.75f}; + double BUTTON_SHADOW_FOCUS[] = {color_button[3].r, color_button[3].g, color_button[3].b, 1.0f}; + double INNER_COMPRESSED_END[] = {color_inner_compressed[1].r, color_inner_compressed[1].g, color_inner_compressed[1].b, 1.0f}; + double INNER_COMPRESSED_START[] = {color_inner_compressed[0].r, color_inner_compressed[0].g, color_inner_compressed[0].b, 1.0f}; + + + draw_gradient (cr, + X, + Y, + RECT_WIDTH, + OUTER_RADIUS, + OUTER_START, + OUTER_END); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH - 2, + MIDDLE_RADIUS, + MIDDLE_START, + MIDDLE_END); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH - 4, + MIDDLE_RADIUS, + MIDDLE_START, + MIDDLE_END); + + + if(item->cur_pushed_button == BUTTON_PREVIOUS) + { + draw_gradient (cr, + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_END, + OUTER_START); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if(item->cur_pushed_button == BUTTON_NEXT) + { + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_END, + OUTER_START); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 1, + (RECT_WIDTH - 4.5)/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 2, + (RECT_WIDTH - 7)/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if (item->cur_hover_button == BUTTON_PREVIOUS) + { + draw_gradient (cr, + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_START_PRELIGHT, + OUTER_END_PRELIGHT); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + else if (item->cur_hover_button == BUTTON_NEXT) + { + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_START_PRELIGHT, + OUTER_END_PRELIGHT); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 1, + (RECT_WIDTH - 4.5)/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 2, + (RECT_WIDTH - 7)/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + + // play/pause shadow + if(item->cur_pushed_button != BUTTON_PLAYPAUSE) + { + cairo_save (cr); + cairo_rectangle (cr, X, Y, RECT_WIDTH, MIDDLE_RADIUS*2); + cairo_clip (cr); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f - 1.0f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) - 1.0f, + CIRCLE_RADIUS + 1.0f, + SHADOW_BUTTON, + SHADOW_BUTTON); + + cairo_restore (cr); + } + + // play/pause button + if(item->cur_pushed_button == BUTTON_PLAYPAUSE) + { + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) , + CIRCLE_RADIUS, + OUTER_PLAY_END, + OUTER_PLAY_START); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if (item->cur_hover_button == BUTTON_PLAYPAUSE) + { + /* this subtle offset is to fix alpha borders, should be removed once this draw routine will be refactored */ + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 0.1, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 0.1, + CIRCLE_RADIUS - 0.1, + OUTER_PLAY_START_PRELIGHT, + OUTER_PLAY_END_PRELIGHT); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + else + { + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)), + CIRCLE_RADIUS, + OUTER_PLAY_START, + OUTER_PLAY_END); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + MIDDLE_START, + MIDDLE_END); + } + + // draw previous-button drop-shadow + if (item->cur_pushed_button == BUTTON_PREVIOUS && item->keyboard_activated ) + { + _setup (&cr_surf, &surf, PREV_WIDTH+6, PREV_HEIGHT+6); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PREV_X, PREV_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y + 1.0f); + } + + // draw previous-button + _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y); + + // draw next-button drop-shadow + if (item->cur_pushed_button == BUTTON_NEXT && item->keyboard_activated) + { + _setup (&cr_surf, &surf, NEXT_WIDTH+6, NEXT_HEIGHT+6); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 1.0f); + } + + // draw next-button + _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y); + + // draw pause-button drop-shadow + if (item->current_state == STATE_PLAYING) + { + if (item->has_focus && + (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) + { + _setup (&cr_surf, &surf, PAUSE_WIDTH+6, PAUSE_HEIGHT+6); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + TRUE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + TRUE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 1.0f); + } + + // draw pause-button + _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_START, + BUTTON_END, + TRUE); + _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y); + } + else if (item->current_state == STATE_PAUSED) + { + if (item->has_focus && + (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) + { + _setup (&cr_surf, &surf, PLAY_WIDTH+6, PLAY_HEIGHT+6); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PAUSE_X-0.75f, PAUSE_Y + 1.0f); + } + + // draw play-button + _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); + cairo_set_line_width (cr, 10.5); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y); + } + else if (item->current_state == STATE_LAUNCHING) + { + // the spinner is not aligned, why? because the play button has odd width/height numbers + gtk_render_activity (gtk_widget_get_style_context (button), cr, 106, 6, 30, 30); + } + return FALSE; +} diff --git a/src/idoplaybackmenuitem.h b/src/idoplaybackmenuitem.h new file mode 100644 index 0000000..8b6d45a --- /dev/null +++ b/src/idoplaybackmenuitem.h @@ -0,0 +1,37 @@ +/* + * 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: + * Conor Curran <conor.curran@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_PLAYBACK_MENU_ITEM_H__ +#define __IDO_PLAYBACK_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_PLAYBACK_MENU_ITEM (ido_playback_menu_item_get_type ()) +#define IDO_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM, IdoPlaybackMenuItem)) +#define IDO_IS_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM)) + +typedef struct _IdoPlaybackMenuItem IdoPlaybackMenuItem; + +GType ido_playback_menu_item_get_type (void); + +GtkMenuItem * ido_playback_menu_item_new_from_model (GMenuItem *item, + GActionGroup *actions); + +#endif |