hythmbox-2.98/shell/rb-statusbar.c

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 }