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"
40 static void rb_header_class_init (RBHeaderClass
*klass
);
41 static void rb_header_init (RBHeader
*header
);
42 static void rb_header_finalize (GObject
*object
);
43 static void rb_header_set_property (GObject
*object
,
47 static void rb_header_get_property (GObject
*object
,
51 static void rb_header_set_show_timeline (RBHeader
*header
,
53 static void rb_header_update_elapsed (RBHeader
*header
);
54 static gboolean
slider_press_callback (GtkWidget
*widget
, GdkEventButton
*event
, RBHeader
*header
);
55 static gboolean
slider_moved_callback (GtkWidget
*widget
, GdkEventMotion
*event
, RBHeader
*header
);
56 static gboolean
slider_release_callback (GtkWidget
*widget
, GdkEventButton
*event
, RBHeader
*header
);
57 static void slider_changed_callback (GtkWidget
*widget
, RBHeader
*header
);
59 static void rb_header_elapsed_changed_cb (RBShellPlayer
*player
, guint elapsed
, RBHeader
*header
);
61 struct RBHeaderPrivate
68 RBShellPlayer
*shell_player
;
75 gboolean scaleline_shown
;
78 GtkAdjustment
*adjustment
;
79 gboolean slider_dragging
;
80 gboolean slider_locked
;
81 guint slider_moved_timeout
;
83 guint value_changed_update_handler
;
99 #define SONG_MARKUP(xSONG) g_markup_printf_escaped ("<big><b>%s</b></big>", xSONG);
100 #define SONG_MARKUP_ALBUM_ARTIST(xSONG, xALBUM, xARTIST) g_markup_printf_escaped ("<big><b>%s</b></big> %s <i>%s</i> %s <i>%s</i>", xSONG, _("by"), xARTIST, _("from"), xALBUM);
102 G_DEFINE_TYPE (RBHeader
, rb_header
, GTK_TYPE_HBOX
)
106 rb_header_class_init (RBHeaderClass
*klass
)
108 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
110 object_class
->finalize
= rb_header_finalize
;
112 object_class
->set_property
= rb_header_set_property
;
113 object_class
->get_property
= rb_header_get_property
;
115 g_object_class_install_property (object_class
,
117 g_param_spec_object ("db",
123 g_object_class_install_property (object_class
,
125 g_param_spec_pointer ("entry",
127 "RhythmDBEntry pointer",
129 g_object_class_install_property (object_class
,
131 g_param_spec_object ("shell-player",
133 "RBShellPlayer object",
134 RB_TYPE_SHELL_PLAYER
,
135 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
136 g_object_class_install_property (object_class
,
138 g_param_spec_string ("title",
144 g_type_class_add_private (klass
, sizeof (RBHeaderPrivate
));
148 rb_header_init (RBHeader
*header
)
151 * The children in this widget look like this:
154 * GtkLabel (priv->song)
155 * GtkHBox (priv->timeline)
156 * GtkHScale (priv->scale)
158 * GtkLabel (priv->elapsed)
163 header
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (header
, RB_TYPE_HEADER
, RBHeaderPrivate
);
165 gtk_box_set_spacing (GTK_BOX (header
), 3);
167 vbox
= gtk_vbox_new (FALSE
, 6);
168 gtk_widget_show (vbox
);
169 gtk_box_pack_start (GTK_BOX (header
), vbox
, TRUE
, TRUE
, 0);
172 hbox
= gtk_hbox_new (FALSE
, 16);
173 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
174 gtk_widget_show (hbox
);
176 header
->priv
->song
= gtk_label_new ("");
177 gtk_label_set_use_markup (GTK_LABEL (header
->priv
->song
), TRUE
);
178 gtk_label_set_selectable (GTK_LABEL (header
->priv
->song
), TRUE
);
179 gtk_label_set_ellipsize (GTK_LABEL (header
->priv
->song
), PANGO_ELLIPSIZE_END
);
180 gtk_box_pack_start (GTK_BOX (hbox
), header
->priv
->song
, TRUE
, TRUE
, 0);
181 gtk_widget_show (header
->priv
->song
);
183 /* construct the time display */
184 header
->priv
->timeline
= gtk_hbox_new (FALSE
, 3);
185 header
->priv
->elapsed
= gtk_label_new ("");
187 gtk_misc_set_padding (GTK_MISC (header
->priv
->elapsed
), 2, 0);
188 gtk_box_pack_start (GTK_BOX (header
->priv
->timeline
), header
->priv
->elapsed
, FALSE
, FALSE
, 0);
189 gtk_widget_set_sensitive (header
->priv
->timeline
, FALSE
);
190 gtk_box_pack_end (GTK_BOX (hbox
), header
->priv
->timeline
, FALSE
, FALSE
, 0);
191 gtk_widget_show_all (header
->priv
->timeline
);
193 /* row for the position slider */
194 header
->priv
->scaleline
= gtk_hbox_new (FALSE
, 3);
195 gtk_box_pack_start (GTK_BOX (vbox
), header
->priv
->scaleline
, FALSE
, FALSE
, 0);
196 header
->priv
->scaleline_shown
= FALSE
;
198 header
->priv
->adjustment
= GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 10.0, 1.0, 10.0, 0.0));
199 header
->priv
->scale
= gtk_hscale_new (header
->priv
->adjustment
);
200 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
201 "button_press_event",
202 G_CALLBACK (slider_press_callback
),
204 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
205 "button_release_event",
206 G_CALLBACK (slider_release_callback
),
208 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
209 "motion_notify_event",
210 G_CALLBACK (slider_moved_callback
),
212 g_signal_connect_object (G_OBJECT (header
->priv
->scale
),
214 G_CALLBACK (slider_changed_callback
),
216 gtk_scale_set_draw_value (GTK_SCALE (header
->priv
->scale
), FALSE
);
217 gtk_widget_set_size_request (header
->priv
->scale
, 150, -1);
218 gtk_box_pack_start (GTK_BOX (header
->priv
->scaleline
), header
->priv
->scale
, TRUE
, TRUE
, 0);
222 rb_header_finalize (GObject
*object
)
226 g_return_if_fail (object
!= NULL
);
227 g_return_if_fail (RB_IS_HEADER (object
));
229 header
= RB_HEADER (object
);
230 g_return_if_fail (header
->priv
!= NULL
);
232 G_OBJECT_CLASS (rb_header_parent_class
)->finalize (object
);
236 rb_header_set_property (GObject
*object
,
241 RBHeader
*header
= RB_HEADER (object
);
245 header
->priv
->db
= g_value_get_object (value
);
248 header
->priv
->entry
= g_value_get_pointer (value
);
249 if (header
->priv
->entry
) {
250 header
->priv
->duration
= rhythmdb_entry_get_ulong (header
->priv
->entry
,
251 RHYTHMDB_PROP_DURATION
);
253 header
->priv
->duration
= -1;
256 case PROP_SHELL_PLAYER
:
257 header
->priv
->shell_player
= g_value_get_object (value
);
258 g_signal_connect (G_OBJECT (header
->priv
->shell_player
),
260 (GCallback
) rb_header_elapsed_changed_cb
,
264 g_free (header
->priv
->title
);
265 header
->priv
->title
= g_value_dup_string (value
);
268 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
274 rb_header_get_property (GObject
*object
,
279 RBHeader
*header
= RB_HEADER (object
);
283 g_value_set_object (value
, header
->priv
->db
);
286 g_value_set_object (value
, header
->priv
->entry
);
288 case PROP_SHELL_PLAYER
:
289 g_value_set_object (value
, header
->priv
->shell_player
);
292 g_value_set_string (value
, header
->priv
->title
);
295 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
301 rb_header_new (RBShellPlayer
*shell_player
)
305 header
= RB_HEADER (g_object_new (RB_TYPE_HEADER
, "shell-player", shell_player
,
307 "spacing", 6, NULL
));
309 g_return_val_if_fail (header
->priv
!= NULL
, NULL
);
315 rb_header_set_playing_entry (RBHeader
*header
, RhythmDBEntry
*entry
)
317 g_object_set (G_OBJECT (header
), "entry", entry
, NULL
);
321 rb_header_set_title (RBHeader
*header
, const char *title
)
323 g_object_set (G_OBJECT (header
), "title", title
, NULL
);
327 rb_header_sync (RBHeader
*header
)
331 rb_debug ("syncing with entry = %p", header
->priv
->entry
);
333 if (header
->priv
->entry
!= NULL
) {
334 const char *song
= header
->priv
->title
;
338 gboolean have_duration
= (header
->priv
->duration
> 0);
340 album
= rhythmdb_entry_get_string (header
->priv
->entry
, RHYTHMDB_PROP_ALBUM
);
341 artist
= rhythmdb_entry_get_string (header
->priv
->entry
, RHYTHMDB_PROP_ARTIST
);
343 /* check for artist and album */
344 if ((album
!= NULL
&& artist
!= NULL
)
345 && (strlen (album
) > 0 && strlen (artist
) > 0)) {
346 label_text
= SONG_MARKUP_ALBUM_ARTIST (song
, album
, artist
);
348 label_text
= SONG_MARKUP (song
);
351 gtk_label_set_markup (GTK_LABEL (header
->priv
->song
), label_text
);
354 rb_header_set_show_timeline (header
, have_duration
);
356 rb_header_sync_time (header
);
360 rb_debug ("not playing");
361 label_text
= SONG_MARKUP (_("Not Playing"));
362 gtk_label_set_markup (GTK_LABEL (header
->priv
->song
), label_text
);
365 rb_header_set_show_timeline (header
, FALSE
);
367 header
->priv
->slider_locked
= TRUE
;
368 gtk_adjustment_set_value (header
->priv
->adjustment
, 0.0);
369 header
->priv
->slider_locked
= FALSE
;
370 gtk_widget_set_sensitive (header
->priv
->scale
, FALSE
);
372 tmp
= rb_make_elapsed_time_string (0, 0, !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY
));
373 gtk_label_set_text (GTK_LABEL (header
->priv
->elapsed
), tmp
);
379 rb_header_set_show_position_slider (RBHeader
*header
,
382 if (header
->priv
->scaleline_shown
== show
)
385 header
->priv
->scaleline_shown
= show
;
388 gtk_widget_show_all (GTK_WIDGET (header
->priv
->scaleline
));
389 rb_header_sync_time (header
);
391 gtk_widget_hide (GTK_WIDGET (header
->priv
->scaleline
));
396 rb_header_set_show_timeline (RBHeader
*header
,
399 gtk_widget_set_sensitive (header
->priv
->timeline
, show
);
400 gtk_widget_set_sensitive (header
->priv
->scaleline
, show
);
404 rb_header_sync_time (RBHeader
*header
)
408 if (header
->priv
->shell_player
== NULL
)
411 if (header
->priv
->slider_dragging
== TRUE
) {
412 rb_debug ("slider is dragging, not syncing");
416 seconds
= header
->priv
->elapsed_time
;
418 if (header
->priv
->duration
> -1) {
419 double progress
= 0.0;
422 progress
= (double) (long) seconds
;
424 header
->priv
->adjustment
->upper
= header
->priv
->duration
;
425 g_signal_emit_by_name (G_OBJECT (header
->priv
->adjustment
), "changed");
428 header
->priv
->slider_locked
= TRUE
;
429 gtk_adjustment_set_value (header
->priv
->adjustment
, progress
);
430 header
->priv
->slider_locked
= FALSE
;
431 gtk_widget_set_sensitive (header
->priv
->scale
, TRUE
);
433 header
->priv
->slider_locked
= TRUE
;
434 gtk_adjustment_set_value (header
->priv
->adjustment
, 0.0);
435 header
->priv
->slider_locked
= FALSE
;
436 gtk_widget_set_sensitive (header
->priv
->scale
, FALSE
);
439 rb_header_update_elapsed (header
);
445 slider_press_callback (GtkWidget
*widget
,
446 GdkEventButton
*event
,
449 header
->priv
->slider_dragging
= TRUE
;
450 header
->priv
->latest_set_time
= -1;
455 slider_moved_timeout (RBHeader
*header
)
460 GDK_THREADS_ENTER ();
462 progress
= gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (header
->priv
->scale
)));
463 new = (long) (progress
+0.5);
465 rb_debug ("setting time to %ld", new);
466 rb_shell_player_set_playing_time (header
->priv
->shell_player
, new, NULL
);
468 header
->priv
->latest_set_time
= new;
469 header
->priv
->slider_moved_timeout
= 0;
471 GDK_THREADS_LEAVE ();
477 slider_moved_callback (GtkWidget
*widget
,
478 GdkEventMotion
*event
,
481 GtkAdjustment
*adjustment
;
484 if (header
->priv
->slider_dragging
== FALSE
) {
485 rb_debug ("slider is not dragging");
489 adjustment
= gtk_range_get_adjustment (GTK_RANGE (widget
));
491 progress
= gtk_adjustment_get_value (adjustment
);
492 header
->priv
->elapsed_time
= (long) (progress
+0.5);
494 rb_header_update_elapsed (header
);
496 if (header
->priv
->slider_moved_timeout
!= 0) {
497 rb_debug ("removing old timer");
498 g_source_remove (header
->priv
->slider_moved_timeout
);
499 header
->priv
->slider_moved_timeout
= 0;
501 header
->priv
->slider_moved_timeout
=
502 g_timeout_add (40, (GSourceFunc
) slider_moved_timeout
, header
);
508 slider_release_callback (GtkWidget
*widget
,
509 GdkEventButton
*event
,
514 GtkAdjustment
*adjustment
;
516 if (header
->priv
->slider_dragging
== FALSE
) {
517 rb_debug ("slider is not dragging");
521 adjustment
= gtk_range_get_adjustment (GTK_RANGE (widget
));
523 progress
= gtk_adjustment_get_value (adjustment
);
524 new = (long) (progress
+0.5);
526 if (new != header
->priv
->latest_set_time
) {
527 rb_debug ("setting time to %ld", new);
528 rb_shell_player_set_playing_time (header
->priv
->shell_player
, new, NULL
);
531 header
->priv
->slider_dragging
= FALSE
;
533 rb_header_sync_time (header
);
535 if (header
->priv
->slider_moved_timeout
!= 0) {
536 g_source_remove (header
->priv
->slider_moved_timeout
);
537 header
->priv
->slider_moved_timeout
= 0;
544 changed_idle_callback (RBHeader
*header
)
546 GDK_THREADS_ENTER ();
548 slider_release_callback (header
->priv
->scale
, NULL
, header
);
550 header
->priv
->value_changed_update_handler
= 0;
551 rb_debug ("in changed_idle_callback");
553 GDK_THREADS_LEAVE ();
559 slider_changed_callback (GtkWidget
*widget
,
562 if (header
->priv
->slider_dragging
== FALSE
&&
563 header
->priv
->slider_locked
== FALSE
&&
564 header
->priv
->value_changed_update_handler
== 0) {
565 header
->priv
->slider_dragging
= TRUE
;
566 header
->priv
->value_changed_update_handler
=
567 g_idle_add ((GSourceFunc
) changed_idle_callback
, header
);
572 rb_header_update_elapsed (RBHeader
*header
)
577 if ((header
->priv
->elapsed_time
> header
->priv
->duration
) || (header
->priv
->elapsed_time
< 0))
580 elapsed_text
= rb_make_elapsed_time_string (header
->priv
->elapsed_time
,
581 header
->priv
->duration
,
582 !eel_gconf_get_boolean (CONF_UI_TIME_DISPLAY
));
584 gtk_label_set_text (GTK_LABEL (header
->priv
->elapsed
), elapsed_text
);
585 g_free (elapsed_text
);
590 rb_header_elapsed_changed_cb (RBShellPlayer
*player
,
594 header
->priv
->elapsed_time
= elapsed
;
595 rb_header_sync_time (header
);