No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include "rb-display-page.h"
32 #include "rb-shell.h"
33 #include "rb-debug.h"
34 #include "rb-util.h"
35
36 G_DEFINE_ABSTRACT_TYPE (RBDisplayPage, rb_display_page, GTK_TYPE_HBOX)
37
38 /**
39 * SECTION:rb-display-page
40 * @short_description: base class for items that appear in the display page tree
41 *
42 * This is the base class for items that appear in the display page tree and can
43 * occupy the main display area. Sources and source groups are display pages.
44 * Other types of display, such as music visualization, could be implemented as
45 * display pages too.
46 *
47 * The display page object itself is the widget shown in the main display area.
48 * The pixbuf and name properties control its appearance in the display page
49 * tree, and its location is determined by its parent display page, the sorting
50 * rules for its source group (if any), and insertion order. The visibility property
51 * controls whether the display page is actually shown in the display page tree at all.
52 */
53
54 struct _RBDisplayPagePrivate
55 {
56 char *name;
57 gboolean visible;
58 gboolean selected;
59 GdkPixbuf *pixbuf;
60 RBDisplayPage *parent;
61
62 GObject *plugin;
63 RBShell *shell;
64
65 gboolean deleted;
66
67 GList *pending_children;
68 };
69
70 enum
71 {
72 PROP_0,
73 PROP_SHELL,
74 PROP_UI_MANAGER,
75 PROP_NAME,
76 PROP_PIXBUF,
77 PROP_VISIBLE,
78 PROP_PARENT,
79 PROP_PLUGIN,
80 PROP_SELECTED,
81 };
82
83 enum
84 {
85 STATUS_CHANGED,
86 DELETED,
87 LAST_SIGNAL
88 };
89
90 static guint signals[LAST_SIGNAL] = { 0 };
91
92 void
93 _rb_display_page_add_pending_child (RBDisplayPage *page, RBDisplayPage *child)
94 {
95 page->priv->pending_children = g_list_append (page->priv->pending_children, child);
96 }
97
98 GList *
99 _rb_display_page_get_pending_children (RBDisplayPage *page)
100 {
101 GList *c = page->priv->pending_children;
102 page->priv->pending_children = NULL;
103 return c;
104 }
105
106 /**
107 * rb_display_age_receive_drag:
108 * @page: a #RBDisplayPage
109 * @data: the selection data
110 *
111 * This is called when the user drags something to the page.
112 * Depending on the drag data type, the data might be a list of
113 * #RhythmDBEntry objects, a list of URIs, or a list of album
114 * or artist or genre names.
115 *
116 * Return value: TRUE if the page accepted the drag data
117 */
118 gboolean
119 rb_display_page_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
120 {
121 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
122
123 if (klass->receive_drag)
124 return klass->receive_drag (page, data);
125 else
126 return FALSE;
127 }
128
129 /**
130 * rb_display_page_show_popup:
131 * @page: a #RBDisplayPage
132 *
133 * Called when the user performs an action (such as right-clicking)
134 * that should result in a popup menu being displayed for the page.
135 *
136 * Return value: TRUE if the page managed to display a popup
137 */
138 gboolean
139 rb_display_page_show_popup (RBDisplayPage *page)
140 {
141 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
142
143 if (klass->show_popup)
144 return klass->show_popup (page);
145 else
146 return FALSE;
147 }
148
149 /**
150 * rb_display_page_delete_thyself:
151 * @page: a #RBDisplayPage
152 *
153 * This is called when the page should delete itself.
154 * The 'deleted' signal will be emitted, which removes the page
155 * from the page model. This will not actually dispose of the
156 * page object, so reference counting must still be handled
157 * correctly.
158 */
159 void
160 rb_display_page_delete_thyself (RBDisplayPage *page)
161 {
162 RBDisplayPageClass *klass;
163
164 g_return_if_fail (page != NULL);
165 if (page->priv->deleted) {
166 rb_debug ("source has already been deleted");
167 return;
168 }
169 page->priv->deleted = TRUE;
170
171 klass = RB_DISPLAY_PAGE_GET_CLASS (page);
172 klass->delete_thyself (page);
173
174 g_signal_emit (G_OBJECT (page), signals[DELETED], 0);
175 }
176
177 /**
178 * rb_display_page_selectable:
179 * @page: a #RBDisplayPage
180 *
181 * Checks if @page can be selected
182 */
183 gboolean
184 rb_display_page_selectable (RBDisplayPage *page)
185 {
186 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
187 if (klass->selectable)
188 return klass->selectable (page);
189 else
190 return TRUE;
191 }
192
193 /**
194 * rb_display_page_selected:
195 * @page: a #RBDisplayPage
196 *
197 * Called when the page is selected in the page tree.
198 */
199 void
200 rb_display_page_selected (RBDisplayPage *page)
201 {
202 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
203
204 if (klass->selected)
205 klass->selected (page);
206
207 page->priv->selected = TRUE;
208 g_object_notify (G_OBJECT (page), "selected");
209 }
210
211 /**
212 * rb_display_page_deselected:
213 * @page: a #RBDisplayPage
214 *
215 * Called when the page is deselected in the page tree.
216 */
217 void
218 rb_display_page_deselected (RBDisplayPage *page)
219 {
220 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
221
222 if (klass->deselected)
223 klass->deselected (page);
224
225 page->priv->selected = FALSE;
226 g_object_notify (G_OBJECT (page), "selected");
227 }
228
229 /**
230 * rb_display_page_activate:
231 * @page: a #RBDisplayPage
232 *
233 * Called when the page is activated (double clicked, etc.) in the page tree.
234 */
235 void
236 rb_display_page_activate (RBDisplayPage *page)
237 {
238 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
239
240 if (klass->activate)
241 klass->activate (page);
242 }
243
244
245 /**
246 * rb_display_page_get_config_widget:
247 * @page: a #RBDisplayPage
248 * @prefs: the #RBShellPreferences object
249 *
250 * Source implementations can use this to return an optional
251 * configuration widget. The widget will be displayed in a
252 * page in the preferences dialog.
253 *
254 * Return value: (transfer none): configuration widget
255 */
256 GtkWidget *
257 rb_display_page_get_config_widget (RBDisplayPage *page,
258 RBShellPreferences *prefs)
259 {
260 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
261
262 if (klass->get_config_widget) {
263 return klass->get_config_widget (page, prefs);
264 } else {
265 return NULL;
266 }
267 }
268
269 /**
270 * rb_display_page_get_status:
271 * @page: a #RBDisplayPage
272 * @text: (inout) (allow-none) (transfer full): holds the returned status text
273 * @progress_text: (inout) (allow-none) (transfer full): holds the returned text for the progress bar
274 * @progress: (inout) (allow-none): holds the progress value
275 *
276 * Retrieves the details to display in the status bar for the page.
277 * If the progress value returned is less than zero, the progress bar
278 * will pulse. If the progress value is greater than or equal to 1,
279 * the progress bar will be hidden.
280 **/
281 void
282 rb_display_page_get_status (RBDisplayPage *page,
283 char **text,
284 char **progress_text,
285 float *progress)
286 {
287 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
288
289 if (klass->get_status)
290 klass->get_status (page, text, progress_text, progress);
291 }
292
293 /**
294 * rb_display_page_notify_status_changed:
295 * @page: a #RBDisplayPage
296 *
297 * Page implementations call this when their status bar information
298 * changes.
299 */
300 void
301 rb_display_page_notify_status_changed (RBDisplayPage *page)
302 {
303 g_signal_emit (G_OBJECT (page), signals[STATUS_CHANGED], 0);
304 }
305
306 /**
307 * _rb_display_page_show_popup:
308 * @page: a #RBDisplayPage
309 * @ui_path: UI path to the popup to show
310 *
311 * Page implementations can use this as a shortcut to
312 * display a popup that has been loaded into the UI manager.
313 */
314 void
315 _rb_display_page_show_popup (RBDisplayPage *page, const char *ui_path)
316 {
317 GtkUIManager *uimanager;
318
319 g_object_get (page->priv->shell, "ui-manager", &uimanager, NULL);
320 rb_gtk_action_popup_menu (uimanager, ui_path);
321 g_object_unref (uimanager);
322 }
323
324 static GtkActionGroup *
325 find_action_group (GtkUIManager *uimanager, const char *group_name)
326 {
327 GList *actiongroups;
328 GList *i;
329 actiongroups = gtk_ui_manager_get_action_groups (uimanager);
330
331 /* Don't create the action group if it's already registered */
332 for (i = actiongroups; i != NULL; i = i->next) {
333 const char *name;
334
335 name = gtk_action_group_get_name (GTK_ACTION_GROUP (i->data));
336 if (g_strcmp0 (name, group_name) == 0) {
337 return GTK_ACTION_GROUP (i->data);
338 }
339 }
340
341 return NULL;
342 }
343
344 /**
345 * _rb_display_page_register_action_group:
346 * @page: a #RBDisplayPage
347 * @group_name: action group name
348 * @actions: array of GtkActionEntry structures for the action group
349 * @num_actions: number of actions in the @actions array
350 * @user_data: user data to use for action signal handlers
351 *
352 * Creates and registers a GtkActionGroup for the page.
353 *
354 * Return value: the created action group
355 */
356 GtkActionGroup *
357 _rb_display_page_register_action_group (RBDisplayPage *page,
358 const char *group_name,
359 GtkActionEntry *actions,
360 int num_actions,
361 gpointer user_data)
362 {
363 GtkUIManager *uimanager;
364 GtkActionGroup *group;
365
366 g_return_val_if_fail (group_name != NULL, NULL);
367
368 g_object_get (page, "ui-manager", &uimanager, NULL);
369 group = find_action_group (uimanager, group_name);
370 if (group == NULL) {
371 group = gtk_action_group_new (group_name);
372 gtk_action_group_set_translation_domain (group,
373 GETTEXT_PACKAGE);
374 if (actions != NULL) {
375 gtk_action_group_add_actions (group,
376 actions, num_actions,
377 user_data);
378 }
379 gtk_ui_manager_insert_action_group (uimanager, group, 0);
380 } else {
381 g_object_ref (group);
382 }
383 g_object_unref (uimanager);
384
385 return group;
386 }
387
388 typedef void (*DisplayPageActionCallback) (GtkAction *action, RBDisplayPage *page);
389
390 typedef struct {
391 DisplayPageActionCallback callback;
392 gpointer shell;
393 } DisplayPageActionData;
394
395 static void
396 display_page_action_data_destroy (DisplayPageActionData *data)
397 {
398 if (data->shell != NULL) {
399 g_object_remove_weak_pointer (G_OBJECT (data->shell), &data->shell);
400 }
401 g_slice_free (DisplayPageActionData, data);
402 }
403
404 static void
405 display_page_action_cb (GtkAction *action, DisplayPageActionData *data)
406 {
407 RBDisplayPage *page;
408
409 if (data->shell == NULL) {
410 return;
411 }
412
413 /* get current page */
414 g_object_get (data->shell, "selected-page", &page, NULL);
415 if (page != NULL) {
416 data->callback (action, page);
417 g_object_unref (page);
418 }
419 }
420
421 /**
422 * _rb_action_group_add_display_page_actions:
423 * @group: a #GtkActionGroup
424 * @shell: the #RBShell
425 * @actions: array of GtkActionEntry structures for the action group
426 * @num_actions: number of actions in the @actions array
427 *
428 * Adds actions to an action group where the action callback is
429 * called with the current selected display page. This can safely be called
430 * multiple times on the same action group.
431 */
432 void
433 _rb_action_group_add_display_page_actions (GtkActionGroup *group,
434 GObject *shell,
435 GtkActionEntry *actions,
436 int num_actions)
437 {
438 int i;
439 for (i = 0; i < num_actions; i++) {
440 GtkAction *action;
441 const char *label;
442 const char *tooltip;
443 DisplayPageActionData *page_action_data;
444
445 if (gtk_action_group_get_action (group, actions[i].name) != NULL) {
446 /* action was already added */
447 continue;
448 }
449
450 label = gtk_action_group_translate_string (group, actions[i].label);
451 tooltip = gtk_action_group_translate_string (group, actions[i].tooltip);
452
453 action = gtk_action_new (actions[i].name, label, tooltip, NULL);
454 if (actions[i].stock_id != NULL) {
455 g_object_set (action, "stock-id", actions[i].stock_id, NULL);
456 if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
457 actions[i].stock_id)) {
458 g_object_set (action, "icon-name", actions[i].stock_id, NULL);
459 }
460 }
461
462 if (actions[i].callback) {
463 GClosure *closure;
464 page_action_data = g_slice_new0 (DisplayPageActionData);
465 page_action_data->callback = (DisplayPageActionCallback) actions[i].callback;
466 page_action_data->shell = shell;
467 g_object_add_weak_pointer (shell, &page_action_data->shell);
468
469 closure = g_cclosure_new (G_CALLBACK (display_page_action_cb),
470 page_action_data,
471 (GClosureNotify) display_page_action_data_destroy);
472 g_signal_connect_closure (action, "activate", closure, FALSE);
473 }
474
475 gtk_action_group_add_action_with_accel (group, action, actions[i].accelerator);
476 g_object_unref (action);
477 }
478 }
479
480 static void
481 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
482 {
483 RBDisplayPage *page = RB_DISPLAY_PAGE (object);
484
485 switch (prop_id) {
486 case PROP_SHELL:
487 g_value_set_object (value, page->priv->shell);
488 break;
489 case PROP_UI_MANAGER:
490 {
491 GtkUIManager *manager;
492 g_object_get (page->priv->shell, "ui-manager", &manager, NULL);
493 g_value_set_object (value, manager);
494 g_object_unref (manager);
495 break;
496 }
497 case PROP_NAME:
498 g_value_set_string (value, page->priv->name);
499 break;
500 case PROP_PIXBUF:
501 g_value_set_object (value, page->priv->pixbuf);
502 break;
503 case PROP_VISIBLE:
504 g_value_set_boolean (value, page->priv->visible);
505 break;
506 case PROP_PARENT:
507 g_value_set_object (value, page->priv->parent);
508 break;
509 case PROP_PLUGIN:
510 g_value_set_object (value, page->priv->plugin);
511 break;
512 case PROP_SELECTED:
513 g_value_set_boolean (value, page->priv->selected);
514 break;
515 default:
516 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
517 break;
518 }
519 }
520
521 static void
522 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
523 {
524 RBDisplayPage *page = RB_DISPLAY_PAGE (object);
525
526 switch (prop_id) {
527 case PROP_SHELL:
528 page->priv->shell = g_value_get_object (value);
529 break;
530 case PROP_NAME:
531 g_free (page->priv->name);
532 page->priv->name = g_value_dup_string (value);
533 break;
534 case PROP_PIXBUF:
535 if (page->priv->pixbuf) {
536 g_object_unref (page->priv->pixbuf);
537 }
538 page->priv->pixbuf = g_value_dup_object (value);
539 break;
540 case PROP_VISIBLE:
541 page->priv->visible = g_value_get_boolean (value);
542 break;
543 case PROP_PARENT:
544 page->priv->parent = g_value_get_object (value);
545 break;
546 case PROP_PLUGIN:
547 page->priv->plugin = g_value_get_object (value);
548 break;
549 default:
550 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
551 break;
552 }
553 }
554
555 static void
556 impl_delete_thyself (RBDisplayPage *page)
557 {
558 }
559
560 static void
561 impl_selected (RBDisplayPage *page)
562 {
563 }
564
565 static void
566 impl_deselected (RBDisplayPage *page)
567 {
568 }
569
570 static void
571 impl_dispose (GObject *object)
572 {
573 RBDisplayPage *page;
574
575 g_return_if_fail (object != NULL);
576 g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
577 page = RB_DISPLAY_PAGE (object);
578
579 rb_debug ("Disposing page %s", page->priv->name);
580 if (page->priv->pixbuf != NULL) {
581 g_object_unref (page->priv->pixbuf);
582 page->priv->pixbuf = NULL;
583 }
584
585 G_OBJECT_CLASS (rb_display_page_parent_class)->dispose (object);
586 }
587
588 static void
589 impl_finalize (GObject *object)
590 {
591 RBDisplayPage *page;
592
593 g_return_if_fail (object != NULL);
594 g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
595 page = RB_DISPLAY_PAGE (object);
596
597 rb_debug ("finalizing page %s", page->priv->name);
598
599 g_free (page->priv->name);
600
601 G_OBJECT_CLASS (rb_display_page_parent_class)->finalize (object);
602 }
603
604 static void
605 rb_display_page_init (RBDisplayPage *page)
606 {
607 page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, RB_TYPE_DISPLAY_PAGE, RBDisplayPagePrivate);
608
609 page->priv->visible = TRUE;
610 }
611
612 static void
613 rb_display_page_class_init (RBDisplayPageClass *klass)
614 {
615 GObjectClass *object_class = G_OBJECT_CLASS (klass);
616
617 object_class->dispose = impl_dispose;
618 object_class->finalize = impl_finalize;
619
620 object_class->set_property = impl_set_property;
621 object_class->get_property = impl_get_property;
622
623 klass->selected = impl_selected;
624 klass->deselected = impl_deselected;
625 klass->delete_thyself = impl_delete_thyself;
626
627 /**
628 * RBDisplayPage:shell:
629 *
630 * The rhythmbox shell object
631 */
632 g_object_class_install_property (object_class,
633 PROP_SHELL,
634 g_param_spec_object ("shell",
635 "RBShell",
636 "RBShell object",
637 RB_TYPE_SHELL,
638 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
639 /**
640 * RBDisplayPage:ui-manager:
641 *
642 * The Gtk UIManager object
643 */
644 g_object_class_install_property (object_class,
645 PROP_UI_MANAGER,
646 g_param_spec_object ("ui-manager",
647 "GtkUIManager",
648 "GtkUIManager object",
649 GTK_TYPE_UI_MANAGER,
650 G_PARAM_READABLE));
651 /**
652 * RBDisplayPage:name:
653 *
654 * Page name as displayed in the tree
655 */
656 g_object_class_install_property (object_class,
657 PROP_NAME,
658 g_param_spec_string ("name",
659 "UI name",
660 "Interface name",
661 NULL,
662 G_PARAM_READWRITE));
663 /**
664 * RBDisplayPage:pixbuf:
665 *
666 * Pixbuf to display in the page tree
667 */
668 g_object_class_install_property (object_class,
669 PROP_PIXBUF,
670 g_param_spec_object ("pixbuf",
671 "Pixbuf",
672 "Page pixbuf",
673 GDK_TYPE_PIXBUF,
674 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
675 /**
676 * RBDisplayPage:visibility:
677 *
678 * If FALSE, the page will not be displayed in the tree
679 */
680 g_object_class_install_property (object_class,
681 PROP_VISIBLE,
682 g_param_spec_boolean ("visibility",
683 "visibility",
684 "Whether the page should be displayed in the tree",
685 TRUE,
686 G_PARAM_READWRITE));
687 /**
688 * RBDisplayPage:parent:
689 *
690 * The parent page in the tree (may be NULL)
691 */
692 g_object_class_install_property (object_class,
693 PROP_PARENT,
694 g_param_spec_object ("parent",
695 "Parent",
696 "Parent page",
697 RB_TYPE_DISPLAY_PAGE,
698 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
699 /**
700 * RBDisplayPage:plugin:
701 *
702 * The plugin that created this page.
703 */
704 g_object_class_install_property (object_class,
705 PROP_PLUGIN,
706 g_param_spec_object ("plugin",
707 "plugin instance",
708 "plugin instance that created the page",
709 G_TYPE_OBJECT,
710 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
711 /**
712 * RBDisplayPage:selected:
713 *
714 * TRUE when the page is selected in the page tree.
715 */
716 g_object_class_install_property (object_class,
717 PROP_SELECTED,
718 g_param_spec_boolean ("selected",
719 "selected",
720 "Whether the page is currently selected",
721 FALSE,
722 G_PARAM_READABLE));
723 /**
724 * RBDisplayPage::deleted:
725 * @page: the #RBDisplayPage
726 *
727 * Emitted when the page is being deleted.
728 */
729 signals[DELETED] =
730 g_signal_new ("deleted",
731 RB_TYPE_DISPLAY_PAGE,
732 G_SIGNAL_RUN_LAST,
733 G_STRUCT_OFFSET (RBDisplayPageClass, deleted),
734 NULL, NULL,
735 g_cclosure_marshal_VOID__VOID,
736 G_TYPE_NONE,
737 0);
738 /**
739 * RBDisplayPage::status-changed:
740 * @page: the #RBDisplayPage
741 *
742 * Emitted when the page's status changes.
743 */
744 signals[STATUS_CHANGED] =
745 g_signal_new ("status_changed",
746 RB_TYPE_DISPLAY_PAGE,
747 G_SIGNAL_RUN_LAST,
748 G_STRUCT_OFFSET (RBDisplayPageClass, status_changed),
749 NULL, NULL,
750 g_cclosure_marshal_VOID__VOID,
751 G_TYPE_NONE,
752 0);
753
754 g_type_class_add_private (object_class, sizeof (RBDisplayPagePrivate));
755 }
756
757 /* introspection annotations for vmethods */
758
759 /**
760 * impl_get_status:
761 * @source: a #RBSource
762 * @text: (inout) (allow-none) (transfer full): holds the returned status text
763 * @progress_text: (inout) (allow-none) (transfer full): holds the returned text for the progress bar
764 * @progress: (inout): holds the progress value
765 */
766
767 /**
768 * impl_get_config_widget:
769 * @source: a #RBSource
770 * @prefs: a #RBShellPreferences
771 *
772 * Return value: (transfer none): configuration widget
773 */