udapted vi.po
[rhythmbox.git] / widgets / rb-header.c
blob4bb6df082ab107d1ccdc255e705a5f44fe8a1fa3
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.
24 #include <config.h>
26 #include <math.h>
27 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
32 #include "rb-stock-icons.h"
33 #include "rb-header.h"
34 #include "rb-debug.h"
35 #include "rb-preferences.h"
36 #include "rb-shell-player.h"
37 #include "eel-gconf-extensions.h"
38 #include "rb-util.h"
39 #include "rhythmdb.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,
45 guint prop_id,
46 const GValue *value,
47 GParamSpec *pspec);
48 static void rb_header_get_property (GObject *object,
49 guint prop_id,
50 GValue *value,
51 GParamSpec *pspec);
52 static void rb_header_set_show_timeline (RBHeader *header,
53 gboolean show);
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
64 RhythmDB *db;
65 RhythmDBEntry *entry;
67 RBShellPlayer *shell_player;
69 GtkWidget *image;
70 GtkWidget *song;
72 GtkWidget *timeline;
73 GtkWidget *scaleline;
74 gboolean scaleline_shown;
76 GtkWidget *scale;
77 GtkAdjustment *adjustment;
78 gboolean slider_dragging;
79 gboolean slider_locked;
80 guint slider_moved_timeout;
81 long latest_set_time;
82 guint value_changed_update_handler;
83 GtkWidget *elapsed;
85 guint elapsed_time;
86 long duration;
87 gboolean seekable;
90 enum
92 PROP_0,
93 PROP_DB,
94 PROP_SHELL_PLAYER,
95 PROP_ENTRY,
96 PROP_SEEKABLE,
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)
106 static void
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,
117 PROP_DB,
118 g_param_spec_object ("db",
119 "RhythmDB",
120 "RhythmDB object",
121 RHYTHMDB_TYPE,
122 G_PARAM_READWRITE));
124 g_object_class_install_property (object_class,
125 PROP_ENTRY,
126 g_param_spec_boxed ("entry",
127 "RhythmDBEntry",
128 "RhythmDBEntry pointer",
129 RHYTHMDB_TYPE_ENTRY,
130 G_PARAM_READWRITE));
131 g_object_class_install_property (object_class,
132 PROP_SHELL_PLAYER,
133 g_param_spec_object ("shell-player",
134 "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,
139 PROP_SEEKABLE,
140 g_param_spec_boolean ("seekable",
141 "seekable",
142 "seekable",
143 TRUE,
144 G_PARAM_READWRITE));
146 g_type_class_add_private (klass, sizeof (RBHeaderPrivate));
149 static void
150 rb_header_init (RBHeader *header)
153 * The children in this widget look like this:
154 * RBHeader
155 * GtkHBox
156 * GtkLabel (priv->song)
157 * GtkHBox (priv->timeline)
158 * GtkHScale (priv->scale)
159 * GtkAlignment
160 * GtkLabel (priv->elapsed)
162 GtkWidget *hbox;
163 GtkWidget *vbox;
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);
173 /* song info */
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),
205 header, 0);
206 g_signal_connect_object (G_OBJECT (header->priv->scale),
207 "button_release_event",
208 G_CALLBACK (slider_release_callback),
209 header, 0);
210 g_signal_connect_object (G_OBJECT (header->priv->scale),
211 "motion_notify_event",
212 G_CALLBACK (slider_moved_callback),
213 header, 0);
214 g_signal_connect_object (G_OBJECT (header->priv->scale),
215 "value_changed",
216 G_CALLBACK (slider_changed_callback),
217 header, 0);
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);
223 static void
224 rb_header_finalize (GObject *object)
226 RBHeader *header;
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);
237 static void
238 rb_header_set_property (GObject *object,
239 guint prop_id,
240 const GValue *value,
241 GParamSpec *pspec)
243 RBHeader *header = RB_HEADER (object);
245 switch (prop_id) {
246 case PROP_DB:
247 header->priv->db = g_value_get_object (value);
248 break;
249 case PROP_ENTRY:
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);
254 } else {
255 header->priv->duration = -1;
257 break;
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),
261 "elapsed-changed",
262 (GCallback) rb_header_elapsed_changed_cb,
263 header);
264 break;
265 case PROP_SEEKABLE:
266 header->priv->seekable = g_value_get_boolean (value);
267 break;
268 default:
269 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
270 break;
274 static void
275 rb_header_get_property (GObject *object,
276 guint prop_id,
277 GValue *value,
278 GParamSpec *pspec)
280 RBHeader *header = RB_HEADER (object);
282 switch (prop_id) {
283 case PROP_DB:
284 g_value_set_object (value, header->priv->db);
285 break;
286 case PROP_ENTRY:
287 g_value_set_boxed (value, header->priv->entry);
288 break;
289 case PROP_SHELL_PLAYER:
290 g_value_set_object (value, header->priv->shell_player);
291 break;
292 case PROP_SEEKABLE:
293 g_value_set_boolean (value, header->priv->seekable);
294 break;
295 default:
296 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
297 break;
301 RBHeader *
302 rb_header_new (RBShellPlayer *shell_player, RhythmDB *db)
304 RBHeader *header;
306 header = RB_HEADER (g_object_new (RB_TYPE_HEADER,
307 "shell-player", shell_player,
308 "db", db,
309 "spacing", 6, NULL));
311 g_return_val_if_fail (header->priv != NULL, NULL);
313 return header;
316 void
317 rb_header_set_playing_entry (RBHeader *header,
318 RhythmDBEntry *entry,
319 gboolean seekable)
321 g_object_set (header,
322 "entry", entry,
323 "seekable", seekable,
324 NULL);
327 static void
328 append_and_free (GString *str,
329 char *text)
331 g_string_append (str, text);
332 g_free (text);
335 static void
336 get_extra_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *field, char **value)
338 GValue *v;
340 v = rhythmdb_entry_request_extra_metadata (db, entry, field);
341 if (v != NULL) {
342 *value = g_value_dup_string (v);
343 g_value_unset (v);
344 g_free (v);
345 } else {
346 *value = NULL;
350 void
351 rb_header_sync (RBHeader *header)
353 char *label_text;
355 rb_debug ("syncing with entry = %p", header->priv->entry);
357 if (header->priv->entry != NULL) {
358 const char *title;
359 const char *album;
360 const char *artist;
361 const char *stream_name = NULL;
362 char *streaming_title;
363 char *streaming_artist;
364 char *streaming_album;
365 GString *label_str;
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,
374 header->priv->entry,
375 RHYTHMDB_PROP_STREAM_SONG_TITLE,
376 &streaming_title);
377 if (streaming_title) {
378 /* use entry title as stream name */
379 stream_name = title;
380 title = streaming_title;
383 get_extra_metadata (header->priv->db,
384 header->priv->entry,
385 RHYTHMDB_PROP_STREAM_SONG_ARTIST,
386 &streaming_artist);
387 if (streaming_artist) {
388 /* override artist from entry */
389 artist = streaming_artist;
392 get_extra_metadata (header->priv->db,
393 header->priv->entry,
394 RHYTHMDB_PROP_STREAM_SONG_ALBUM,
395 &streaming_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);
416 g_free (label_text);
418 rb_header_set_show_timeline (header, have_duration && header->priv->seekable);
419 if (have_duration)
420 rb_header_sync_time (header);
422 g_free (streaming_artist);
423 g_free (streaming_album);
424 g_free (streaming_title);
425 } else {
426 char *tmp;
428 rb_debug ("not playing");
429 label_text = TITLE_MARKUP (_("Not Playing"));
430 gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
431 g_free (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);
442 g_free (tmp);
446 void
447 rb_header_set_show_position_slider (RBHeader *header,
448 gboolean show)
450 if (header->priv->scaleline_shown == show)
451 return;
453 header->priv->scaleline_shown = show;
455 if (show) {
456 gtk_widget_show_all (GTK_WIDGET (header->priv->scaleline));
457 rb_header_sync_time (header);
458 } else {
459 gtk_widget_hide (GTK_WIDGET (header->priv->scaleline));
463 static void
464 rb_header_set_show_timeline (RBHeader *header,
465 gboolean show)
467 gtk_widget_set_sensitive (header->priv->timeline, show);
468 gtk_widget_set_sensitive (header->priv->scaleline, show);
471 gboolean
472 rb_header_sync_time (RBHeader *header)
474 guint seconds;
476 if (header->priv->shell_player == NULL)
477 return TRUE;
479 if (header->priv->slider_dragging == TRUE) {
480 rb_debug ("slider is dragging, not syncing");
481 return TRUE;
484 seconds = header->priv->elapsed_time;
486 if (header->priv->duration > -1) {
487 double progress = 0.0;
489 if (seconds > 0) {
490 progress = (double) seconds;
491 } else {
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);
500 } else {
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);
509 return TRUE;
512 static gboolean
513 slider_press_callback (GtkWidget *widget,
514 GdkEventButton *event,
515 RBHeader *header)
517 header->priv->slider_dragging = TRUE;
518 header->priv->latest_set_time = -1;
519 return FALSE;
522 static gboolean
523 slider_moved_timeout (RBHeader *header)
525 double progress;
526 long new;
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 ();
541 return FALSE;
544 static gboolean
545 slider_moved_callback (GtkWidget *widget,
546 GdkEventMotion *event,
547 RBHeader *header)
549 GtkAdjustment *adjustment;
550 double progress;
552 if (header->priv->slider_dragging == FALSE) {
553 rb_debug ("slider is not dragging");
554 return FALSE;
557 adjustment = gtk_range_get_adjustment (GTK_RANGE (widget));
559 progress = gtk_adjustment_get_value (adjustment);
560 header->priv->elapsed_time = (guint) (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);
572 return FALSE;
575 static gboolean
576 slider_release_callback (GtkWidget *widget,
577 GdkEventButton *event,
578 RBHeader *header)
580 double progress;
581 long new;
582 GtkAdjustment *adjustment;
584 if (header->priv->slider_dragging == FALSE) {
585 rb_debug ("slider is not dragging");
586 return FALSE;
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;
608 return FALSE;
611 static gboolean
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 ();
623 return FALSE;
626 static void
627 slider_changed_callback (GtkWidget *widget,
628 RBHeader *header)
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);
639 static void
640 rb_header_update_elapsed (RBHeader *header)
642 char *elapsed_text;
644 /* sanity check */
645 if (header->priv->elapsed_time > header->priv->duration)
646 return;
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);
656 static void
657 rb_header_elapsed_changed_cb (RBShellPlayer *player,
658 guint elapsed,
659 RBHeader *header)
661 header->priv->elapsed_time = elapsed;
662 rb_header_sync_time (header);