1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3
* Copyright (C) 2002, 2003 Jorn Baayen <jorn@nl.linux.org>
4
* Copyright (C) 2003 Colin Walters <walters@gnome.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* The Rhythmbox authors hereby grant permission for non-GPL compatible
12
* GStreamer plugins to be used and distributed together with GStreamer
13
* and Rhythmbox. This permission is above and beyond the permissions granted
14
* by the GPL license by which Rhythmbox is covered. If you modify this code
15
* you may extend this exception to your version of the code, but you are not
16
* obligated to do so. If you do not wish to do so, delete this exception
17
* statement from your version.
19
* This program is distributed in the hope that it will be useful,
20
* but WITHOUT ANY WARRANTY; without even the implied warranty of
21
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
* GNU General Public License for more details.
24
* You should have received a copy of the GNU General Public License
25
* along with this program; if not, write to the Free Software
26
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
35
#include <glib/gi18n.h>
38
#include "rb-stock-icons.h"
39
#include "rb-header.h"
41
#include "rb-shell-player.h"
44
#include "rb-player.h"
45
#include "rb-text-helpers.h"
46
#include "rb-fading-image.h"
47
#include "rb-file-helpers.h"
48
#include "rb-ext-db.h"
52
* @short_description: playback area widgetry
54
* The RBHeader widget displays information about the current playing track
55
* (title, album, artist), the elapsed or remaining playback time, and a
56
* position slider indicating the playback position. It translates slider
57
* move and drag events into seek requests for the player backend.
59
* For shoutcast-style streams, the title/artist/album display is supplemented
60
* by metadata extracted from the stream. See #RBStreamingSource for more information
61
* on how the metadata is reported.
64
static void rb_header_class_init (RBHeaderClass *klass);
65
static void rb_header_init (RBHeader *header);
66
static void rb_header_dispose (GObject *object);
67
static void rb_header_finalize (GObject *object);
68
static void rb_header_set_property (GObject *object,
72
static void rb_header_get_property (GObject *object,
76
static GtkSizeRequestMode rb_header_get_request_mode (GtkWidget *widget);
77
static void rb_header_get_preferred_width (GtkWidget *widget,
80
static void rb_header_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
81
static void rb_header_update_elapsed (RBHeader *header);
82
static void apply_slider_position (RBHeader *header);
83
static gboolean slider_press_callback (GtkWidget *widget, GdkEventButton *event, RBHeader *header);
84
static gboolean slider_moved_callback (GtkWidget *widget, GdkEventMotion *event, RBHeader *header);
85
static gboolean slider_release_callback (GtkWidget *widget, GdkEventButton *event, RBHeader *header);
86
static void slider_changed_callback (GtkWidget *widget, RBHeader *header);
87
static gboolean slider_scroll_callback (GtkWidget *widget, GdkEventScroll *event, RBHeader *header);
88
static void time_button_clicked_cb (GtkWidget *button, RBHeader *header);
90
static void rb_header_elapsed_changed_cb (RBShellPlayer *player, gint64 elapsed, RBHeader *header);
91
static void rb_header_extra_metadata_cb (RhythmDB *db, RhythmDBEntry *entry, const char *property_name, const GValue *metadata, RBHeader *header);
92
static void rb_header_sync (RBHeader *header);
93
static void rb_header_sync_time (RBHeader *header);
95
static void uri_dropped_cb (RBFadingImage *image, const char *uri, RBHeader *header);
96
static void pixbuf_dropped_cb (RBFadingImage *image, GdkPixbuf *pixbuf, RBHeader *header);
97
static void image_button_press_cb (GtkWidget *widget, GdkEvent *event, RBHeader *header);
98
static void art_added_cb (RBExtDB *db, RBExtDBKey *key, const char *filename, GValue *data, RBHeader *header);
100
struct RBHeaderPrivate
103
RhythmDBEntry *entry;
106
RBShellPlayer *shell_player;
114
GtkAdjustment *adjustment;
115
gboolean slider_dragging;
116
gboolean slider_locked;
117
guint slider_moved_timeout;
118
long latest_set_time;
120
GtkWidget *timebutton;
121
GtkWidget *timelabel;
123
gint64 elapsed_time; /* nanoseconds */
124
gboolean show_remaining;
128
gboolean show_album_art;
129
gboolean show_slider;
138
PROP_SLIDER_DRAGGING,
140
PROP_SHOW_POSITION_SLIDER,
144
#define TITLE_FORMAT "<big><b>%s</b></big>"
145
#define ALBUM_FORMAT "<i>%s</i>"
146
#define ARTIST_FORMAT "<i>%s</i>"
147
#define STREAM_FORMAT "%s"
149
/* unicode graphic characters, encoded in UTF-8 */
150
static const char const *UNICODE_MIDDLE_DOT = "\xC2\xB7";
152
#define SCROLL_UP_SEEK_OFFSET 5
153
#define SCROLL_DOWN_SEEK_OFFSET -5
155
G_DEFINE_TYPE (RBHeader, rb_header, GTK_TYPE_GRID)
158
rb_header_class_init (RBHeaderClass *klass)
160
GObjectClass *object_class = G_OBJECT_CLASS (klass);
161
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
163
object_class->dispose = rb_header_dispose;
164
object_class->finalize = rb_header_finalize;
166
object_class->set_property = rb_header_set_property;
167
object_class->get_property = rb_header_get_property;
169
widget_class->get_request_mode = rb_header_get_request_mode;
170
widget_class->get_preferred_width = rb_header_get_preferred_width;
171
widget_class->size_allocate = rb_header_size_allocate;
172
/* GtkGrid's get_preferred_height_for_width does all we need here */
179
g_object_class_install_property (object_class,
181
g_param_spec_object ("db",
188
* RBHeader:shell-player:
190
* The #RBShellPlayer instance
192
g_object_class_install_property (object_class,
194
g_param_spec_object ("shell-player",
196
"RBShellPlayer object",
197
RB_TYPE_SHELL_PLAYER,
198
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
202
* If TRUE, the header should allow seeking by dragging the playback position slider
204
g_object_class_install_property (object_class,
206
g_param_spec_boolean ("seekable",
213
* RBHeader:slider-dragging:
215
* Whether the song position slider is currently being dragged.
217
g_object_class_install_property (object_class,
218
PROP_SLIDER_DRAGGING,
219
g_param_spec_boolean ("slider-dragging",
225
* RBHeader:show-remaining:
227
* Whether to show remaining time (as opposed to elapsed time) in the numeric
230
g_object_class_install_property (object_class,
232
g_param_spec_boolean ("show-remaining",
234
"whether to show remaining or elapsed time",
239
* RBHeader:show-position-slider:
241
* Whether to show the playback position slider.
243
g_object_class_install_property (object_class,
244
PROP_SHOW_POSITION_SLIDER,
245
g_param_spec_boolean ("show-position-slider",
246
"show position slider",
247
"whether to show the playback position slider",
251
* RBHeader:show-album-art:
253
* Whether to show the album art display widget.
255
g_object_class_install_property (object_class,
257
g_param_spec_boolean ("show-album-art",
259
"whether to show album art",
263
g_type_class_add_private (klass, sizeof (RBHeaderPrivate));
267
rb_header_init (RBHeader *header)
269
header->priv = G_TYPE_INSTANCE_GET_PRIVATE (header, RB_TYPE_HEADER, RBHeaderPrivate);
271
gtk_grid_set_column_spacing (GTK_GRID (header), 6);
272
gtk_grid_set_column_homogeneous (GTK_GRID (header), TRUE);
273
gtk_container_set_border_width (GTK_CONTAINER (header), 3);
275
/* set up position slider */
276
header->priv->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 10.0, 1.0, 10.0, 0.0));
277
header->priv->scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, header->priv->adjustment);
278
gtk_widget_set_hexpand (header->priv->scale, TRUE);
279
g_signal_connect_object (G_OBJECT (header->priv->scale),
280
"button_press_event",
281
G_CALLBACK (slider_press_callback),
283
g_signal_connect_object (G_OBJECT (header->priv->scale),
284
"button_release_event",
285
G_CALLBACK (slider_release_callback),
287
g_signal_connect_object (G_OBJECT (header->priv->scale),
288
"motion_notify_event",
289
G_CALLBACK (slider_moved_callback),
291
g_signal_connect_object (G_OBJECT (header->priv->scale),
293
G_CALLBACK (slider_changed_callback),
295
g_signal_connect_object (G_OBJECT (header->priv->scale),
297
G_CALLBACK (slider_scroll_callback),
299
gtk_scale_set_draw_value (GTK_SCALE (header->priv->scale), FALSE);
300
gtk_widget_set_size_request (header->priv->scale, 150, -1);
302
/* set up song information labels */
303
header->priv->songbox = gtk_grid_new ();
304
gtk_widget_set_hexpand (header->priv->songbox, TRUE);
305
gtk_widget_set_valign (header->priv->songbox, GTK_ALIGN_CENTER);
307
header->priv->song = gtk_label_new (" ");
308
gtk_label_set_use_markup (GTK_LABEL (header->priv->song), TRUE);
309
gtk_label_set_selectable (GTK_LABEL (header->priv->song), TRUE);
310
gtk_label_set_ellipsize (GTK_LABEL (header->priv->song), PANGO_ELLIPSIZE_END);
311
gtk_misc_set_alignment (GTK_MISC (header->priv->song), 0.0, 0.5);
312
gtk_grid_attach (GTK_GRID (header->priv->songbox), header->priv->song, 0, 0, 1, 1);
314
header->priv->details = gtk_label_new ("");
315
gtk_label_set_use_markup (GTK_LABEL (header->priv->details), TRUE);
316
gtk_label_set_selectable (GTK_LABEL (header->priv->details), TRUE);
317
gtk_label_set_ellipsize (GTK_LABEL (header->priv->details), PANGO_ELLIPSIZE_END);
318
gtk_widget_set_hexpand (header->priv->details, TRUE);
319
gtk_misc_set_alignment (GTK_MISC (header->priv->details), 0.0, 0.5);
320
gtk_grid_attach (GTK_GRID (header->priv->songbox), header->priv->details, 0, 1, 1, 2);
322
/* elapsed time / duration display */
323
header->priv->timelabel = gtk_label_new ("");
324
gtk_widget_set_halign (header->priv->timelabel, GTK_ALIGN_END);
326
header->priv->timebutton = gtk_button_new ();
327
gtk_container_add (GTK_CONTAINER (header->priv->timebutton), header->priv->timelabel);
328
g_signal_connect_object (header->priv->timebutton,
330
G_CALLBACK (time_button_clicked_cb),
334
header->priv->art_store = rb_ext_db_new ("album-art");
335
g_signal_connect (header->priv->art_store,
337
G_CALLBACK (art_added_cb),
339
header->priv->image = GTK_WIDGET (g_object_new (RB_TYPE_FADING_IMAGE,
340
"fallback", RB_STOCK_MISSING_ARTWORK,
342
g_signal_connect (header->priv->image,
344
G_CALLBACK (pixbuf_dropped_cb),
346
g_signal_connect (header->priv->image,
348
G_CALLBACK (uri_dropped_cb),
350
g_signal_connect (header->priv->image,
351
"button-press-event",
352
G_CALLBACK (image_button_press_cb),
355
gtk_grid_attach (GTK_GRID (header), header->priv->image, 0, 0, 1, 1);
356
gtk_grid_attach (GTK_GRID (header), header->priv->songbox, 2, 0, 1, 1);
357
gtk_grid_attach (GTK_GRID (header), header->priv->timebutton, 3, 0, 1, 1);
358
gtk_grid_attach (GTK_GRID (header), header->priv->scale, 4, 0, 1, 1);
360
/* currently, nothing sets this. it should be set on track changes. */
361
header->priv->seekable = TRUE;
363
rb_header_sync (header);
367
rb_header_dispose (GObject *object)
369
RBHeader *header = RB_HEADER (object);
371
if (header->priv->db != NULL) {
372
g_object_unref (header->priv->db);
373
header->priv->db = NULL;
376
if (header->priv->shell_player != NULL) {
377
g_object_unref (header->priv->shell_player);
378
header->priv->shell_player = NULL;
381
if (header->priv->art_store != NULL) {
382
g_object_unref (header->priv->art_store);
383
header->priv->art_store = NULL;
386
G_OBJECT_CLASS (rb_header_parent_class)->dispose (object);
390
rb_header_finalize (GObject *object)
394
g_return_if_fail (object != NULL);
395
g_return_if_fail (RB_IS_HEADER (object));
397
header = RB_HEADER (object);
398
g_return_if_fail (header->priv != NULL);
400
g_free (header->priv->image_path);
402
G_OBJECT_CLASS (rb_header_parent_class)->finalize (object);
406
art_cb (RBExtDBKey *key, const char *filename, GValue *data, RBHeader *header)
408
RhythmDBEntry *entry;
410
entry = rb_shell_player_get_playing_entry (header->priv->shell_player);
415
if (rhythmdb_entry_matches_ext_db_key (header->priv->db, entry, key)) {
416
GdkPixbuf *pixbuf = NULL;
418
if (data != NULL && G_VALUE_HOLDS (data, GDK_TYPE_PIXBUF)) {
419
pixbuf = GDK_PIXBUF (g_value_get_object (data));
422
rb_fading_image_set_pixbuf (RB_FADING_IMAGE (header->priv->image), pixbuf);
424
g_free (header->priv->image_path);
425
header->priv->image_path = g_strdup (filename);
428
rhythmdb_entry_unref (entry);
432
art_added_cb (RBExtDB *db, RBExtDBKey *key, const char *filename, GValue *data, RBHeader *header)
434
art_cb (key, filename, data, header);
439
rb_header_playing_song_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBHeader *header)
441
if (header->priv->entry == entry)
444
header->priv->entry = entry;
445
if (header->priv->entry) {
448
header->priv->duration = rhythmdb_entry_get_ulong (header->priv->entry,
449
RHYTHMDB_PROP_DURATION);
451
key = rhythmdb_entry_create_ext_db_key (entry, RHYTHMDB_PROP_ALBUM);
452
rb_ext_db_request (header->priv->art_store,
454
(RBExtDBRequestCallback) art_cb,
455
g_object_ref (header),
457
rb_ext_db_key_free (key);
459
header->priv->duration = 0;
462
rb_header_sync (header);
464
g_free (header->priv->image_path);
465
header->priv->image_path = NULL;
467
rb_fading_image_start (RB_FADING_IMAGE (header->priv->image), 2000);
470
static GtkSizeRequestMode
471
rb_header_get_request_mode (GtkWidget *widget)
473
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
477
rb_header_get_preferred_width (GtkWidget *widget,
486
rb_header_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
493
GtkAllocation child_alloc;
496
gtk_widget_set_allocation (widget, allocation);
497
spacing = gtk_grid_get_column_spacing (GTK_GRID (widget));
498
rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
500
/* take some leading space for the image, which we always make square */
501
if (RB_HEADER (widget)->priv->show_album_art) {
502
image_width = allocation->height;
504
child_alloc.x = allocation->x + allocation->width - image_width;
505
allocation->x -= image_width + spacing;
507
child_alloc.x = allocation->x;
508
allocation->x += image_width + spacing;
510
allocation->width -= image_width + spacing;
511
child_alloc.y = allocation->y;
512
child_alloc.width = image_width;
513
child_alloc.height = allocation->height;
514
gtk_widget_size_allocate (RB_HEADER (widget)->priv->image, &child_alloc);
519
/* figure out how much space to allocate to the scale.
520
* it gets at least its minimum size, at most 1/3 of the
523
if (RB_HEADER (widget)->priv->show_slider) {
524
gtk_widget_get_preferred_width (RB_HEADER (widget)->priv->scale, &scale_width, NULL);
525
if (scale_width < allocation->width / 3)
526
scale_width = allocation->width / 3;
528
if (scale_width + image_width > allocation->width)
529
scale_width = allocation->width - image_width;
531
if (scale_width > 0) {
533
child_alloc.x = allocation->x;
535
child_alloc.x = allocation->x + (allocation->width - scale_width) + spacing;
537
child_alloc.y = allocation->y;
538
child_alloc.width = scale_width - spacing;
539
child_alloc.height = allocation->height;
540
gtk_widget_show (RB_HEADER (widget)->priv->scale);
541
gtk_widget_size_allocate (RB_HEADER (widget)->priv->scale, &child_alloc);
543
gtk_widget_hide (RB_HEADER (widget)->priv->scale);
549
/* time button gets its minimum size */
550
gtk_widget_get_preferred_width (RB_HEADER (widget)->priv->songbox, NULL, &info_width);
551
gtk_widget_get_preferred_width (RB_HEADER (widget)->priv->timebutton, &time_width, NULL);
553
info_width = allocation->width - (scale_width + time_width) - (2 * spacing);
556
child_alloc.x = allocation->x + allocation->width - info_width;
558
child_alloc.x = allocation->x;
561
if (info_width > 0) {
562
child_alloc.y = allocation->y;
563
child_alloc.width = info_width;
564
child_alloc.height = allocation->height;
565
gtk_widget_show (RB_HEADER (widget)->priv->songbox);
566
gtk_widget_size_allocate (RB_HEADER (widget)->priv->songbox, &child_alloc);
568
gtk_widget_hide (RB_HEADER (widget)->priv->songbox);
572
if (info_width + scale_width + (2 * spacing) + time_width > allocation->width) {
573
gtk_widget_hide (RB_HEADER (widget)->priv->timebutton);
576
child_alloc.x = allocation->x + scale_width + spacing;
578
child_alloc.x = allocation->x + info_width + spacing;
580
child_alloc.y = allocation->y;
581
child_alloc.width = time_width;
582
child_alloc.height = allocation->height;
583
gtk_widget_show (RB_HEADER (widget)->priv->timebutton);
584
gtk_widget_size_allocate (RB_HEADER (widget)->priv->timebutton, &child_alloc);
589
rb_header_set_property (GObject *object,
594
RBHeader *header = RB_HEADER (object);
598
header->priv->db = g_value_get_object (value);
599
g_signal_connect_object (header->priv->db,
600
"entry-extra-metadata-notify",
601
G_CALLBACK (rb_header_extra_metadata_cb),
604
case PROP_SHELL_PLAYER:
605
header->priv->shell_player = g_value_get_object (value);
606
g_signal_connect_object (header->priv->shell_player,
607
"elapsed-nano-changed",
608
G_CALLBACK (rb_header_elapsed_changed_cb),
610
g_signal_connect_object (header->priv->shell_player,
611
"playing-song-changed",
612
G_CALLBACK (rb_header_playing_song_changed_cb),
616
header->priv->seekable = g_value_get_boolean (value);
618
case PROP_SHOW_REMAINING:
619
header->priv->show_remaining = g_value_get_boolean (value);
620
rb_header_update_elapsed (header);
622
case PROP_SHOW_POSITION_SLIDER:
623
header->priv->show_slider = g_value_get_boolean (value);
624
gtk_widget_set_visible (header->priv->scale, header->priv->show_slider);
626
case PROP_SHOW_ALBUM_ART:
627
header->priv->show_album_art = g_value_get_boolean (value);
628
gtk_widget_set_visible (header->priv->image, header->priv->show_album_art);
631
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
637
rb_header_get_property (GObject *object,
642
RBHeader *header = RB_HEADER (object);
646
g_value_set_object (value, header->priv->db);
648
case PROP_SHELL_PLAYER:
649
g_value_set_object (value, header->priv->shell_player);
652
g_value_set_boolean (value, header->priv->seekable);
654
case PROP_SLIDER_DRAGGING:
655
g_value_set_boolean (value, header->priv->slider_dragging);
657
case PROP_SHOW_REMAINING:
658
g_value_set_boolean (value, header->priv->show_remaining);
660
case PROP_SHOW_POSITION_SLIDER:
661
g_value_set_boolean (value, header->priv->show_slider);
663
case PROP_SHOW_ALBUM_ART:
664
g_value_set_boolean (value, header->priv->show_album_art);
667
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
674
* @shell_player: the #RBShellPlayer instance
675
* @db: the #RhythmDB instance
677
* Creates a new header widget.
679
* Return value: the header widget
682
rb_header_new (RBShellPlayer *shell_player, RhythmDB *db)
686
header = RB_HEADER (g_object_new (RB_TYPE_HEADER,
687
"shell-player", shell_player,
691
g_return_val_if_fail (header->priv != NULL, NULL);
697
get_extra_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *field, char **value)
701
v = rhythmdb_entry_request_extra_metadata (db, entry, field);
703
*value = g_value_dup_string (v);
712
rb_header_sync (RBHeader *header)
715
const char *location = "<null>";
717
if (header->priv->entry != NULL) {
718
location = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_LOCATION);
720
rb_debug ("syncing with entry = %s", location);
722
if (header->priv->entry != NULL) {
726
const char *stream_name = NULL;
727
char *streaming_title;
728
char *streaming_artist;
729
char *streaming_album;
730
PangoDirection widget_dir;
732
gboolean have_duration = (header->priv->duration > 0);
734
title = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_TITLE);
735
album = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ALBUM);
736
artist = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ARTIST);
738
get_extra_metadata (header->priv->db,
740
RHYTHMDB_PROP_STREAM_SONG_TITLE,
742
if (streaming_title) {
743
/* use entry title as stream name */
745
title = streaming_title;
748
get_extra_metadata (header->priv->db,
750
RHYTHMDB_PROP_STREAM_SONG_ARTIST,
752
if (streaming_artist) {
753
/* override artist from entry */
754
artist = streaming_artist;
757
get_extra_metadata (header->priv->db,
759
RHYTHMDB_PROP_STREAM_SONG_ALBUM,
761
if (streaming_album) {
762
/* override album from entry */
763
album = streaming_album;
766
widget_dir = (gtk_widget_get_direction (GTK_WIDGET (header->priv->song)) == GTK_TEXT_DIR_LTR) ?
767
PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
770
t = rb_text_cat (widget_dir, title, TITLE_FORMAT, NULL);
771
gtk_label_set_markup (GTK_LABEL (header->priv->song), t);
774
if (artist == NULL || artist[0] == '\0') { /* this is crap; should be up to the entry type */
775
if (stream_name != NULL) {
776
t = rb_text_cat (widget_dir, stream_name, STREAM_FORMAT, NULL);
777
gtk_label_set_markup (GTK_LABEL (header->priv->details), t);
780
gtk_label_set_markup (GTK_LABEL (header->priv->details), "");
786
PangoDirection native;
788
native = PANGO_DIRECTION_LTR;
789
if (gtk_widget_get_direction (GTK_WIDGET (header->priv->details)) != GTK_TEXT_DIR_LTR) {
790
native = PANGO_DIRECTION_RTL;
793
dir = rb_text_common_direction (artist, album, NULL);
794
if (!rb_text_direction_conflict (dir, native)) {
799
by = UNICODE_MIDDLE_DOT;
800
from = UNICODE_MIDDLE_DOT;
803
t = rb_text_cat (dir,
805
artist, ARTIST_FORMAT,
809
gtk_label_set_markup (GTK_LABEL (header->priv->details), t);
813
gtk_widget_set_sensitive (header->priv->scale, have_duration && header->priv->seekable);
814
rb_header_sync_time (header);
816
g_free (streaming_artist);
817
g_free (streaming_album);
818
g_free (streaming_title);
820
rb_debug ("not playing");
821
label_text = g_markup_printf_escaped (TITLE_FORMAT, _("Not Playing"));
822
gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
825
gtk_label_set_text (GTK_LABEL (header->priv->details), "");
827
rb_header_sync_time (header);
832
rb_header_sync_time (RBHeader *header)
834
if (header->priv->shell_player == NULL)
837
if (header->priv->slider_dragging == TRUE) {
838
rb_debug ("slider is dragging, not syncing");
842
if (header->priv->duration > 0) {
843
double progress = ((double) header->priv->elapsed_time) / RB_PLAYER_SECOND;
845
header->priv->slider_locked = TRUE;
847
g_object_freeze_notify (G_OBJECT (header->priv->adjustment));
848
gtk_adjustment_set_value (header->priv->adjustment, progress);
849
gtk_adjustment_set_upper (header->priv->adjustment, header->priv->duration);
850
g_object_thaw_notify (G_OBJECT (header->priv->adjustment));
852
header->priv->slider_locked = FALSE;
853
gtk_widget_set_sensitive (header->priv->scale, header->priv->seekable);
855
header->priv->slider_locked = TRUE;
857
g_object_freeze_notify (G_OBJECT (header->priv->adjustment));
858
gtk_adjustment_set_value (header->priv->adjustment, 0.0);
859
gtk_adjustment_set_upper (header->priv->adjustment, 0.0);
860
g_object_thaw_notify (G_OBJECT (header->priv->adjustment));
862
header->priv->slider_locked = FALSE;
863
gtk_widget_set_sensitive (header->priv->scale, FALSE);
866
rb_header_update_elapsed (header);
870
slider_press_callback (GtkWidget *widget,
871
GdkEventButton *event,
874
header->priv->slider_dragging = TRUE;
875
header->priv->latest_set_time = -1;
876
g_object_notify (G_OBJECT (header), "slider-dragging");
878
/* HACK: we want the behaviour you get with the middle button, so we
879
* mangle the event. clicking with other buttons moves the slider in
880
* step increments, clicking with the middle button moves the slider to
881
* the location of the click.
890
slider_moved_timeout (RBHeader *header)
892
GDK_THREADS_ENTER ();
894
apply_slider_position (header);
895
header->priv->slider_moved_timeout = 0;
897
GDK_THREADS_LEAVE ();
903
slider_moved_callback (GtkWidget *widget,
904
GdkEventMotion *event,
909
if (header->priv->slider_dragging == FALSE) {
910
rb_debug ("slider is not dragging");
914
progress = gtk_adjustment_get_value (header->priv->adjustment);
915
header->priv->elapsed_time = (gint64) ((progress+0.5) * RB_PLAYER_SECOND);
917
rb_header_update_elapsed (header);
919
if (header->priv->slider_moved_timeout != 0) {
920
rb_debug ("removing old timer");
921
g_source_remove (header->priv->slider_moved_timeout);
922
header->priv->slider_moved_timeout = 0;
924
header->priv->slider_moved_timeout =
925
g_timeout_add (40, (GSourceFunc) slider_moved_timeout, header);
931
apply_slider_position (RBHeader *header)
936
progress = gtk_adjustment_get_value (header->priv->adjustment);
937
new = (long) (progress+0.5);
939
if (new != header->priv->latest_set_time) {
940
rb_debug ("setting time to %ld", new);
941
rb_shell_player_set_playing_time (header->priv->shell_player, new, NULL);
942
header->priv->latest_set_time = new;
947
slider_release_callback (GtkWidget *widget,
948
GdkEventButton *event,
951
/* HACK: see slider_press_callback */
954
if (header->priv->slider_dragging == FALSE) {
955
rb_debug ("slider is not dragging");
959
if (header->priv->slider_moved_timeout != 0) {
960
g_source_remove (header->priv->slider_moved_timeout);
961
header->priv->slider_moved_timeout = 0;
964
apply_slider_position (header);
965
header->priv->slider_dragging = FALSE;
966
g_object_notify (G_OBJECT (header), "slider-dragging");
971
slider_changed_callback (GtkWidget *widget,
974
/* if the slider isn't being dragged, and nothing else is happening,
975
* this indicates the position was adjusted with a keypress (page up/page down etc.),
976
* so we should directly apply the change.
978
if (header->priv->slider_dragging == FALSE &&
979
header->priv->slider_locked == FALSE) {
980
apply_slider_position (header);
985
slider_scroll_callback (GtkWidget *widget, GdkEventScroll *event, RBHeader *header)
987
gboolean retval = TRUE;
988
gdouble adj = gtk_adjustment_get_value (header->priv->adjustment);
990
switch (event->direction) {
992
rb_debug ("slider scrolling up");
993
gtk_adjustment_set_value (header->priv->adjustment, adj + SCROLL_UP_SEEK_OFFSET);
996
case GDK_SCROLL_DOWN:
997
rb_debug ("slider scrolling down");
998
gtk_adjustment_set_value (header->priv->adjustment, adj + SCROLL_DOWN_SEEK_OFFSET);
1010
rb_header_update_elapsed (RBHeader *header)
1017
if (header->priv->entry == NULL) {
1018
gtk_label_set_text (GTK_LABEL (header->priv->timelabel), "");
1022
seconds = header->priv->elapsed_time / RB_PLAYER_SECOND;
1023
if (header->priv->duration == 0) {
1024
label = rb_make_time_string (seconds);
1025
gtk_label_set_text (GTK_LABEL (header->priv->timelabel), label);
1027
} else if (header->priv->show_remaining) {
1029
duration = rb_make_time_string (header->priv->duration);
1031
if (seconds > header->priv->duration) {
1032
elapsed = rb_make_time_string (0);
1034
elapsed = rb_make_time_string (header->priv->duration - seconds);
1037
/* Translators: remaining time / total time */
1038
label = g_strdup_printf (_("-%s / %s"), elapsed, duration);
1039
gtk_label_set_text (GTK_LABEL (header->priv->timelabel), label);
1045
elapsed = rb_make_time_string (seconds);
1046
duration = rb_make_time_string (header->priv->duration);
1048
/* Translators: elapsed time / total time */
1049
label = g_strdup_printf (_("%s / %s"), elapsed, duration);
1050
gtk_label_set_text (GTK_LABEL (header->priv->timelabel), label);
1059
rb_header_elapsed_changed_cb (RBShellPlayer *player,
1063
header->priv->elapsed_time = elapsed;
1064
rb_header_sync_time (header);
1068
rb_header_extra_metadata_cb (RhythmDB *db,
1069
RhythmDBEntry *entry,
1070
const char *property_name,
1071
const GValue *metadata,
1074
if (entry != header->priv->entry)
1077
if (g_str_equal (property_name, RHYTHMDB_PROP_STREAM_SONG_TITLE) ||
1078
g_str_equal (property_name, RHYTHMDB_PROP_STREAM_SONG_ARTIST) ||
1079
g_str_equal (property_name, RHYTHMDB_PROP_STREAM_SONG_ALBUM)) {
1080
rb_header_sync (header);
1085
pixbuf_dropped_cb (RBFadingImage *image, GdkPixbuf *pixbuf, RBHeader *header)
1089
GValue v = G_VALUE_INIT;
1091
if (header->priv->entry == NULL || pixbuf == NULL)
1094
/* maybe ignore tiny pixbufs? */
1096
key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ALBUM));
1097
artist = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1098
if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
1099
artist = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ARTIST);
1101
rb_ext_db_key_add_field (key, "artist", artist);
1103
g_value_init (&v, GDK_TYPE_PIXBUF);
1104
g_value_set_object (&v, image);
1105
rb_ext_db_store (header->priv->art_store, key, RB_EXT_DB_SOURCE_USER_EXPLICIT, &v);
1108
rb_ext_db_key_free (key);
1112
uri_dropped_cb (RBFadingImage *image, const char *uri, RBHeader *header)
1117
if (header->priv->entry == NULL || uri == NULL)
1120
/* maybe ignore tiny pixbufs? */
1122
key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ALBUM));
1123
artist = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1124
if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
1125
artist = rhythmdb_entry_get_string (header->priv->entry, RHYTHMDB_PROP_ARTIST);
1127
rb_ext_db_key_add_field (key, "artist", artist);
1129
rb_ext_db_store_uri (header->priv->art_store, key, RB_EXT_DB_SOURCE_USER_EXPLICIT, uri);
1131
rb_ext_db_key_free (key);
1135
image_button_press_cb (GtkWidget *widget, GdkEvent *event, RBHeader *header)
1137
if (event->button.type != GDK_2BUTTON_PRESS ||
1138
event->button.button != 1)
1141
if (header->priv->image_path != NULL) {
1143
GAppLaunchContext *context;
1144
GList *files = NULL;
1146
app = g_app_info_get_default_for_type ("image/jpeg", FALSE);
1151
files = g_list_append (NULL, g_file_new_for_path (header->priv->image_path));
1153
context = G_APP_LAUNCH_CONTEXT (gdk_display_get_app_launch_context (gtk_widget_get_display (widget)));
1154
g_app_info_launch (app, files, context, NULL);
1155
g_object_unref (context);
1156
g_object_unref (app);
1157
g_list_free_full (files, g_object_unref);
1162
time_button_clicked_cb (GtkWidget *widget, RBHeader *header)
1164
g_object_set (header, "show-remaining", !header->priv->show_remaining, NULL);