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_BOX)
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 icon 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 GIcon *icon;
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_NAME,
75 PROP_ICON,
76 PROP_VISIBLE,
77 PROP_PARENT,
78 PROP_PLUGIN,
79 PROP_SELECTED,
80 };
81
82 enum
83 {
84 STATUS_CHANGED,
85 DELETED,
86 LAST_SIGNAL
87 };
88
89 static guint signals[LAST_SIGNAL] = { 0 };
90
91 void
_rb_display_page_add_pending_child(RBDisplayPage * page,RBDisplayPage * child)92 _rb_display_page_add_pending_child (RBDisplayPage *page, RBDisplayPage *child)
93 {
94 page->priv->pending_children = g_list_append (page->priv->pending_children, child);
95 }
96
97 GList *
_rb_display_page_get_pending_children(RBDisplayPage * page)98 _rb_display_page_get_pending_children (RBDisplayPage *page)
99 {
100 GList *c = page->priv->pending_children;
101 page->priv->pending_children = NULL;
102 return c;
103 }
104
105 /**
106 * rb_display_age_receive_drag:
107 * @page: a #RBDisplayPage
108 * @data: the selection data
109 *
110 * This is called when the user drags something to the page.
111 * Depending on the drag data type, the data might be a list of
112 * #RhythmDBEntry objects, a list of URIs, or a list of album
113 * or artist or genre names.
114 *
115 * Return value: TRUE if the page accepted the drag data
116 */
117 gboolean
rb_display_page_receive_drag(RBDisplayPage * page,GtkSelectionData * data)118 rb_display_page_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
119 {
120 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
121
122 if (klass->receive_drag)
123 return klass->receive_drag (page, data);
124 else
125 return FALSE;
126 }
127
128 /**
129 * rb_display_page_delete_thyself:
130 * @page: a #RBDisplayPage
131 *
132 * This is called when the page should delete itself.
133 * The 'deleted' signal will be emitted, which removes the page
134 * from the page model. This will not actually dispose of the
135 * page object, so reference counting must still be handled
136 * correctly.
137 */
138 void
rb_display_page_delete_thyself(RBDisplayPage * page)139 rb_display_page_delete_thyself (RBDisplayPage *page)
140 {
141 RBDisplayPageClass *klass;
142
143 g_return_if_fail (page != NULL);
144 if (page->priv->deleted) {
145 rb_debug ("source has already been deleted");
146 return;
147 }
148 page->priv->deleted = TRUE;
149
150 klass = RB_DISPLAY_PAGE_GET_CLASS (page);
151 klass->delete_thyself (page);
152
153 g_signal_emit (G_OBJECT (page), signals[DELETED], 0);
154 }
155
156 /**
157 * rb_display_page_can_remove:
158 * @page: a #RBDisplayPage
159 *
160 * Called to check whether the user is able to remove the page
161 *
162 * Return value: %TRUE if the page can be removed
163 */
164 gboolean
rb_display_page_can_remove(RBDisplayPage * page)165 rb_display_page_can_remove (RBDisplayPage *page)
166 {
167 RBDisplayPageClass *klass;
168 klass = RB_DISPLAY_PAGE_GET_CLASS (page);
169 if (klass->can_remove)
170 return klass->can_remove (page);
171
172 return FALSE;
173 }
174
175 /**
176 * rb_display_page_remove:
177 * @page: a #RBDisplayPage
178 *
179 * Called when the user requests removal of a page.
180 */
181 void
rb_display_page_remove(RBDisplayPage * page)182 rb_display_page_remove (RBDisplayPage *page)
183 {
184 RBDisplayPageClass *klass;
185 klass = RB_DISPLAY_PAGE_GET_CLASS (page);
186 g_assert (klass->remove != NULL);
187 klass->remove (page);
188 }
189
190 /**
191 * rb_display_page_selectable:
192 * @page: a #RBDisplayPage
193 *
194 * Checks if @page can be selected
195 */
196 gboolean
rb_display_page_selectable(RBDisplayPage * page)197 rb_display_page_selectable (RBDisplayPage *page)
198 {
199 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
200 if (klass->selectable)
201 return klass->selectable (page);
202 else
203 return TRUE;
204 }
205
206 /**
207 * rb_display_page_selected:
208 * @page: a #RBDisplayPage
209 *
210 * Called when the page is selected in the page tree.
211 */
212 void
rb_display_page_selected(RBDisplayPage * page)213 rb_display_page_selected (RBDisplayPage *page)
214 {
215 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
216
217 if (klass->selected)
218 klass->selected (page);
219
220 page->priv->selected = TRUE;
221 g_object_notify (G_OBJECT (page), "selected");
222 }
223
224 /**
225 * rb_display_page_deselected:
226 * @page: a #RBDisplayPage
227 *
228 * Called when the page is deselected in the page tree.
229 */
230 void
rb_display_page_deselected(RBDisplayPage * page)231 rb_display_page_deselected (RBDisplayPage *page)
232 {
233 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
234
235 if (klass->deselected)
236 klass->deselected (page);
237
238 page->priv->selected = FALSE;
239 g_object_notify (G_OBJECT (page), "selected");
240 }
241
242 /**
243 * rb_display_page_activate:
244 * @page: a #RBDisplayPage
245 *
246 * Called when the page is activated (double clicked, etc.) in the page tree.
247 */
248 void
rb_display_page_activate(RBDisplayPage * page)249 rb_display_page_activate (RBDisplayPage *page)
250 {
251 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
252
253 if (klass->activate)
254 klass->activate (page);
255 }
256
257
258 /**
259 * rb_display_page_get_config_widget:
260 * @page: a #RBDisplayPage
261 * @prefs: the #RBShellPreferences object
262 *
263 * Source implementations can use this to return an optional
264 * configuration widget. The widget will be displayed in a
265 * page in the preferences dialog.
266 *
267 * Return value: (transfer none): configuration widget
268 */
269 GtkWidget *
rb_display_page_get_config_widget(RBDisplayPage * page,RBShellPreferences * prefs)270 rb_display_page_get_config_widget (RBDisplayPage *page,
271 RBShellPreferences *prefs)
272 {
273 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
274
275 if (klass->get_config_widget) {
276 return klass->get_config_widget (page, prefs);
277 } else {
278 return NULL;
279 }
280 }
281
282 /**
283 * rb_display_page_get_status:
284 * @page: a #RBDisplayPage
285 * @text: (inout) (allow-none) (transfer full): holds the returned status text
286 * @busy: (inout) (allow-none): holds the busy status
287 *
288 * Retrieves status details for the page.
289 **/
290 void
rb_display_page_get_status(RBDisplayPage * page,char ** text,gboolean * busy)291 rb_display_page_get_status (RBDisplayPage *page,
292 char **text,
293 gboolean *busy)
294 {
295 RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
296
297 if (klass->get_status)
298 klass->get_status (page, text, busy);
299 }
300
301 /**
302 * rb_display_page_notify_status_changed:
303 * @page: a #RBDisplayPage
304 *
305 * Page implementations call this when their status bar information
306 * changes.
307 */
308 void
rb_display_page_notify_status_changed(RBDisplayPage * page)309 rb_display_page_notify_status_changed (RBDisplayPage *page)
310 {
311 g_signal_emit (G_OBJECT (page), signals[STATUS_CHANGED], 0);
312 }
313
314 typedef void (*DisplayPageActionActivateCallback) (GSimpleAction *action, GVariant *parameters, RBDisplayPage *page);
315 typedef void (*DisplayPageActionChangeStateCallback) (GSimpleAction *action, GVariant *value, RBDisplayPage *page);
316
317 typedef struct {
318 union {
319 DisplayPageActionActivateCallback gaction;
320 DisplayPageActionChangeStateCallback gactionstate;
321 } u;
322 gpointer shell;
323 } DisplayPageActionData;
324
325 static void
display_page_action_data_destroy(DisplayPageActionData * data)326 display_page_action_data_destroy (DisplayPageActionData *data)
327 {
328 if (data->shell != NULL) {
329 g_object_remove_weak_pointer (G_OBJECT (data->shell), &data->shell);
330 }
331 g_slice_free (DisplayPageActionData, data);
332 }
333
334 static void
display_page_action_activate_cb(GSimpleAction * action,GVariant * parameters,DisplayPageActionData * data)335 display_page_action_activate_cb (GSimpleAction *action, GVariant *parameters, DisplayPageActionData *data)
336 {
337 RBDisplayPage *page;
338
339 if (data->shell == NULL) {
340 return;
341 }
342
343 g_object_get (data->shell, "selected-page", &page, NULL);
344 if (page != NULL) {
345 data->u.gaction (action, parameters, page);
346 g_object_unref (page);
347 }
348 }
349
350 static void
display_page_action_change_state_cb(GSimpleAction * action,GVariant * value,DisplayPageActionData * data)351 display_page_action_change_state_cb (GSimpleAction *action, GVariant *value, DisplayPageActionData *data)
352 {
353 RBDisplayPage *page;
354
355 if (data->shell == NULL) {
356 return;
357 }
358
359 g_object_get (data->shell, "selected-page", &page, NULL);
360 if (page != NULL) {
361 data->u.gactionstate (action, value, page);
362 g_object_unref (page);
363 }
364 }
365
366 void
_rb_add_display_page_actions(GActionMap * map,GObject * shell,const GActionEntry * actions,gint n_entries)367 _rb_add_display_page_actions (GActionMap *map, GObject *shell, const GActionEntry *actions, gint n_entries)
368 {
369 int i;
370 for (i = 0; i < n_entries; i++) {
371 GSimpleAction *action;
372 const GVariantType *parameter_type;
373 DisplayPageActionData *page_action_data;
374
375 if (g_action_map_lookup_action (map, actions[i].name) != NULL) {
376 /* action was already added */
377 continue;
378 }
379
380 if (actions[i].parameter_type) {
381 parameter_type = G_VARIANT_TYPE (actions[i].parameter_type);
382 } else {
383 parameter_type = NULL;
384 }
385
386 if (actions[i].state) {
387 GVariant *state;
388 GError *error = NULL;
389 state = g_variant_parse (NULL, actions[i].state, NULL, NULL, &error);
390 if (state == NULL) {
391 g_critical ("could not parse state value '%s' for action "
392 "%s: %s",
393 actions[i].state, actions[i].name, error->message);
394 g_error_free (error);
395 continue;
396 }
397 action = g_simple_action_new_stateful (actions[i].name,
398 parameter_type,
399 state);
400 } else {
401 action = g_simple_action_new (actions[i].name, parameter_type);
402 }
403
404 if (actions[i].activate) {
405 GClosure *closure;
406 page_action_data = g_slice_new0 (DisplayPageActionData);
407 page_action_data->u.gaction = (DisplayPageActionActivateCallback) actions[i].activate;
408 page_action_data->shell = shell;
409 g_object_add_weak_pointer (shell, &page_action_data->shell);
410
411 closure = g_cclosure_new (G_CALLBACK (display_page_action_activate_cb),
412 page_action_data,
413 (GClosureNotify) display_page_action_data_destroy);
414 g_signal_connect_closure (action, "activate", closure, FALSE);
415 }
416
417 if (actions[i].change_state) {
418 GClosure *closure;
419 page_action_data = g_slice_new0 (DisplayPageActionData);
420 page_action_data->u.gactionstate = (DisplayPageActionChangeStateCallback) actions[i].change_state;
421 page_action_data->shell = shell;
422 g_object_add_weak_pointer (shell, &page_action_data->shell);
423
424 closure = g_cclosure_new (G_CALLBACK (display_page_action_change_state_cb),
425 page_action_data,
426 (GClosureNotify) display_page_action_data_destroy);
427 g_signal_connect_closure (action, "change-state", closure, FALSE);
428 }
429
430 g_action_map_add_action (map, G_ACTION (action));
431 g_object_unref (action);
432 }
433 }
434
435 /**
436 * rb_display_page_set_icon_name:
437 * @page: a #RBDisplayPage
438 * @icon_name: icon name to use
439 *
440 * Sets the icon for the page to the specified icon name.
441 */
442 void
rb_display_page_set_icon_name(RBDisplayPage * page,const char * icon_name)443 rb_display_page_set_icon_name (RBDisplayPage *page, const char *icon_name)
444 {
445 GIcon *icon;
446
447 icon = g_themed_icon_new (icon_name);
448 g_object_set (page, "icon", icon, NULL);
449 g_object_unref (icon);
450 }
451
452 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)453 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
454 {
455 RBDisplayPage *page = RB_DISPLAY_PAGE (object);
456
457 switch (prop_id) {
458 case PROP_SHELL:
459 g_value_set_object (value, page->priv->shell);
460 break;
461 case PROP_NAME:
462 g_value_set_string (value, page->priv->name);
463 break;
464 case PROP_ICON:
465 g_value_set_object (value, page->priv->icon);
466 break;
467 case PROP_VISIBLE:
468 g_value_set_boolean (value, page->priv->visible);
469 break;
470 case PROP_PARENT:
471 g_value_set_object (value, page->priv->parent);
472 break;
473 case PROP_PLUGIN:
474 g_value_set_object (value, page->priv->plugin);
475 break;
476 case PROP_SELECTED:
477 g_value_set_boolean (value, page->priv->selected);
478 break;
479 default:
480 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
481 break;
482 }
483 }
484
485 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)486 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
487 {
488 RBDisplayPage *page = RB_DISPLAY_PAGE (object);
489
490 switch (prop_id) {
491 case PROP_SHELL:
492 page->priv->shell = g_value_get_object (value);
493 break;
494 case PROP_NAME:
495 g_free (page->priv->name);
496 page->priv->name = g_value_dup_string (value);
497 break;
498 case PROP_ICON:
499 g_clear_object (&page->priv->icon);
500 page->priv->icon = g_value_dup_object (value);
501 break;
502 case PROP_VISIBLE:
503 page->priv->visible = g_value_get_boolean (value);
504 break;
505 case PROP_PARENT:
506 page->priv->parent = g_value_get_object (value);
507 break;
508 case PROP_PLUGIN:
509 page->priv->plugin = g_value_get_object (value);
510 break;
511 default:
512 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
513 break;
514 }
515 }
516
517 static void
impl_delete_thyself(RBDisplayPage * page)518 impl_delete_thyself (RBDisplayPage *page)
519 {
520 }
521
522 static void
impl_selected(RBDisplayPage * page)523 impl_selected (RBDisplayPage *page)
524 {
525 }
526
527 static void
impl_deselected(RBDisplayPage * page)528 impl_deselected (RBDisplayPage *page)
529 {
530 }
531
532 static void
impl_dispose(GObject * object)533 impl_dispose (GObject *object)
534 {
535 RBDisplayPage *page;
536
537 g_return_if_fail (object != NULL);
538 g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
539 page = RB_DISPLAY_PAGE (object);
540
541 rb_debug ("Disposing page %s", page->priv->name);
542 g_clear_object (&page->priv->icon);
543
544 G_OBJECT_CLASS (rb_display_page_parent_class)->dispose (object);
545 }
546
547 static void
impl_finalize(GObject * object)548 impl_finalize (GObject *object)
549 {
550 RBDisplayPage *page;
551
552 g_return_if_fail (object != NULL);
553 g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
554 page = RB_DISPLAY_PAGE (object);
555
556 rb_debug ("finalizing page %s", page->priv->name);
557
558 g_free (page->priv->name);
559
560 G_OBJECT_CLASS (rb_display_page_parent_class)->finalize (object);
561 }
562
563 static void
rb_display_page_init(RBDisplayPage * page)564 rb_display_page_init (RBDisplayPage *page)
565 {
566 gtk_orientable_set_orientation (GTK_ORIENTABLE (page), GTK_ORIENTATION_HORIZONTAL);
567 page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, RB_TYPE_DISPLAY_PAGE, RBDisplayPagePrivate);
568
569 page->priv->visible = TRUE;
570 }
571
572 static void
rb_display_page_class_init(RBDisplayPageClass * klass)573 rb_display_page_class_init (RBDisplayPageClass *klass)
574 {
575 GObjectClass *object_class = G_OBJECT_CLASS (klass);
576
577 object_class->dispose = impl_dispose;
578 object_class->finalize = impl_finalize;
579
580 object_class->set_property = impl_set_property;
581 object_class->get_property = impl_get_property;
582
583 klass->selected = impl_selected;
584 klass->deselected = impl_deselected;
585 klass->delete_thyself = impl_delete_thyself;
586
587 /**
588 * RBDisplayPage:shell:
589 *
590 * The rhythmbox shell object
591 */
592 g_object_class_install_property (object_class,
593 PROP_SHELL,
594 g_param_spec_object ("shell",
595 "RBShell",
596 "RBShell object",
597 RB_TYPE_SHELL,
598 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
599 /**
600 * RBDisplayPage:name:
601 *
602 * Page name as displayed in the tree
603 */
604 g_object_class_install_property (object_class,
605 PROP_NAME,
606 g_param_spec_string ("name",
607 "UI name",
608 "Interface name",
609 NULL,
610 G_PARAM_READWRITE));
611 /**
612 * RBDisplayPage:icon:
613 *
614 * Icon to display in the page tree
615 */
616 g_object_class_install_property (object_class,
617 PROP_ICON,
618 g_param_spec_object ("icon",
619 "Icon",
620 "Page icon",
621 G_TYPE_ICON,
622 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
623 /**
624 * RBDisplayPage:visibility:
625 *
626 * If FALSE, the page will not be displayed in the tree
627 */
628 g_object_class_install_property (object_class,
629 PROP_VISIBLE,
630 g_param_spec_boolean ("visibility",
631 "visibility",
632 "Whether the page should be displayed in the tree",
633 TRUE,
634 G_PARAM_READWRITE));
635 /**
636 * RBDisplayPage:parent:
637 *
638 * The parent page in the tree (may be NULL)
639 */
640 g_object_class_install_property (object_class,
641 PROP_PARENT,
642 g_param_spec_object ("parent",
643 "Parent",
644 "Parent page",
645 RB_TYPE_DISPLAY_PAGE,
646 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
647 /**
648 * RBDisplayPage:plugin:
649 *
650 * The plugin that created this page.
651 */
652 g_object_class_install_property (object_class,
653 PROP_PLUGIN,
654 g_param_spec_object ("plugin",
655 "plugin instance",
656 "plugin instance that created the page",
657 G_TYPE_OBJECT,
658 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
659 /**
660 * RBDisplayPage:selected:
661 *
662 * TRUE when the page is selected in the page tree.
663 */
664 g_object_class_install_property (object_class,
665 PROP_SELECTED,
666 g_param_spec_boolean ("selected",
667 "selected",
668 "Whether the page is currently selected",
669 FALSE,
670 G_PARAM_READABLE));
671 /**
672 * RBDisplayPage::deleted:
673 * @page: the #RBDisplayPage
674 *
675 * Emitted when the page is being deleted.
676 */
677 signals[DELETED] =
678 g_signal_new ("deleted",
679 RB_TYPE_DISPLAY_PAGE,
680 G_SIGNAL_RUN_LAST,
681 G_STRUCT_OFFSET (RBDisplayPageClass, deleted),
682 NULL, NULL,
683 NULL,
684 G_TYPE_NONE,
685 0);
686 /**
687 * RBDisplayPage::status-changed:
688 * @page: the #RBDisplayPage
689 *
690 * Emitted when the page's status changes.
691 */
692 signals[STATUS_CHANGED] =
693 g_signal_new ("status_changed",
694 RB_TYPE_DISPLAY_PAGE,
695 G_SIGNAL_RUN_LAST,
696 G_STRUCT_OFFSET (RBDisplayPageClass, status_changed),
697 NULL, NULL,
698 NULL,
699 G_TYPE_NONE,
700 0);
701
702 g_type_class_add_private (object_class, sizeof (RBDisplayPagePrivate));
703 }
704