1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of main song information display widget
5 * Copyright (C) 2002, 2003 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003 Colin Walters <walters@gnome.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
29 #include <glib/gi18n.h>
32 #include "rb-stock-icons.h"
33 #include "rb-header.h"
35 #include "rb-preferences.h"
36 #include "rb-shell-player.h"
37 #include "eel-gconf-extensions.h"
41 static void rb_header_class_init (RBHeaderClass
*klass
);
42 static void rb_header_init (RBHeader
*header
);
43 static void rb_header_finalize (GObject
*object
);
44 static void rb_header_set_property (GObject
*object
,
48 static void rb_header_get_property (GObject
*object
,
52 static void rb_header_set_show_timeline (RBHeader
*header
,
54 static void rb_header_update_elapsed (RBHeader
*header
);
55 static gboolean
slider_press_callback (GtkWidget
*widget
, GdkEventButton
*event
, RBHeader
*header
);
56 static gboolean
slider_moved_callback (GtkWidget
*widget
, GdkEventMotion
*event
, RBHeader
*header
);
57 static gboolean
slider_release_callback (GtkWidget
*widget
, GdkEventButton
*event
, RBHeader
*header
);
58 static void slider_changed_callback (GtkWidget
*widget
, RBHeader
*header
);
60 static void rb_header_elapsed_changed_cb (RBShellPlayer
*player
, guint elapsed
, RBHeader
*header
);
62 struct RBHeaderPrivate
67 RBShellPlayer
*shell_player
;
74 gboolean scaleline_shown
;
77 GtkAdjustment
*adjustment
;
78 gboolean slider_dragging
;
79 gboolean slider_locked
;
80 guint slider_moved_timeout
;
82 guint value_changed_update_handler
;
99 #define TITLE_MARKUP(xTITLE) g_markup_printf_escaped ("<big><b>%s</b></big>", xTITLE)
100 #define ALBUM_MARKUP(xALBUM) g_markup_printf_escaped (" %s <i>%s</i>", _("from"), xALBUM)
101 #define ARTIST_MARKUP(xARTIST) g_markup_printf_escaped (" %s <i>%s</i>", _("by"), xARTIST)
102 #define STREAM_MARKUP(xSTREAM) g_markup_printf_escaped (" (%s)", xSTREAM)
104 G_DEFINE_TYPE (RBHeader
, rb_header
, GTK_TYPE_HBOX
)
107 rb_header_class_init (RBHeaderClass
*klass
)
109 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
111 object_class
->finalize
= rb_header_finalize
;
113 object_class
->set_property
= rb_header_set_property
;
114 object_class
->get_property
= rb_header_get_property
;
116 g_object_class_install_property (object_class
,
118 g_param_spec_object ("db",
124 g_object_class_install_property (object_class
,
126 g_param_spec_boxed ("entry",
128 "RhythmDBEntry pointer",
131 g_object_class_install_property (object_class
,
133 g_param_spec_object ("shell-player",
135 "RBShellPlayer object",
136 RB_TYPE_SHELL_PLAYER
,
137 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
138 g_object_class_install_property (object_class
,
140 g_param_spec_boolean ("seekable",
146 g_type_class_add_private (klass
, sizeof (RBHeaderPrivate
));
150 rb_header_init (RBHeader
*header
)
153 * The children in this widget look like this:
156 * GtkLabel (priv->song)
157 * GtkHBox (priv->timeline)
158 * GtkHScale (priv->scale)
160 * GtkLabel (priv->elapsed)
165 header
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (header
, RB_TYPE_HEADER
, RBHeaderPrivate
);
167 gtk_box_set_spacing (GTK_BOX (header
), 3);
169 vbox
= gtk_vbox_new (FALSE
, 6);
170 gtk_widget_show (vbox
);
171 gtk_box_pack_start (GTK_BOX (header
), vbox
, TRUE
, TRUE
, 0);
174 hbox
= gtk_hbox_new (FALSE
, 16);
175 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
176 gtk_widget_show (hbox
);
178 header
->priv
->song
= gtk_label_new ("");
179 gtk_label_set_use_markup (GTK_LABEL (header
->priv
->song
), TRUE
);
180 gtk_label_set_selectable (GTK_LABEL (header
->priv
->song
), TRUE
);
181 gtk_label_set_ellipsize (GTK_LABEL (header
->priv
->song
), PANGO_ELLIPSIZE_END
);
182 gtk_box_pack_start (GTK_BOX (hbox
), header
->priv
->song
, TRUE
, TRUE
, 0);
183 gtk_widget_show (header
->priv
->song
);
185 /* construct the time display */
186 header
->priv
->timeline
= gtk_hbox_new (FALSE
, 3);
187 header
->priv
->elapsed
= gtk_label_new ("");
189 gtk_misc_set_padding (GTK_MISC (header
->priv
->elapsed
), 2, 0);
190 gtk_box_pack_start (GTK_BOX (header
->priv
->timeline
), header
->priv
->elapsed
, FALSE
, FALSE
, 0);
191 gtk_widget_set_sensitive (header
->priv
->timeline
, FALSE
);
192 gtk_box_pack_end (GTK_BOX (hbox
), header
->priv
->timeline
, FALSE
, FALSE
, 0);
193 gtk_widget_show_all (header
->priv
->timeline
);
195 /* row for the position slider */
196 header
->priv
->scaleline
= gtk_hbox_new (FALSE
, 3);
197 gtk_box_pack_start (GTK_BOX (vbox
), header
->priv
->scaleline
, FALSE
, FALSE
, 0);
198 header
->priv
->scaleline_shown
= FALSE
;
200 header
->priv
->adjustment
= GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 10.0, 1.0, 10.0, 0.0));
201 header
->priv
->scale
= gtk_hscale_new (header
->priv
->adjustment
);
202 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
203 "button_press_event",
204 G_CALLBACK (slider_press_callback
),
206 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
207 "button_release_event",
208 G_CALLBACK (slider_release_callback
),
210 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
211 "motion_notify_event",
212 G_CALLBACK (slider_moved_callback
),
214 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
216 G_CALLBACK (slider_changed_callback
),
218 gtk_scale_set_draw_value (GTK_SCALE (header
->priv
->scale
), FALSE
);
219 gtk_widget_set_size_request (header
->priv
->scale
, 150, -1);
220 gtk_box_pack_start (GTK_BOX (header
->priv
->scaleline
), header
->priv
->scale
, TRUE
, TRUE
, 0);
224 rb_header_finalize (GObject
*object
)
228 g_return_if_fail (object
!= NULL
);
229 g_return_if_fail (RB_IS_HEADER (object
));
231 header
= RB_HEADER (object
);
232 g_return_if_fail (header
->priv
!= NULL
);
234 G_OBJECT_CLASS (rb_header_parent_class
)->finalize (object
);
238 rb_header_set_property (GObject
*object
,
243 RBHeader
*header
= RB_HEADER (object
);
247 header
->priv
->db
= g_value_get_object (value
);
250 header
->priv
->entry
= g_value_get_boxed (value
);
251 if (header
->priv
->entry
) {
252 header
->priv
->duration
= rhythmdb_entry_get_ulong (header
->priv
->entry
,
253 RHYTHMDB_PROP_DURATION
);
255 header
->priv
->duration
= -1;
258 case PROP_SHELL_PLAYER
:
259 header
->priv
->shell_player
= g_value_get_object (value
);
260 g_signal_connect (G_OBJECT (header
->priv
->shell_player
),
262 (GCallback
) rb_header_elapsed_changed_cb
,
266 header
->priv
->seekable
= g_value_get_boolean (value
);
269 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
275 rb_header_get_property (GObject
*object
,
280 RBHeader
*header
= RB_HEADER (object
);
284 g_value_set_object (value
, header
->priv
->db
);
287 g_value_set_boxed (value
, header
->priv
->entry
);
289 case PROP_SHELL_PLAYER
:
290 g_value_set_object (value
, header
->priv
->shell_player
);
293 g_value_set_boolean (value
, header
->priv
->seekable
);
296 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
302 rb_header_new (RBShellPlayer
*shell_player
, RhythmDB
*db
)
306 header
= RB_HEADER (g_object_new (RB_TYPE_HEADER
,
307 "shell-player", shell_player
,
309 "spacing", 6, NULL
));
311 g_return_val_if_fail (header
->priv
!= NULL
, NULL
);
317 rb_header_set_playing_entry (RBHeader
*header
,
318 RhythmDBEntry
*entry
,
321 g_object_set (header
,
323 "seekable", seekable
,
328 append_and_free (GString
*str
,
331 g_string_append (str
, text
);
336 get_extra_metadata (RhythmDB
*db
, RhythmDBEntry
*entry
, const char *field
, char **value
)
340 v
= rhythmdb_entry_request_extra_metadata (db
, entry
, field
);
342 *value
= g_value_dup_string (v
);
351 rb_header_sync (RBHeader
*header
)
355 rb_debug ("syncing with entry = %p", header
->priv
->entry
);
357 if (header
->priv
->entry
!= NULL
) {
361 const char *stream_name
= NULL
;
362 char *streaming_title
;
363 char *streaming_artist
;
364 char *streaming_album
;
367 gboolean have_duration
= (header
->priv
->duration
> 0);
369 title
= rhythmdb_entry_get_string (header
->priv
->entry
, RHYTHMDB_PROP_TITLE
);
370 album
= rhythmdb_entry_get_string (header
->priv
->entry
, RHYTHMDB_PROP_ALBUM
);
371 artist
= rhythmdb_entry_get_string (header
->priv
->entry
, RHYTHMDB_PROP_ARTIST
);
373 get_extra_metadata (header
->priv
->db
,
375 RHYTHMDB_PROP_STREAM_SONG_TITLE
,
377 if (streaming_title
) {
378 /* use entry title as stream name */
380 title
= streaming_title
;
383 get_extra_metadata (header
->priv
->db
,
385 RHYTHMDB_PROP_STREAM_SONG_ARTIST
,
387 if (streaming_artist
) {
388 /* override artist from entry */
389 artist
= streaming_artist
;
392 get_extra_metadata (header
->priv
->db
,
394 RHYTHMDB_PROP_STREAM_SONG_ALBUM
,
396 if (streaming_album
) {
397 /* override album from entry */
398 album
= streaming_album
;
401 label_str
= g_string_sized_new (100);
403 append_and_free (label_str
, TITLE_MARKUP (title
));
405 if (artist
!= NULL
&& artist
[0] != '\0')
406 append_and_free (label_str
, ARTIST_MARKUP (artist
));
408 if (album
!= NULL
&& album
[0] != '\0')
409 append_and_free (label_str
, ALBUM_MARKUP (album
));
411 if (stream_name
&& stream_name
[0] != '\0')
412 append_and_free (label_str
, STREAM_MARKUP (stream_name
));
414 label_text
= g_string_free (label_str
, FALSE
);
415 gtk_label_set_markup (GTK_LABEL (header
->priv
->song
), label_text
);
418 rb_header_set_show_timeline (header
, have_duration
&& header
->priv
->seekable
);
420 rb_header_sync_time (header
);
422 g_free (streaming_artist
);
423 g_free (streaming_album
);
424 g_free (streaming_title
);
428 rb_debug ("not playing");
429 label_text
= TITLE_MARKUP (_("Not Playing"));
430 gtk_label_set_markup (GTK_LABEL (header
->priv
->song
), label_text
);
433 rb_header_set_show_timeline (header
, FALSE
);
435 header
->priv
->slider_locked
= TRUE
;
436 gtk_adjustment_set_value (header
->priv
->adjustment
, 0.0);
437 header
->priv
->slider_locked
= FALSE
;
438 gtk_widget_set_sensitive (header
->priv
->scale
, FALSE
);
440 tmp
= rb_make_elapsed_time_string (0, 0, !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY
));
441 gtk_label_set_text (GTK_LABEL (header
->priv
->elapsed
), tmp
);
447 rb_header_set_show_position_slider (RBHeader
*header
,
450 if (header
->priv
->scaleline_shown
== show
)
453 header
->priv
->scaleline_shown
= show
;
456 gtk_widget_show_all (GTK_WIDGET (header
->priv
->scaleline
));
457 rb_header_sync_time (header
);
459 gtk_widget_hide (GTK_WIDGET (header
->priv
->scaleline
));
464 rb_header_set_show_timeline (RBHeader
*header
,
467 gtk_widget_set_sensitive (header
->priv
->timeline
, show
);
468 gtk_widget_set_sensitive (header
->priv
->scaleline
, show
);
472 rb_header_sync_time (RBHeader
*header
)
476 if (header
->priv
->shell_player
== NULL
)
479 if (header
->priv
->slider_dragging
== TRUE
) {
480 rb_debug ("slider is dragging, not syncing");
484 seconds
= header
->priv
->elapsed_time
;
486 if (header
->priv
->duration
> -1) {
487 double progress
= 0.0;
490 progress
= (double) (long) seconds
;
492 header
->priv
->adjustment
->upper
= header
->priv
->duration
;
493 g_signal_emit_by_name (G_OBJECT (header
->priv
->adjustment
), "changed");
496 header
->priv
->slider_locked
= TRUE
;
497 gtk_adjustment_set_value (header
->priv
->adjustment
, progress
);
498 header
->priv
->slider_locked
= FALSE
;
499 gtk_widget_set_sensitive (header
->priv
->scale
, header
->priv
->seekable
);
501 header
->priv
->slider_locked
= TRUE
;
502 gtk_adjustment_set_value (header
->priv
->adjustment
, 0.0);
503 header
->priv
->slider_locked
= FALSE
;
504 gtk_widget_set_sensitive (header
->priv
->scale
, FALSE
);
507 rb_header_update_elapsed (header
);
513 slider_press_callback (GtkWidget
*widget
,
514 GdkEventButton
*event
,
517 header
->priv
->slider_dragging
= TRUE
;
518 header
->priv
->latest_set_time
= -1;
523 slider_moved_timeout (RBHeader
*header
)
528 GDK_THREADS_ENTER ();
530 progress
= gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (header
->priv
->scale
)));
531 new = (long) (progress
+0.5);
533 rb_debug ("setting time to %ld", new);
534 rb_shell_player_set_playing_time (header
->priv
->shell_player
, new, NULL
);
536 header
->priv
->latest_set_time
= new;
537 header
->priv
->slider_moved_timeout
= 0;
539 GDK_THREADS_LEAVE ();
545 slider_moved_callback (GtkWidget
*widget
,
546 GdkEventMotion
*event
,
549 GtkAdjustment
*adjustment
;
552 if (header
->priv
->slider_dragging
== FALSE
) {
553 rb_debug ("slider is not dragging");
557 adjustment
= gtk_range_get_adjustment (GTK_RANGE (widget
));
559 progress
= gtk_adjustment_get_value (adjustment
);
560 header
->priv
->elapsed_time
= (long) (progress
+0.5);
562 rb_header_update_elapsed (header
);
564 if (header
->priv
->slider_moved_timeout
!= 0) {
565 rb_debug ("removing old timer");
566 g_source_remove (header
->priv
->slider_moved_timeout
);
567 header
->priv
->slider_moved_timeout
= 0;
569 header
->priv
->slider_moved_timeout
=
570 g_timeout_add (40, (GSourceFunc
) slider_moved_timeout
, header
);
576 slider_release_callback (GtkWidget
*widget
,
577 GdkEventButton
*event
,
582 GtkAdjustment
*adjustment
;
584 if (header
->priv
->slider_dragging
== FALSE
) {
585 rb_debug ("slider is not dragging");
589 adjustment
= gtk_range_get_adjustment (GTK_RANGE (widget
));
591 progress
= gtk_adjustment_get_value (adjustment
);
592 new = (long) (progress
+0.5);
594 if (new != header
->priv
->latest_set_time
) {
595 rb_debug ("setting time to %ld", new);
596 rb_shell_player_set_playing_time (header
->priv
->shell_player
, new, NULL
);
599 header
->priv
->slider_dragging
= FALSE
;
601 rb_header_sync_time (header
);
603 if (header
->priv
->slider_moved_timeout
!= 0) {
604 g_source_remove (header
->priv
->slider_moved_timeout
);
605 header
->priv
->slider_moved_timeout
= 0;
612 changed_idle_callback (RBHeader
*header
)
614 GDK_THREADS_ENTER ();
616 slider_release_callback (header
->priv
->scale
, NULL
, header
);
618 header
->priv
->value_changed_update_handler
= 0;
619 rb_debug ("in changed_idle_callback");
621 GDK_THREADS_LEAVE ();
627 slider_changed_callback (GtkWidget
*widget
,
630 if (header
->priv
->slider_dragging
== FALSE
&&
631 header
->priv
->slider_locked
== FALSE
&&
632 header
->priv
->value_changed_update_handler
== 0) {
633 header
->priv
->slider_dragging
= TRUE
;
634 header
->priv
->value_changed_update_handler
=
635 g_idle_add ((GSourceFunc
) changed_idle_callback
, header
);
640 rb_header_update_elapsed (RBHeader
*header
)
645 if ((header
->priv
->elapsed_time
> header
->priv
->duration
) || (header
->priv
->elapsed_time
< 0))
648 elapsed_text
= rb_make_elapsed_time_string (header
->priv
->elapsed_time
,
649 header
->priv
->duration
,
650 !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY
));
652 gtk_label_set_text (GTK_LABEL (header
->priv
->elapsed
), elapsed_text
);
653 g_free (elapsed_text
);
657 rb_header_elapsed_changed_cb (RBShellPlayer
*player
,
661 header
->priv
->elapsed_time
= elapsed
;
662 rb_header_sync_time (header
);