No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003 Jorn Baayen <jorn@nl.linux.org>
4 * Copyright (C) 2003,2004 Colin Walters <walters@redhat.com>
5 *
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.
10 *
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.
18 *
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.
23 *
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.
27 *
28 */
29
30 #include "config.h"
31
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include <glib/gi18n.h>
37 #include <gtk/gtk.h>
38
39 #include "rb-statusbar.h"
40 #include "rb-track-transfer-queue.h"
41 #include "rb-debug.h"
42
43 /**
44 * SECTION:rb-statusbar
45 * @short_description: status bar widget
46 *
47 * The status bar is displayed at the bottom of the main window. It consists of some
48 * status text and a progress bar.
49 *
50 * The status text usually comes from the selected page, and typically shows the number
51 * of songs, the total duration and the total file size. When a menu is open, however,
52 * the status text shows the description of the currently selected menu item.
53 *
54 * The progress bar shows progress information from a variety of sources. The page that
55 * is currently selected in the display page tree can provide progress information, such as
56 * buffering feedback, track transfer status, or progress for updating a song catalog.
57 * If the page does not provide status information and the database is busy (loading the
58 * database from disk, processing a query, etc.) the progress bar will be pulsed periodically.
59 */
60
61 #define EPSILON (0.00001)
62
63 static void rb_statusbar_class_init (RBStatusbarClass *klass);
64 static void rb_statusbar_init (RBStatusbar *statusbar);
65 static void rb_statusbar_dispose (GObject *object);
66 static void rb_statusbar_finalize (GObject *object);
67 static void rb_statusbar_set_property (GObject *object,
68 guint prop_id,
69 const GValue *value,
70 GParamSpec *pspec);
71 static void rb_statusbar_get_property (GObject *object,
72 guint prop_id,
73 GValue *value,
74 GParamSpec *pspec);
75
76 static gboolean poll_status (RBStatusbar *status);
77 static void rb_statusbar_sync_status (RBStatusbar *status);
78 static void rb_statusbar_page_status_changed_cb (RBDisplayPage *page,
79 RBStatusbar *statusbar);
80 static void rb_statusbar_transfer_progress_cb (RBTrackTransferQueue *queue,
81 int done,
82 int total,
83 double fraction,
84 int time_left,
85 RBStatusbar *statusbar);
86
87 struct RBStatusbarPrivate
88 {
89 RBDisplayPage *selected_page;
90 RBTrackTransferQueue *transfer_queue;
91
92 RhythmDB *db;
93
94 GtkUIManager *ui_manager;
95
96 GtkWidget *progress;
97
98 guint status_poll_id;
99 };
100
101 enum
102 {
103 PROP_0,
104 PROP_DB,
105 PROP_UI_MANAGER,
106 PROP_PAGE,
107 PROP_TRANSFER_QUEUE
108 };
109
110 G_DEFINE_TYPE (RBStatusbar, rb_statusbar, GTK_TYPE_STATUSBAR)
111
112 static void
113 rb_statusbar_class_init (RBStatusbarClass *klass)
114 {
115 GObjectClass *object_class = G_OBJECT_CLASS (klass);
116
117 object_class->dispose = rb_statusbar_dispose;
118 object_class->finalize = rb_statusbar_finalize;
119
120 object_class->set_property = rb_statusbar_set_property;
121 object_class->get_property = rb_statusbar_get_property;
122
123 /**
124 * RBStatusbar:db:
125 *
126 * The #RhythmDB instance
127 */
128 g_object_class_install_property (object_class,
129 PROP_DB,
130 g_param_spec_object ("db",
131 "RhythmDB",
132 "RhythmDB object",
133 RHYTHMDB_TYPE,
134 G_PARAM_READWRITE));
135 /**
136 * RBStatusbar:page:
137 *
138 * The currently selected #RBDisplayPage
139 */
140 g_object_class_install_property (object_class,
141 PROP_PAGE,
142 g_param_spec_object ("page",
143 "RBDisplayPage",
144 "RBDisplayPage object",
145 RB_TYPE_DISPLAY_PAGE,
146 G_PARAM_READWRITE));
147 /**
148 * RBStatusbar:ui-manager:
149 *
150 * The #GtkUIManager instance
151 */
152 g_object_class_install_property (object_class,
153 PROP_UI_MANAGER,
154 g_param_spec_object ("ui-manager",
155 "GtkUIManager",
156 "GtkUIManager object",
157 GTK_TYPE_UI_MANAGER,
158 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
159
160 /**
161 * RBStatusbar::transfer-queue:
162 *
163 * The #RBTrackTransferQueue instance
164 */
165 g_object_class_install_property (object_class,
166 PROP_TRANSFER_QUEUE,
167 g_param_spec_object ("transfer-queue",
168 "RBTrackTransferQueue",
169 "RBTrackTransferQueue instance",
170 RB_TYPE_TRACK_TRANSFER_QUEUE,
171 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
172
173 g_type_class_add_private (klass, sizeof (RBStatusbarPrivate));
174 }
175
176 static void
177 rb_statusbar_init (RBStatusbar *statusbar)
178 {
179 statusbar->priv = G_TYPE_INSTANCE_GET_PRIVATE (statusbar,
180 RB_TYPE_STATUSBAR,
181 RBStatusbarPrivate);
182
183 statusbar->priv->progress = gtk_progress_bar_new ();
184 gtk_widget_set_size_request (statusbar->priv->progress, -1, 10);
185
186 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (statusbar->priv->progress), 1.0);
187 gtk_widget_hide (statusbar->priv->progress);
188
189 gtk_box_pack_start (GTK_BOX (statusbar),
190 GTK_WIDGET (statusbar->priv->progress), FALSE, TRUE, 0);
191 }
192
193 static void
194 rb_statusbar_dispose (GObject *object)
195 {
196 RBStatusbar *statusbar;
197
198 g_return_if_fail (object != NULL);
199 g_return_if_fail (RB_IS_STATUSBAR (object));
200
201 statusbar = RB_STATUSBAR (object);
202
203 g_return_if_fail (statusbar->priv != NULL);
204
205 if (statusbar->priv->status_poll_id) {
206 g_source_remove (statusbar->priv->status_poll_id);
207 statusbar->priv->status_poll_id = 0;
208 }
209
210 if (statusbar->priv->db != NULL) {
211 g_object_unref (statusbar->priv->db);
212 statusbar->priv->db = NULL;
213 }
214
215 if (statusbar->priv->ui_manager != NULL) {
216 g_object_unref (statusbar->priv->ui_manager);
217 statusbar->priv->ui_manager = NULL;
218 }
219
220 if (statusbar->priv->selected_page != NULL) {
221 g_object_unref (statusbar->priv->selected_page);
222 statusbar->priv->selected_page = NULL;
223 }
224
225 if (statusbar->priv->transfer_queue != NULL) {
226 g_object_unref (statusbar->priv->transfer_queue);
227 statusbar->priv->transfer_queue = NULL;
228 }
229
230 G_OBJECT_CLASS (rb_statusbar_parent_class)->dispose (object);
231 }
232
233 static void
234 rb_statusbar_finalize (GObject *object)
235 {
236 RBStatusbar *statusbar;
237
238 g_return_if_fail (object != NULL);
239 g_return_if_fail (RB_IS_STATUSBAR (object));
240
241 statusbar = RB_STATUSBAR (object);
242
243 g_return_if_fail (statusbar->priv != NULL);
244
245 G_OBJECT_CLASS (rb_statusbar_parent_class)->finalize (object);
246 }
247
248 typedef struct {
249 GtkWidget *statusbar;
250 char *tooltip;
251 } StatusTip;
252
253 static void
254 statustip_free (StatusTip *tip)
255 {
256 g_object_unref (tip->statusbar);
257 g_free (tip->tooltip);
258 g_free (tip);
259 }
260
261 static void
262 set_statusbar_tooltip (GtkWidget *widget,
263 StatusTip *data)
264 {
265 guint context_id;
266
267 context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (data->statusbar),
268 "rb_statusbar_tooltip");
269 gtk_statusbar_push (GTK_STATUSBAR (data->statusbar),
270 context_id,
271 data->tooltip ? data->tooltip: "");
272 }
273
274 static void
275 unset_statusbar_tooltip (GtkWidget *widget,
276 GtkWidget *statusbar)
277 {
278 guint context_id;
279
280 context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
281 "rb_statusbar_tooltip");
282 gtk_statusbar_pop (GTK_STATUSBAR (statusbar), context_id);
283 }
284
285 static void
286 rb_statusbar_connect_ui_manager (RBStatusbar *statusbar,
287 GtkAction *action,
288 GtkWidget *proxy,
289 GtkUIManager *ui_manager)
290 {
291 char *tooltip;
292
293 if (! GTK_IS_MENU_ITEM (proxy))
294 return;
295
296 g_object_get (action, "tooltip", &tooltip, NULL);
297
298 if (tooltip) {
299 StatusTip *statustip;
300
301 statustip = g_new (StatusTip, 1);
302 statustip->statusbar = g_object_ref (statusbar);
303 statustip->tooltip = tooltip;
304 g_signal_connect_data (proxy, "select",
305 G_CALLBACK (set_statusbar_tooltip),
306 statustip, (GClosureNotify)statustip_free, 0);
307
308 g_signal_connect (proxy, "deselect",
309 G_CALLBACK (unset_statusbar_tooltip),
310 statusbar);
311 }
312 }
313
314 static void
315 rb_statusbar_set_property (GObject *object,
316 guint prop_id,
317 const GValue *value,
318 GParamSpec *pspec)
319 {
320 RBStatusbar *statusbar = RB_STATUSBAR (object);
321
322 switch (prop_id)
323 {
324 case PROP_DB:
325 statusbar->priv->db = g_value_get_object (value);
326 g_object_ref (statusbar->priv->db);
327 statusbar->priv->status_poll_id
328 = g_idle_add ((GSourceFunc) poll_status, statusbar);
329 break;
330 case PROP_PAGE:
331 if (statusbar->priv->selected_page != NULL) {
332 g_signal_handlers_disconnect_by_func (G_OBJECT (statusbar->priv->selected_page),
333 G_CALLBACK (rb_statusbar_page_status_changed_cb),
334 statusbar);
335 g_object_unref (statusbar->priv->selected_page);
336 }
337
338 statusbar->priv->selected_page = g_value_dup_object (value);
339 rb_debug ("selected page %p", statusbar->priv->selected_page);
340
341 if (statusbar->priv->selected_page != NULL) {
342 g_signal_connect_object (G_OBJECT (statusbar->priv->selected_page),
343 "status-changed",
344 G_CALLBACK (rb_statusbar_page_status_changed_cb),
345 statusbar, 0);
346 }
347 rb_statusbar_sync_status (statusbar);
348
349 break;
350 case PROP_UI_MANAGER:
351 if (statusbar->priv->ui_manager) {
352 g_signal_handlers_disconnect_by_func (G_OBJECT (statusbar->priv->ui_manager),
353 G_CALLBACK (rb_statusbar_connect_ui_manager),
354 statusbar);
355 g_object_unref (statusbar->priv->ui_manager);
356 }
357 statusbar->priv->ui_manager = g_value_get_object (value);
358 g_object_ref (statusbar->priv->ui_manager);
359
360 g_signal_connect_object (statusbar->priv->ui_manager,
361 "connect-proxy",
362 G_CALLBACK (rb_statusbar_connect_ui_manager),
363 statusbar,
364 G_CONNECT_SWAPPED);
365 break;
366 case PROP_TRANSFER_QUEUE:
367 statusbar->priv->transfer_queue = g_value_dup_object (value);
368 g_signal_connect_object (G_OBJECT (statusbar->priv->transfer_queue),
369 "transfer-progress",
370 G_CALLBACK (rb_statusbar_transfer_progress_cb),
371 statusbar,
372 0);
373 break;
374 default:
375 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376 break;
377 }
378 }
379
380 static void
381 rb_statusbar_get_property (GObject *object,
382 guint prop_id,
383 GValue *value,
384 GParamSpec *pspec)
385 {
386 RBStatusbar *statusbar = RB_STATUSBAR (object);
387
388 switch (prop_id)
389 {
390 case PROP_DB:
391 g_value_set_object (value, statusbar->priv->db);
392 break;
393 case PROP_PAGE:
394 g_value_set_object (value, statusbar->priv->selected_page);
395 break;
396 case PROP_UI_MANAGER:
397 g_value_set_object (value, statusbar->priv->ui_manager);
398 break;
399 case PROP_TRANSFER_QUEUE:
400 g_value_set_object (value, statusbar->priv->transfer_queue);
401 break;
402 default:
403 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
404 break;
405 }
406 }
407
408 /**
409 * rb_statusbar_set_page:
410 * @statusbar: the #RBStatusbar
411 * @page: the new selected #RBDisplayPage
412 *
413 * Updates the status bar for a newly selected page.
414 */
415 void
416 rb_statusbar_set_page (RBStatusbar *statusbar, RBDisplayPage *page)
417 {
418 g_return_if_fail (RB_IS_STATUSBAR (statusbar));
419 g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
420
421 g_object_set (statusbar, "page", page, NULL);
422 }
423
424 static gboolean
425 poll_status (RBStatusbar *status)
426 {
427 GDK_THREADS_ENTER ();
428
429 status->priv->status_poll_id = 0;
430 rb_statusbar_sync_status (status);
431
432 GDK_THREADS_LEAVE ();
433
434 return FALSE;
435 }
436
437 static void
438 rb_statusbar_sync_status (RBStatusbar *status)
439 {
440 gboolean changed = FALSE;
441 char *status_text = NULL;
442 char *progress_text = NULL;
443 float progress = 999;
444 int time_left = 0;
445
446 /*
447 * Behaviour of status bar:
448 * - use page's status text
449 * - use page's progress value and text, unless transfer queue provides something
450 * - if no page progress value or transfer progress value and library is busy,
451 * pulse the progress bar
452 */
453
454 /* library busy? */
455 if (rhythmdb_is_busy (status->priv->db)) {
456 progress = -1.0f;
457
458 /* see if it wants to provide more details */
459 rhythmdb_get_progress_info (status->priv->db, &progress_text, &progress);
460 changed = TRUE;
461 }
462
463 /* get page details */
464 if (status->priv->selected_page) {
465 rb_display_page_get_status (status->priv->selected_page, &status_text, &progress_text, &progress);
466 rb_debug ("updating status with: '%s', '%s', %f",
467 status_text ? status_text : "", progress_text ? progress_text : "", progress);
468 }
469
470 /* get transfer details */
471 rb_track_transfer_queue_get_status (status->priv->transfer_queue,
472 &status_text,
473 &progress_text,
474 &progress,
475 &time_left);
476
477 /* set up the status text */
478 if (status_text) {
479 gtk_statusbar_pop (GTK_STATUSBAR (status), 0);
480 gtk_statusbar_push (GTK_STATUSBAR (status), 0, status_text);
481 g_free (status_text);
482 }
483
484 /* set up the progress bar */
485 if (progress > (1.0f - EPSILON)) {
486 gtk_widget_hide (status->priv->progress);
487 } else {
488 gtk_widget_show (status->priv->progress);
489
490 if (progress < EPSILON) {
491 gtk_progress_bar_pulse (GTK_PROGRESS_BAR (status->priv->progress));
492 changed = TRUE;
493 } else {
494 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (status->priv->progress),
495 progress);
496 }
497 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (status->priv->progress),
498 progress_text);
499 }
500
501 g_free (progress_text);
502
503 if (status->priv->status_poll_id == 0 && changed)
504 status->priv->status_poll_id = g_timeout_add (250, (GSourceFunc) poll_status, status);
505 }
506
507 /**
508 * rb_statusbar_new:
509 * @db: the #RhythmDB instance
510 * @ui_manager: the #GtkUIManager
511 * @transfer_queue: the #RBTrackTransferQueue
512 *
513 * Creates the status bar widget.
514 *
515 * Return value: the status bar widget
516 */
517 RBStatusbar *
518 rb_statusbar_new (RhythmDB *db,
519 GtkUIManager *ui_manager,
520 RBTrackTransferQueue *queue)
521 {
522 RBStatusbar *statusbar = g_object_new (RB_TYPE_STATUSBAR,
523 "db", db,
524 "ui-manager", ui_manager,
525 "transfer-queue", queue,
526 NULL);
527
528 g_return_val_if_fail (statusbar->priv != NULL, NULL);
529
530 return statusbar;
531 }
532
533 static void
534 add_status_poll (RBStatusbar *statusbar)
535 {
536 if (statusbar->priv->status_poll_id == 0)
537 statusbar->priv->status_poll_id =
538 g_idle_add ((GSourceFunc) poll_status, statusbar);
539 }
540
541 static void
542 rb_statusbar_page_status_changed_cb (RBDisplayPage *page, RBStatusbar *statusbar)
543 {
544 rb_debug ("source status changed");
545 add_status_poll (statusbar);
546 }
547
548 static void
549 rb_statusbar_transfer_progress_cb (RBTrackTransferQueue *queue,
550 int done,
551 int total,
552 double progress,
553 int time_left,
554 RBStatusbar *statusbar)
555 {
556 rb_debug ("transfer progress changed");
557 add_status_poll (statusbar);
558 }