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