aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2013-06-20 01:51:44 +0000
committerTarmac <Unknown>2013-06-20 01:51:44 +0000
commit8f8d487e5a29c1d483e9edd2067e17e4b8c0e205 (patch)
treebf6077a911b65ddeb740bc1674d841b43ed04761
parente84fd152bb24fab0676989f1501cc2b807400bd6 (diff)
parente88a842c9f502810f58ab478e009161f92dc9884 (diff)
downloadayatana-ido-8f8d487e5a29c1d483e9edd2067e17e4b8c0e205.tar.gz
ayatana-ido-8f8d487e5a29c1d483e9edd2067e17e4b8c0e205.tar.bz2
ayatana-ido-8f8d487e5a29c1d483e9edd2067e17e4b8c0e205.zip
Add IdoMediaPlayerMenuItem and IdoPlaybackMenuItem.
Approved by Ted Gould, PS Jenkins bot.
-rw-r--r--debian/libido3-0.1-0.symbols4
-rw-r--r--src/Makefile.am10
-rw-r--r--src/idomediaplayermenuitem.c376
-rw-r--r--src/idomediaplayermenuitem.h42
-rw-r--r--src/idomenuitemfactory.c8
-rw-r--r--src/idoplaybackmenuitem.c1680
-rw-r--r--src/idoplaybackmenuitem.h37
7 files changed, 2154 insertions, 3 deletions
diff --git a/debian/libido3-0.1-0.symbols b/debian/libido3-0.1-0.symbols
index 401e75d..d253c59 100644
--- a/debian/libido3-0.1-0.symbols
+++ b/debian/libido3-0.1-0.symbols
@@ -88,3 +88,7 @@ libido3-0.1.so.0 libido3-0.1-0 #MINVER#
ido_user_menu_item_set_icon@Base 13.10.0daily13.06.19
ido_user_menu_item_set_label@Base 13.10.0daily13.06.19
ido_user_menu_item_set_logged_in@Base 13.10.0daily13.06.19
+ ido_media_player_menu_item_get_type@Base 0replaceme
+ ido_media_player_menu_item_new_from_model@Base 0replaceme
+ ido_playback_menu_item_get_type@Base 0replaceme
+ ido_playback_menu_item_new_from_model@Base 0replaceme
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..7e6e9d3
--- /dev/null
+++ b/src/idomediaplayermenuitem.c
@@ -0,0 +1,376 @@
+/*
+ * 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"
+
+#define ALBUM_ART_SIZE 60
+
+typedef GtkMenuItemClass IdoMediaPlayerMenuItemClass;
+
+struct _IdoMediaPlayerMenuItem
+{
+ GtkMenuItem parent;
+
+ GCancellable *cancellable;
+ 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 void
+ido_media_player_menu_item_dispose (GObject *object)
+{
+ IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (object);
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ G_OBJECT_CLASS (ido_media_player_menu_item_parent_class)->dispose (object);
+}
+
+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)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ido_media_player_menu_item_dispose;
+
+ 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->cancellable = g_cancellable_new ();
+
+ 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_size_request (self->album_art, ALBUM_ART_SIZE, ALBUM_ART_SIZE);
+ 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
+album_art_received (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdoMediaPlayerMenuItem *self = user_data;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ pixbuf = gdk_pixbuf_new_from_stream_finish (result, &error);
+ if (pixbuf == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("unable to fetch album art: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (self->album_art), pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+album_art_file_opened (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdoMediaPlayerMenuItem *self = user_data;
+ GFileInputStream *input;
+ GError *error = NULL;
+
+ input = g_file_read_finish (G_FILE (object), result, &error);
+ if (input == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("unable to fetch album art: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (input),
+ ALBUM_ART_SIZE, ALBUM_ART_SIZE, TRUE,
+ self->cancellable,
+ album_art_received, self);
+
+ g_object_unref (input);
+}
+
+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));
+
+ gtk_image_clear (GTK_IMAGE (self->album_art));
+
+ if (url == NULL)
+ return;
+
+ file = g_file_new_for_uri (url);
+ g_file_read_async (file, G_PRIORITY_DEFAULT, self->cancellable, album_art_file_opened, self);
+
+ 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;
+ const gchar *title = NULL;
+ const gchar *artist = NULL;
+ const gchar *album = NULL;
+ const 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..662ceaf
--- /dev/null
+++ b/src/idoplaybackmenuitem.c
@@ -0,0 +1,1680 @@
+/*
+ * 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)
+{
+ /* 57 101 143 187
+ * 5 +------+
+ * 12 +-----+ +-----+
+ * |prev play next|
+ * 40 +-----+ +-----+
+ * 47 +------+
+ */
+
+ if (x > 57 && x < 102 && y > 12 && y < 40)
+ return BUTTON_PREVIOUS;
+
+ if (x > 101 && x < 143 && y > 5 && y < 47)
+ return BUTTON_PLAYPAUSE;
+
+ 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 != BUTTON_NONE)
+ {
+ 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)
+{
+ g_return_if_fail (state != NULL);
+
+ 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;
+
+ g_return_if_fail (action_name != NULL);
+
+ 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;
+
+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;
+ }
+}
+
+static 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);
+}
+
+static 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;
+}
+
+static 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