Updated Macedonian Translation <arangela@cvs.gnome.org>
[rhythmbox.git] / widgets / rb-header.c
blob4287ba65e1dfcbe4faae50d32aa8480a8a586b6d
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"
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,
44 guint prop_id,
45 const GValue *value,
46 GParamSpec *pspec);
47 static void rb_header_get_property (GObject *object,
48 guint prop_id,
49 GValue *value,
50 GParamSpec *pspec);
51 static void rb_header_set_show_timeline (RBHeader *header,
52 gboolean show);
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
63 RhythmDB *db;
64 RhythmDBEntry *entry;
66 char *title;
68 RBShellPlayer *shell_player;
70 GtkWidget *image;
71 GtkWidget *song;
73 GtkWidget *timeline;
74 GtkWidget *scaleline;
75 gboolean scaleline_shown;
77 GtkWidget *scale;
78 GtkAdjustment *adjustment;
79 gboolean slider_dragging;
80 gboolean slider_locked;
81 guint slider_moved_timeout;
82 long latest_set_time;
83 guint value_changed_update_handler;
84 GtkWidget *elapsed;
86 long elapsed_time;
87 long duration;
90 enum
92 PROP_0,
93 PROP_DB,
94 PROP_SHELL_PLAYER,
95 PROP_ENTRY,
96 PROP_TITLE,
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)
105 static void
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,
116 PROP_DB,
117 g_param_spec_object ("db",
118 "RhythmDB",
119 "RhythmDB object",
120 RHYTHMDB_TYPE,
121 G_PARAM_READWRITE));
123 g_object_class_install_property (object_class,
124 PROP_ENTRY,
125 g_param_spec_pointer ("entry",
126 "RhythmDBEntry",
127 "RhythmDBEntry pointer",
128 G_PARAM_READWRITE));
129 g_object_class_install_property (object_class,
130 PROP_SHELL_PLAYER,
131 g_param_spec_object ("shell-player",
132 "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,
137 PROP_TITLE,
138 g_param_spec_string ("title",
139 "title",
140 "title",
141 NULL,
142 G_PARAM_READWRITE));
144 g_type_class_add_private (klass, sizeof (RBHeaderPrivate));
147 static void
148 rb_header_init (RBHeader *header)
151 * The children in this widget look like this:
152 * RBHeader
153 * GtkHBox
154 * GtkLabel (priv->song)
155 * GtkHBox (priv->timeline)
156 * GtkHScale (priv->scale)
157 * GtkAlignment
158 * GtkLabel (priv->elapsed)
160 GtkWidget *hbox;
161 GtkWidget *vbox;
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);
171 /* song info */
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),
203 header, 0);
204 g_signal_connect_object (G_OBJECT (header->priv->scale),
205 "button_release_event",
206 G_CALLBACK (slider_release_callback),
207 header, 0);
208 g_signal_connect_object (G_OBJECT (header->priv->scale),
209 "motion_notify_event",
210 G_CALLBACK (slider_moved_callback),
211 header, 0);
212 g_signal_connect_object (G_OBJECT (header->priv->scale),
213 "value_changed",
214 G_CALLBACK (slider_changed_callback),
215 header, 0);
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);
221 static void
222 rb_header_finalize (GObject *object)
224 RBHeader *header;
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);
235 static void
236 rb_header_set_property (GObject *object,
237 guint prop_id,
238 const GValue *value,
239 GParamSpec *pspec)
241 RBHeader *header = RB_HEADER (object);
243 switch (prop_id) {
244 case PROP_DB:
245 header->priv->db = g_value_get_object (value);
246 break;
247 case PROP_ENTRY:
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);
252 } else {
253 header->priv->duration = -1;
255 break;
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),
259 "elapsed-changed",
260 (GCallback) rb_header_elapsed_changed_cb,
261 header);
262 break;
263 case PROP_TITLE:
264 g_free (header->priv->title);
265 header->priv->title = g_value_dup_string (value);
266 break;
267 default:
268 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269 break;
273 static void
274 rb_header_get_property (GObject *object,
275 guint prop_id,
276 GValue *value,
277 GParamSpec *pspec)
279 RBHeader *header = RB_HEADER (object);
281 switch (prop_id) {
282 case PROP_DB:
283 g_value_set_object (value, header->priv->db);
284 break;
285 case PROP_ENTRY:
286 g_value_set_object (value, header->priv->entry);
287 break;
288 case PROP_SHELL_PLAYER:
289 g_value_set_object (value, header->priv->shell_player);
290 break;
291 case PROP_TITLE:
292 g_value_set_string (value, header->priv->title);
293 break;
294 default:
295 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
296 break;
300 RBHeader *
301 rb_header_new (RBShellPlayer *shell_player)
303 RBHeader *header;
305 header = RB_HEADER (g_object_new (RB_TYPE_HEADER, "shell-player", shell_player,
306 "title", NULL,
307 "spacing", 6, NULL));
309 g_return_val_if_fail (header->priv != NULL, NULL);
311 return header;
314 void
315 rb_header_set_playing_entry (RBHeader *header, RhythmDBEntry *entry)
317 g_object_set (G_OBJECT (header), "entry", entry, NULL);
320 void
321 rb_header_set_title (RBHeader *header, const char *title)
323 g_object_set (G_OBJECT (header), "title", title, NULL);
326 void
327 rb_header_sync (RBHeader *header)
329 char *label_text;
331 rb_debug ("syncing with entry = %p", header->priv->entry);
333 if (header->priv->entry != NULL) {
334 const char *song = header->priv->title;
335 const char *album;
336 const char *artist;
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);
347 } else {
348 label_text = SONG_MARKUP (song);
351 gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
352 g_free (label_text);
354 rb_header_set_show_timeline (header, have_duration);
355 if (have_duration)
356 rb_header_sync_time (header);
357 } else {
358 char *tmp;
360 rb_debug ("not playing");
361 label_text = SONG_MARKUP (_("Not Playing"));
362 gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
363 g_free (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);
374 g_free (tmp);
378 void
379 rb_header_set_show_position_slider (RBHeader *header,
380 gboolean show)
382 if (header->priv->scaleline_shown == show)
383 return;
385 header->priv->scaleline_shown = show;
387 if (show) {
388 gtk_widget_show_all (GTK_WIDGET (header->priv->scaleline));
389 rb_header_sync_time (header);
390 } else {
391 gtk_widget_hide (GTK_WIDGET (header->priv->scaleline));
395 static void
396 rb_header_set_show_timeline (RBHeader *header,
397 gboolean show)
399 gtk_widget_set_sensitive (header->priv->timeline, show);
400 gtk_widget_set_sensitive (header->priv->scaleline, show);
403 gboolean
404 rb_header_sync_time (RBHeader *header)
406 int seconds;
408 if (header->priv->shell_player == NULL)
409 return TRUE;
411 if (header->priv->slider_dragging == TRUE) {
412 rb_debug ("slider is dragging, not syncing");
413 return TRUE;
416 seconds = header->priv->elapsed_time;
418 if (header->priv->duration > -1) {
419 double progress = 0.0;
421 if (seconds > 0) {
422 progress = (double) (long) seconds;
423 } else {
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);
432 } else {
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);
441 return TRUE;
444 static gboolean
445 slider_press_callback (GtkWidget *widget,
446 GdkEventButton *event,
447 RBHeader *header)
449 header->priv->slider_dragging = TRUE;
450 header->priv->latest_set_time = -1;
451 return FALSE;
454 static gboolean
455 slider_moved_timeout (RBHeader *header)
457 double progress;
458 long new;
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 ();
473 return FALSE;
476 static gboolean
477 slider_moved_callback (GtkWidget *widget,
478 GdkEventMotion *event,
479 RBHeader *header)
481 GtkAdjustment *adjustment;
482 double progress;
484 if (header->priv->slider_dragging == FALSE) {
485 rb_debug ("slider is not dragging");
486 return FALSE;
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);
504 return FALSE;
507 static gboolean
508 slider_release_callback (GtkWidget *widget,
509 GdkEventButton *event,
510 RBHeader *header)
512 double progress;
513 long new;
514 GtkAdjustment *adjustment;
516 if (header->priv->slider_dragging == FALSE) {
517 rb_debug ("slider is not dragging");
518 return FALSE;
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;
540 return FALSE;
543 static gboolean
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 ();
555 return FALSE;
558 static void
559 slider_changed_callback (GtkWidget *widget,
560 RBHeader *header)
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);
571 static void
572 rb_header_update_elapsed (RBHeader *header)
574 char *elapsed_text;
576 /* sanity check */
577 if ((header->priv->elapsed_time > header->priv->duration) || (header->priv->elapsed_time < 0))
578 return;
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);
589 static void
590 rb_header_elapsed_changed_cb (RBShellPlayer *player,
591 guint elapsed,
592 RBHeader *header)
594 header->priv->elapsed_time = elapsed;
595 rb_header_sync_time (header);