Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
rb-shell.c:478:3 | clang-analyzer | Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value') | ||
rb-shell.c:528:3 | clang-analyzer | Access to field 'g_type' results in a dereference of a null pointer (loaded from variable 'value') |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002, 2003 Jorn Baayen
4 * Copyright (C) 2003, 2004 Colin Walters <walters@gnome.org>
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, or (at your option)
9 * 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 /**
31 * SECTION:rb-shell
32 * @short_description: holds the Rhythmbox main window and everything else
33 *
34 * RBShell is the main application class in Rhythmbox. It creates and holds
35 * references to the other main objects (#RBShellPlayer, #RhythmDB, #RBDisplayPageTree),
36 * constructs the main window UI, and provides the basic DBus interface.
37 */
38
39 #include <config.h>
40
41 #include <string.h>
42 #include <ctype.h>
43 #include <stdio.h>
44 #include <sys/stat.h>
45
46 #include <glib/gi18n.h>
47 #include <gdk/gdk.h>
48 #include <gdk/gdkx.h>
49 #include <gtk/gtk.h>
50 #include <girepository.h>
51
52 #include <libpeas/peas.h>
53 #include <libpeas-gtk/peas-gtk.h>
54
55 #include <gst/gst.h>
56
57 #ifdef HAVE_MMKEYS
58 #include <X11/XF86keysym.h>
59 #endif /* HAVE_MMKEYS */
60
61 #include "rb-shell.h"
62 #include "rb-debug.h"
63 #include "rb-dialog.h"
64 #ifdef WITH_RHYTHMDB_TREE
65 #include "rhythmdb-tree.h"
66 #else
67 #error "no database specified. configure broken?"
68 #endif
69 #include "rb-stock-icons.h"
70 #include "rb-display-page-tree.h"
71 #include "rb-display-page-group.h"
72 #include "rb-file-helpers.h"
73 #include "rb-source.h"
74 #include "rb-playlist-manager.h"
75 #include "rb-removable-media-manager.h"
76 #include "rb-track-transfer-queue.h"
77 #include "rb-shell-clipboard.h"
78 #include "rb-shell-player.h"
79 #include "rb-statusbar.h"
80 #include "rb-shell-preferences.h"
81 #include "rb-library-source.h"
82 #include "rb-podcast-source.h"
83 #include "totem-pl-parser.h"
84 #include "rb-shell-preferences.h"
85 #include "rb-playlist-source.h"
86 #include "rb-static-playlist-source.h"
87 #include "rb-play-queue-source.h"
88 #include "rb-missing-files-source.h"
89 #include "rb-import-errors-source.h"
90 #include "rb-util.h"
91 #include "rb-display-page-model.h"
92 #include "rb-song-info.h"
93 #include "rb-marshal.h"
94 #include "rb-missing-plugins.h"
95 #include "rb-header.h"
96 #include "rb-podcast-manager.h"
97 #include "rb-podcast-main-source.h"
98 #include "rb-podcast-entry-types.h"
99 #include "rb-ext-db.h"
100 #include "rb-auto-playlist-source.h"
101
102 #include "eggsmclient.h"
103
104 #define UNINSTALLED_PLUGINS_LOCATION "plugins"
105
106 #define PLAYING_ENTRY_NOTIFY_TIME 4
107
108 static void rb_shell_class_init (RBShellClass *klass);
109 static void rb_shell_init (RBShell *shell);
110 static void rb_shell_constructed (GObject *object);
111 static void rb_shell_finalize (GObject *object);
112 static void rb_shell_set_property (GObject *object,
113 guint prop_id,
114 const GValue *value,
115 GParamSpec *pspec);
116 static void rb_shell_get_property (GObject *object,
117 guint prop_id,
118 GValue *value,
119 GParamSpec *pspec);
120 static void rb_shell_activate (GApplication *app);
121 static void rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint);
122 static gboolean rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status);
123 static gboolean rb_shell_get_visibility (RBShell *shell);
124 static gboolean rb_shell_window_state_cb (GtkWidget *widget,
125 GdkEventWindowState *event,
126 RBShell *shell);
127 static gboolean rb_shell_window_configure_cb (GtkWidget *win,
128 GdkEventConfigure*event,
129 RBShell *shell);
130 static gboolean rb_shell_window_delete_cb (GtkWidget *win,
131 GdkEventAny *event,
132 RBShell *shell);
133 static gboolean rb_shell_key_press_event_cb (GtkWidget *win,
134 GdkEventKey *event,
135 RBShell *shell);
136 static void rb_shell_sync_window_state (RBShell *shell, gboolean dont_maximise);
137 static void rb_shell_sync_paned (RBShell *shell);
138 static void rb_shell_sync_party_mode (RBShell *shell);
139 static void rb_shell_select_page (RBShell *shell, RBDisplayPage *display_page);
140 static void display_page_selected_cb (RBDisplayPageTree *display_page_tree,
141 RBDisplayPage *page,
142 RBShell *shell);
143 static void rb_shell_playing_source_changed_cb (RBShellPlayer *player,
144 RBSource *source,
145 RBShell *shell);
146 static void rb_shell_playing_from_queue_cb (RBShellPlayer *player,
147 GParamSpec *arg,
148 RBShell *shell);
149 static void rb_shell_db_save_error_cb (RhythmDB *db,
150 const char *uri, const GError *error,
151 RBShell *shell);
152
153 static void rb_shell_playlist_added_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
154 static void rb_shell_playlist_created_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
155 static void rb_shell_medium_added_cb (RBRemovableMediaManager *mgr, RBSource *source, RBShell *shell);
156 static void rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell);
157 static void rb_shell_set_window_title (RBShell *shell, const char *window_title);
158 static void rb_shell_player_window_title_changed_cb (RBShellPlayer *player,
159 const char *window_title,
160 RBShell *shell);
161 static void rb_shell_cmd_about (GtkAction *action,
162 RBShell *shell);
163 static void rb_shell_cmd_contents (GtkAction *action,
164 RBShell *shell);
165 static void rb_shell_cmd_quit (GtkAction *action,
166 RBShell *shell);
167 static void rb_shell_cmd_preferences (GtkAction *action,
168 RBShell *shell);
169 static void rb_shell_cmd_plugins (GtkAction *action,
170 RBShell *shell);
171 static void rb_shell_cmd_add_music (GtkAction *action, RBShell *shell);
172
173 static void rb_shell_cmd_current_song (GtkAction *action,
174 RBShell *shell);
175 static void rb_shell_jump_to_current (RBShell *shell);
176 static void rb_shell_jump_to_entry_with_source (RBShell *shell, RBSource *source,
177 RhythmDBEntry *entry);
178 static void rb_shell_play_entry (RBShell *shell, RhythmDBEntry *entry);
179 static void rb_shell_cmd_view_all (GtkAction *action,
180 RBShell *shell);
181 static void rb_shell_view_party_mode_changed_cb (GtkAction *action,
182 RBShell *shell);
183 static void rb_shell_view_statusbar_changed_cb (GtkAction *action,
184 RBShell *shell);
185 static void rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
186 RBShell *shell);
187 static void rb_shell_load_complete_cb (RhythmDB *db, RBShell *shell);
188 static void rb_shell_sync_pane_visibility (RBShell *shell);
189 static void rb_shell_sync_statusbar_visibility (RBShell *shell);
190 static void rb_shell_set_visibility (RBShell *shell,
191 gboolean initial,
192 gboolean visible);
193 static void display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
194 RBDisplayPage *page,
195 GtkSelectionData *data,
196 RBShell *shell);
197
198 static void paned_size_allocate_cb (GtkWidget *widget,
199 GtkAllocation *allocation,
200 RBShell *shell);
201 static void rb_shell_volume_widget_changed_cb (GtkScaleButton *vol,
202 gdouble volume,
203 RBShell *shell);
204 static void rb_shell_player_volume_changed_cb (RBShellPlayer *player,
205 GParamSpec *arg,
206 RBShell *shell);
207
208 static void rb_shell_session_init (RBShell *shell);
209
210 static gboolean rb_shell_visibility_changing (RBShell *shell, gboolean initial, gboolean visible);
211
212 enum
213 {
214 PROP_NONE,
215 PROP_NO_REGISTRATION,
216 PROP_NO_UPDATE,
217 PROP_DRY_RUN,
218 PROP_RHYTHMDB_FILE,
219 PROP_PLAYLISTS_FILE,
220 PROP_SELECTED_PAGE,
221 PROP_DB,
222 PROP_UI_MANAGER,
223 PROP_CLIPBOARD,
224 PROP_PLAYLIST_MANAGER,
225 PROP_REMOVABLE_MEDIA_MANAGER,
226 PROP_SHELL_PLAYER,
227 PROP_WINDOW,
228 PROP_PREFS,
229 PROP_QUEUE_SOURCE,
230 PROP_PROXY_CONFIG,
231 PROP_LIBRARY_SOURCE,
232 PROP_DISPLAY_PAGE_MODEL,
233 PROP_DISPLAY_PAGE_TREE,
234 PROP_VISIBILITY,
235 PROP_TRACK_TRANSFER_QUEUE,
236 PROP_AUTOSTARTED,
237 PROP_DISABLE_PLUGINS
238 };
239
240 enum
241 {
242 VISIBILITY_CHANGED,
243 VISIBILITY_CHANGING,
244 CREATE_SONG_INFO,
245 NOTIFY_PLAYING_ENTRY,
246 NOTIFY_CUSTOM,
247 LAST_SIGNAL
248 };
249
250 static guint rb_shell_signals[LAST_SIGNAL] = { 0 };
251
252 G_DEFINE_TYPE (RBShell, rb_shell, GTK_TYPE_APPLICATION)
253
254 struct _RBShellPrivate
255 {
256 GtkWidget *window;
257 gboolean iconified;
258
259 GtkUIManager *ui_manager;
260 GtkActionGroup *actiongroup;
261 guint source_ui_merge_id;
262
263 GtkWidget *main_vbox;
264 GtkWidget *paned;
265 GtkWidget *right_paned;
266 RBDisplayPageTree *display_page_tree;
267 GtkWidget *notebook;
268 GtkWidget *queue_paned;
269 GtkWidget *queue_sidebar;
270
271 GtkBox *sidebar_container;
272 GtkBox *right_sidebar_container;
273 GtkBox *top_container;
274 GtkBox *bottom_container;
275 guint right_sidebar_widget_count;
276
277 RBDisplayPageModel *display_page_model;
278 GList *sources; /* kill? */
279 GHashTable *sources_hash; /* kill? */
280
281 guint async_state_save_id;
282 guint save_playlist_id;
283
284 gboolean shutting_down;
285 gboolean load_complete;
286
287 gboolean no_registration;
288 gboolean no_update;
289 gboolean dry_run;
290 gboolean autostarted;
291 gboolean disable_plugins;
292 char *rhythmdb_file;
293 char *playlists_file;
294
295 RhythmDB *db;
296 RBExtDB *art_store;
297
298 RBShellPlayer *player_shell;
299 RBShellClipboard *clipboard_shell;
300 RBHeader *header;
301 RBStatusbar *statusbar;
302 RBPlaylistManager *playlist_manager;
303 RBRemovableMediaManager *removable_media_manager;
304 RBTrackTransferQueue *track_transfer_queue;
305 RBPodcastManager *podcast_manager;
306
307 RBLibrarySource *library_source;
308 RBPodcastSource *podcast_source;
309 RBPlaylistSource *queue_source;
310 RBSource *missing_files_source;
311 RBSource *import_errors_source;
312
313 RBDisplayPage *selected_page;
314
315 GtkWidget *prefs;
316 GtkWidget *plugins;
317
318 GtkWidget *volume_button;
319 gboolean syncing_volume;
320
321 char *cached_title;
322 gboolean cached_playing;
323
324 gboolean party_mode;
325
326 GSettings *settings;
327
328 GSettings *plugin_settings;
329 PeasEngine *plugin_engine;
330 PeasExtensionSet *activatable;
331 };
332
333
334 static GtkActionEntry rb_shell_actions [] =
335 {
336 { "Music", NULL, N_("_Music") },
337 { "Edit", NULL, N_("_Edit") },
338 { "View", NULL, N_("_View") },
339 { "Control", NULL, N_("_Control") },
340 { "Tools", NULL, N_("_Tools") },
341 { "Help", NULL, N_("_Help") },
342
343 { "MusicAdd", GTK_STOCK_OPEN, N_("Add Music..."), "<control>O",
344 N_("Add music to the library"),
345 G_CALLBACK (rb_shell_cmd_add_music) },
346 { "HelpAbout", GTK_STOCK_ABOUT, N_("_About"), NULL,
347 N_("Show information about Rhythmbox"),
348 G_CALLBACK (rb_shell_cmd_about) },
349 { "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1",
350 N_("Display Rhythmbox help"),
351 G_CALLBACK (rb_shell_cmd_contents) },
352 { "MusicQuit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q",
353 N_("Quit Rhythmbox"),
354 G_CALLBACK (rb_shell_cmd_quit) },
355 { "EditPreferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL,
356 N_("Edit Rhythmbox preferences"),
357 G_CALLBACK (rb_shell_cmd_preferences) },
358 { "EditPlugins", NULL, N_("Plu_gins"), NULL,
359 N_("Change and configure plugins"),
360 G_CALLBACK (rb_shell_cmd_plugins) },
361 { "ViewAll", NULL, N_("Show _All Tracks"), "<control>Y",
362 N_("Show all tracks in this music source"),
363 G_CALLBACK (rb_shell_cmd_view_all) },
364 { "ViewJumpToPlaying", GTK_STOCK_JUMP_TO, N_("_Jump to Playing Song"), "<control>J",
365 N_("Scroll the view to the currently playing song"),
366 G_CALLBACK (rb_shell_cmd_current_song) },
367 };
368 static guint rb_shell_n_actions = G_N_ELEMENTS (rb_shell_actions);
369
370 static GtkToggleActionEntry rb_shell_toggle_entries [] =
371 {
372 { "ViewSidePane", NULL, N_("Side _Pane"), "F9",
373 N_("Change the visibility of the side pane"),
374 NULL, TRUE },
375 { "ViewPartyMode", NULL, N_("Party _Mode"), "F11",
376 N_("Change the status of the party mode"),
377 G_CALLBACK (rb_shell_view_party_mode_changed_cb), FALSE },
378 { "ViewQueueAsSidebar", NULL, N_("Play _Queue as Side Pane"), "<control>K",
379 N_("Change whether the queue is visible as a source or a sidebar"),
380 G_CALLBACK (rb_shell_view_queue_as_sidebar_changed_cb) },
381 { "ViewStatusbar", NULL, N_("S_tatusbar"), NULL,
382 N_("Change the visibility of the statusbar"),
383 G_CALLBACK (rb_shell_view_statusbar_changed_cb), TRUE },
384 { "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL,
385 N_("Change the visibility of the song position slider"),
386 NULL, TRUE },
387 { "ViewAlbumArt", NULL, N_("_Album Art"), NULL,
388 N_("Change the visibility of the album art display"),
389 NULL, TRUE },
390 { "ViewBrowser", NULL, N_("_Browse"), "<control>B",
391 N_("Change the visibility of the browser"),
392 NULL, TRUE }
393 };
394 static guint rb_shell_n_toggle_entries = G_N_ELEMENTS (rb_shell_toggle_entries);
395
396 static void
397 rb_shell_activate (GApplication *app)
398 {
399 rb_shell_present (RB_SHELL (app), gtk_get_current_event_time (), NULL);
400 }
401
402 static void
403 rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint)
404 {
405 int i;
406
407 for (i = 0; i < n_files; i++) {
408 char *uri;
409
410 uri = g_file_get_uri (files[i]);
411
412 /*
413 * rb_uri_exists won't work if the location isn't mounted.
414 * however, things that are interesting to mount are generally
415 * non-local, so we'll process them anyway.
416 */
417 if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
418 rb_shell_load_uri (RB_SHELL (app), uri, TRUE, NULL);
419 }
420 g_free (uri);
421 }
422 }
423
424 static void
425 load_state_changed_cb (GActionGroup *action_group, const char *action_name, GVariant *state, GPtrArray *files)
426 {
427 gboolean loaded;
428 gboolean scanned;
429
430 if (g_strcmp0 (action_name, "LoadURI") != 0) {
431 return;
432 }
433
434 g_variant_get (state, "(bb)", &loaded, &scanned);
435 if (loaded) {
436 rb_debug ("opening files now");
437 g_signal_handlers_disconnect_by_func (action_group, load_state_changed_cb, files);
438
439 g_application_open (G_APPLICATION (action_group), (GFile **)files->pdata, files->len, "");
440 g_ptr_array_free (files, TRUE);
441 }
442 }
443
444
445
446 static GMountOperation *
447 rb_shell_create_mount_op_cb (RhythmDB *db, RBShell *shell)
448 {
449 /* we don't want the operation to be modal, so we don't associate it with the window. */
450 GMountOperation *op = gtk_mount_operation_new (NULL);
451 gtk_mount_operation_set_screen (GTK_MOUNT_OPERATION (op),
452 gtk_window_get_screen (GTK_WINDOW (shell->priv->window)));
453 return op;
454 }
455
456 static GValue *
457 load_external_art_cb (RBExtDB *store, GValue *value, RBShell *shell)
458 {
459 const char *data;
460 gsize data_size;
461 GdkPixbufLoader *loader;
462 GdkPixbuf *pixbuf;
463 GValue *v;
464 GError *error = NULL;
465
466 if (G_VALUE_HOLDS_STRING (value)) {
467 data = g_value_get_string (value);
468 data_size = strlen (data);
469 } else if (G_VALUE_HOLDS (value, G_TYPE_GSTRING)) {
470 GString *str = g_value_get_boxed (value);
471 data = (const char *)str->str;
472 data_size = str->len;
473 } else if (G_VALUE_HOLDS (value, G_TYPE_BYTE_ARRAY)) {
474 GByteArray *bytes = g_value_get_boxed (value);
475 data = (const char *)bytes->data;
476 data_size = bytes->len;
477 } else {
478 rb_debug ("unable to load pixbufs from values of type %s", G_VALUE_TYPE_NAME (value));
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
479 return NULL;
480 }
481
482 loader = gdk_pixbuf_loader_new ();
483 gdk_pixbuf_loader_write (loader, (const guchar *)data, data_size, &error);
484 if (error != NULL) {
485 rb_debug ("unable to load pixbuf: %s", error->message);
486 g_clear_error (&error);
487 g_object_unref (loader);
488 return NULL;
489 }
490
491 gdk_pixbuf_loader_close (loader, &error);
492 if (error != NULL) {
493 rb_debug ("unable to load pixbuf: %s", error->message);
494 g_clear_error (&error);
495 g_object_unref (loader);
496 return NULL;
497 }
498
499 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
500 v = g_new0 (GValue, 1);
501 g_value_init (v, GDK_TYPE_PIXBUF);
502 g_value_set_object (v, pixbuf);
503 g_object_unref (loader);
504
505 return v;
506 }
507
508 static GValue *
509 store_external_art_cb (RBExtDB *store, GValue *value, RBShell *shell)
510 {
511 const char *jpeg_format = "jpeg";
512 char *jpeg_format_options[] = { "quality", NULL };
513 char *jpeg_format_values[] = { "100", NULL };
514 const char *png_format = "png";
515 char *png_format_options[] = { "compression", NULL };
516 char *png_format_values[] = { "9", NULL };
517 const char *format;
518 char **format_options;
519 char **format_values;
520 GdkPixbuf *pixbuf;
521 char *data;
522 gsize data_size;
523 GError *error = NULL;
524 GString *s;
525 GValue *v;
526
527 if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF) == FALSE) {
528 rb_debug ("can't store values of type %s", G_VALUE_TYPE_NAME (value));
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
529 return NULL;
530 }
531
532 pixbuf = GDK_PIXBUF (g_value_get_object (value));
533
534 /* switch to png if the image has an alpha channel */
535 if (gdk_pixbuf_get_has_alpha (pixbuf)) {
536 format = png_format;
537 format_options = png_format_options;
538 format_values = png_format_values;
539 } else {
540 format = jpeg_format;
541 format_options = jpeg_format_options;
542 format_values = jpeg_format_values;
543 }
544
545 if (gdk_pixbuf_save_to_bufferv (pixbuf, &data, &data_size, format, format_options, format_values, &error) == FALSE) {
546 rb_debug ("unable to save pixbuf: %s", error->message);
547 g_clear_error (&error);
548 return NULL;
549 }
550
551 s = g_slice_new0 (GString);
552 s->str = data;
553 s->len = data_size;
554 s->allocated_len = data_size;
555 v = g_new0 (GValue, 1);
556 g_value_init (v, G_TYPE_GSTRING);
557 g_value_take_boxed (v, s);
558 return v;
559 }
560
561 static void
562 construct_db (RBShell *shell)
563 {
564 char *pathname;
565
566 /* Initialize the database */
567 rb_debug ("creating database object");
568 rb_profile_start ("creating database object");
569
570 if (shell->priv->rhythmdb_file) {
571 pathname = g_strdup (shell->priv->rhythmdb_file);
572 } else {
573 pathname = rb_find_user_data_file ("rhythmdb.xml");
574 }
575
576 #ifdef WITH_RHYTHMDB_TREE
577 shell->priv->db = rhythmdb_tree_new (pathname);
578 #elif defined(WITH_RHYTHMDB_GDA)
579 shell->priv->db = rhythmdb_gda_new (pathname);
580 #endif
581 g_free (pathname);
582
583 if (shell->priv->dry_run)
584 g_object_set (shell->priv->db, "dry-run", TRUE, NULL);
585 if (shell->priv->no_update)
586 g_object_set (shell->priv->db, "no-update", TRUE, NULL);
587
588 g_signal_connect_object (G_OBJECT (shell->priv->db), "load-complete",
589 G_CALLBACK (rb_shell_load_complete_cb), shell,
590 0);
591 g_signal_connect_object (G_OBJECT (shell->priv->db), "create-mount-op",
592 G_CALLBACK (rb_shell_create_mount_op_cb), shell,
593 0);
594
595 shell->priv->art_store = rb_ext_db_new ("album-art");
596 g_signal_connect (shell->priv->art_store, "load", G_CALLBACK (load_external_art_cb), shell);
597 g_signal_connect (shell->priv->art_store, "store", G_CALLBACK (store_external_art_cb), shell);
598
599 rb_profile_end ("creating database object");
600 }
601
602 static void
603 construct_widgets (RBShell *shell)
604 {
605 GtkWindow *win;
606
607 rb_profile_start ("constructing widgets");
608
609 /* initialize UI */
610 win = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
611 gtk_window_set_title (win, _("Rhythmbox"));
612
613 shell->priv->window = GTK_WIDGET (win);
614 shell->priv->iconified = FALSE;
615 g_signal_connect_object (G_OBJECT (win), "window-state-event",
616 G_CALLBACK (rb_shell_window_state_cb),
617 shell, 0);
618
619 g_signal_connect_object (G_OBJECT (win), "configure-event",
620 G_CALLBACK (rb_shell_window_configure_cb),
621 shell, 0);
622
623 /* connect after, so that things can affect behaviour */
624 g_signal_connect_object (G_OBJECT (win), "delete_event",
625 G_CALLBACK (rb_shell_window_delete_cb),
626 shell, G_CONNECT_AFTER);
627
628 gtk_widget_add_events (GTK_WIDGET (win), GDK_KEY_PRESS_MASK);
629 g_signal_connect_object (G_OBJECT(win), "key_press_event",
630 G_CALLBACK (rb_shell_key_press_event_cb), shell, 0);
631
632 rb_debug ("shell: initializing shell services");
633
634 shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db);
635 shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell);
636 shell->priv->ui_manager = gtk_ui_manager_new ();
637 shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager);
638
639 shell->priv->player_shell = rb_shell_player_new (shell->priv->db,
640 shell->priv->ui_manager,
641 shell->priv->actiongroup);
642 g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
643 "playing-source-changed",
644 G_CALLBACK (rb_shell_playing_source_changed_cb),
645 shell, 0);
646 g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
647 "notify::playing-from-queue",
648 G_CALLBACK (rb_shell_playing_from_queue_cb),
649 shell, 0);
650 g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
651 "window_title_changed",
652 G_CALLBACK (rb_shell_player_window_title_changed_cb),
653 shell, 0);
654 shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup,
655 shell->priv->ui_manager,
656 shell->priv->db);
657
658 shell->priv->display_page_tree = rb_display_page_tree_new (shell);
659 gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
660 g_signal_connect_object (shell->priv->display_page_tree, "drop-received",
661 G_CALLBACK (display_page_tree_drag_received_cb), shell, 0);
662 g_object_get (shell->priv->display_page_tree, "model", &shell->priv->display_page_model, NULL);
663 rb_display_page_group_add_core_groups (G_OBJECT (shell), shell->priv->display_page_model);
664
665 shell->priv->header = rb_header_new (shell->priv->player_shell, shell->priv->db);
666 g_object_set (shell->priv->player_shell, "header", shell->priv->header, NULL);
667 gtk_widget_show (GTK_WIDGET (shell->priv->header));
668 g_settings_bind (shell->priv->settings, "time-display", shell->priv->header, "show-remaining", G_SETTINGS_BIND_DEFAULT);
669
670 shell->priv->statusbar = rb_statusbar_new (shell->priv->db,
671 shell->priv->ui_manager,
672 shell->priv->track_transfer_queue);
673 gtk_widget_show (GTK_WIDGET (shell->priv->statusbar));
674
675 g_signal_connect_object (shell->priv->display_page_tree, "selected",
676 G_CALLBACK (display_page_selected_cb), shell, 0);
677
678 shell->priv->notebook = gtk_notebook_new ();
679 gtk_widget_show (shell->priv->notebook);
680 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
681 gtk_notebook_set_show_border (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
682 g_signal_connect_object (shell->priv->display_page_tree,
683 "size-allocate",
684 G_CALLBACK (paned_size_allocate_cb),
685 shell, 0);
686
687 shell->priv->queue_source = RB_PLAYLIST_SOURCE (rb_play_queue_source_new (shell));
688 g_object_set (shell->priv->player_shell, "queue-source", shell->priv->queue_source, NULL);
689 g_object_set (shell->priv->clipboard_shell, "queue-source", shell->priv->queue_source, NULL);
690 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->queue_source), RB_DISPLAY_PAGE_GROUP_LIBRARY);
691 g_object_get (shell->priv->queue_source, "sidebar", &shell->priv->queue_sidebar, NULL);
692 gtk_widget_show_all (shell->priv->queue_sidebar);
693 gtk_widget_set_no_show_all (shell->priv->queue_sidebar, TRUE);
694
695 /* places for plugins to put UI */
696 shell->priv->top_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
697 shell->priv->bottom_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
698 shell->priv->sidebar_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
699 shell->priv->right_sidebar_container = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
700
701 /* set up sidebars */
702 shell->priv->paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
703 shell->priv->right_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
704 gtk_widget_show_all (shell->priv->right_paned);
705 g_signal_connect_object (G_OBJECT (shell->priv->right_paned),
706 "size-allocate",
707 G_CALLBACK (paned_size_allocate_cb),
708 shell, 0);
709 gtk_widget_set_no_show_all (shell->priv->right_paned, TRUE);
710 {
711 GtkWidget *vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
712
713 shell->priv->queue_paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
714 gtk_paned_pack1 (GTK_PANED (shell->priv->queue_paned),
715 GTK_WIDGET (shell->priv->display_page_tree),
716 FALSE, TRUE);
717 gtk_paned_pack2 (GTK_PANED (shell->priv->queue_paned),
718 shell->priv->queue_sidebar,
719 TRUE, TRUE);
720 gtk_container_child_set (GTK_CONTAINER (shell->priv->queue_paned),
721 GTK_WIDGET (shell->priv->display_page_tree),
722 "resize", FALSE,
723 NULL);
724
725 gtk_box_pack_start (GTK_BOX (vbox2),
726 shell->priv->notebook,
727 TRUE, TRUE, 0);
728 gtk_box_pack_start (GTK_BOX (vbox2),
729 GTK_WIDGET (shell->priv->bottom_container),
730 FALSE, FALSE, 0);
731
732 gtk_paned_pack1 (GTK_PANED (shell->priv->right_paned),
733 vbox2, TRUE, TRUE);
734 gtk_paned_pack2 (GTK_PANED (shell->priv->right_paned),
735 GTK_WIDGET (shell->priv->right_sidebar_container),
736 FALSE, FALSE);
737 gtk_widget_hide (GTK_WIDGET(shell->priv->right_sidebar_container));
738
739 gtk_box_pack_start (shell->priv->sidebar_container,
740 shell->priv->queue_paned,
741 TRUE, TRUE, 0);
742 gtk_paned_pack1 (GTK_PANED (shell->priv->paned),
743 GTK_WIDGET (shell->priv->sidebar_container),
744 FALSE, TRUE);
745 gtk_paned_pack2 (GTK_PANED (shell->priv->paned),
746 shell->priv->right_paned,
747 TRUE, TRUE);
748 gtk_widget_show (vbox2);
749 }
750
751 g_signal_connect_object (G_OBJECT (shell->priv->queue_paned),
752 "size-allocate",
753 G_CALLBACK (paned_size_allocate_cb),
754 shell, 0);
755 gtk_widget_show (shell->priv->paned);
756
757 shell->priv->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
758 gtk_container_set_border_width (GTK_CONTAINER (shell->priv->main_vbox), 0);
759
760 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->top_container), FALSE, TRUE, 0);
761 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), shell->priv->paned, TRUE, TRUE, 0);
762 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->statusbar), FALSE, TRUE, 0);
763 gtk_widget_show_all (shell->priv->main_vbox);
764
765 gtk_container_add (GTK_CONTAINER (win), shell->priv->main_vbox);
766
767 rb_profile_end ("constructing widgets");
768 }
769
770 static void
771 construct_sources (RBShell *shell)
772 {
773 RBDisplayPage *page_group;
774 char *pathname;
775
776 rb_profile_start ("constructing sources");
777
778 page_group = RB_DISPLAY_PAGE_GROUP_LIBRARY;
779 shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell));
780 shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager));
781 shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
782
783 shell->priv->import_errors_source = rb_import_errors_source_new (shell,
784 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR,
785 RHYTHMDB_ENTRY_TYPE_SONG,
786 RHYTHMDB_ENTRY_TYPE_IGNORE);
787
788 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source), page_group);
789 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source), page_group);
790 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->missing_files_source), page_group);
791 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->import_errors_source), page_group);
792
793 rb_auto_playlist_source_create_actions (shell);
794 rb_static_playlist_source_create_actions (shell);
795
796 rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
797
798 /* Find the playlist name if none supplied */
799 if (shell->priv->playlists_file) {
800 pathname = g_strdup (shell->priv->playlists_file);
801 } else {
802 pathname = rb_find_user_data_file ("playlists.xml");
803 }
804
805 /* Initialize playlist manager */
806 rb_debug ("shell: creating playlist manager");
807 shell->priv->playlist_manager = rb_playlist_manager_new (shell,
808 shell->priv->display_page_model,
809 shell->priv->display_page_tree,
810 pathname);
811
812 g_object_set (shell->priv->clipboard_shell,
813 "playlist-manager", shell->priv->playlist_manager,
814 NULL);
815
816 g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added",
817 G_CALLBACK (rb_shell_playlist_added_cb), shell, 0);
818 g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created",
819 G_CALLBACK (rb_shell_playlist_created_cb), shell, 0);
820
821 /* Initialize removable media manager */
822 rb_debug ("shell: creating removable media manager");
823 shell->priv->removable_media_manager = rb_removable_media_manager_new (shell);
824
825 g_signal_connect_object (G_OBJECT (shell->priv->removable_media_manager), "medium_added",
826 G_CALLBACK (rb_shell_medium_added_cb), shell, 0);
827
828
829 g_free (pathname);
830
831 rb_profile_end ("constructing sources");
832 }
833
834 static void
835 construct_load_ui (RBShell *shell)
836 {
837 GtkWidget *menubar;
838 GtkWidget *toolbar;
839 GtkToolItem *tool_item;
840 GError *error = NULL;
841
842 rb_debug ("shell: loading ui");
843 rb_profile_start ("loading ui");
844
845 gtk_ui_manager_insert_action_group (shell->priv->ui_manager,
846 shell->priv->actiongroup, 0);
847 gtk_ui_manager_add_ui_from_file (shell->priv->ui_manager,
848 rb_file ("rhythmbox-ui.xml"), &error);
849
850 gtk_ui_manager_ensure_update (shell->priv->ui_manager);
851 gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window),
852 gtk_ui_manager_get_accel_group (shell->priv->ui_manager));
853 menubar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/MenuBar");
854
855 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), menubar, FALSE, FALSE, 0);
856 gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), menubar, 0);
857
858 toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar");
859 gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
860 GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
861 gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH);
862 gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), toolbar, FALSE, FALSE, 0);
863 gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), toolbar, 1);
864
865 shell->priv->volume_button = gtk_volume_button_new ();
866 g_signal_connect (shell->priv->volume_button, "value-changed",
867 G_CALLBACK (rb_shell_volume_widget_changed_cb),
868 shell);
869 g_signal_connect (shell->priv->player_shell, "notify::volume",
870 G_CALLBACK (rb_shell_player_volume_changed_cb),
871 shell);
872 rb_shell_player_volume_changed_cb (shell->priv->player_shell, NULL, shell);
873
874 tool_item = gtk_tool_item_new ();
875 gtk_tool_item_set_expand (tool_item, TRUE);
876 gtk_container_add (GTK_CONTAINER (tool_item), GTK_WIDGET (shell->priv->header));
877 gtk_widget_show_all (GTK_WIDGET (tool_item));
878 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
879
880 tool_item = gtk_tool_item_new ();
881 gtk_container_add (GTK_CONTAINER (tool_item), shell->priv->volume_button);
882 gtk_widget_show_all (GTK_WIDGET (tool_item));
883 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
884
885 gtk_widget_set_tooltip_text (shell->priv->volume_button,
886 _("Change the music volume"));
887
888 if (error != NULL) {
889 g_warning ("Couldn't merge %s: %s",
890 rb_file ("rhythmbox-ui.xml"), error->message);
891 g_clear_error (&error);
892 }
893
894 rb_profile_end ("loading ui");
895 }
896
897 static void
898 extension_added_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
899 {
900 rb_debug ("activating extension %s", peas_plugin_info_get_name (info));
901 peas_extension_call (extension, "activate");
902 }
903
904 static void
905 extension_removed_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
906 {
907 rb_debug ("deactivating extension %s", peas_plugin_info_get_name (info));
908 peas_extension_call (extension, "deactivate");
909 }
910
911 static void
912 construct_plugins (RBShell *shell)
913 {
914 char *typelib_dir;
915 char *plugindir;
916 char *plugindatadir;
917 char **seen_plugins;
918 GPtrArray *new_plugins = NULL;
919 const GList *plugins;
920 const GList *l;
921 GError *error = NULL;
922
923 if (shell->priv->disable_plugins) {
924 return;
925 }
926
927 rb_profile_start ("loading plugins");
928 shell->priv->plugin_settings = g_settings_new ("org.gnome.rhythmbox.plugins");
929
930 shell->priv->plugin_engine = peas_engine_new ();
931 /* need an #ifdef for this? */
932 peas_engine_enable_loader (shell->priv->plugin_engine, "python");
933
934 typelib_dir = g_build_filename (LIBDIR,
935 "girepository-1.0",
936 NULL);
937 if (g_irepository_require_private (g_irepository_get_default (),
938 typelib_dir, "MPID", "3.0", 0, &error) == FALSE) {
939 g_clear_error (&error);
940 if (g_irepository_require (g_irepository_get_default (), "MPID", "3.0", 0, &error) == FALSE) {
941 g_warning ("Could not load MPID typelib: %s", error->message);
942 g_clear_error (&error);
943 }
944 }
945
946 if (g_irepository_require_private (g_irepository_get_default (),
947 typelib_dir, "RB", "3.0", 0, &error) == FALSE) {
948 g_clear_error (&error);
949 if (g_irepository_require (g_irepository_get_default (), "RB", "3.0", 0, &error) == FALSE) {
950 g_warning ("Could not load RB typelib: %s", error->message);
951 g_clear_error (&error);
952 }
953 }
954 g_free (typelib_dir);
955
956 if (g_irepository_require (g_irepository_get_default (), "Peas", "1.0", 0, &error) == FALSE) {
957 g_warning ("Could not load Peas typelib: %s", error->message);
958 g_clear_error (&error);
959 }
960
961 if (g_irepository_require (g_irepository_get_default (), "PeasGtk", "1.0", 0, &error) == FALSE) {
962 g_warning ("Could not load PeasGtk typelib: %s", error->message);
963 g_clear_error (&error);
964 }
965
966 plugindir = g_build_filename (rb_user_data_dir (), "plugins", NULL);
967 rb_debug ("plugin search path: %s", plugindir);
968 peas_engine_add_search_path (shell->priv->plugin_engine,
969 plugindir,
970 plugindir);
971 g_free (plugindir);
972
973 plugindir = g_build_filename (LIBDIR, "rhythmbox", "plugins", NULL);
974 plugindatadir = g_build_filename (DATADIR, "rhythmbox", "plugins", NULL);
975 rb_debug ("plugin search path: %s / %s", plugindir, plugindatadir);
976 peas_engine_add_search_path (shell->priv->plugin_engine,
977 plugindir,
978 plugindatadir);
979 g_free (plugindir);
980 g_free (plugindatadir);
981
982 #ifdef USE_UNINSTALLED_DIRS
983 plugindir = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "..", UNINSTALLED_PLUGINS_LOCATION, NULL);
984 rb_debug ("plugin search path: %s", plugindir);
985 peas_engine_add_search_path (shell->priv->plugin_engine,
986 plugindir,
987 plugindir);
988 g_free (plugindir);
989 #endif
990
991 shell->priv->activatable = peas_extension_set_new (shell->priv->plugin_engine,
992 PEAS_TYPE_ACTIVATABLE,
993 "object", shell,
994 NULL);
995 g_signal_connect (shell->priv->activatable, "extension-added", G_CALLBACK (extension_added_cb), shell);
996 g_signal_connect (shell->priv->activatable, "extension-removed", G_CALLBACK (extension_removed_cb), shell);
997
998 g_settings_bind (shell->priv->plugin_settings,
999 "active-plugins",
1000 shell->priv->plugin_engine,
1001 "loaded-plugins",
1002 G_SETTINGS_BIND_DEFAULT);
1003
1004 seen_plugins = g_settings_get_strv (shell->priv->plugin_settings, "seen-plugins");
1005 plugins = peas_engine_get_plugin_list (shell->priv->plugin_engine);
1006 for (l = plugins; l != NULL; l = l->next) {
1007 PeasPluginInfo *info = PEAS_PLUGIN_INFO (l->data);
1008 char *kf_name;
1009 char *kf_path;
1010 GKeyFile *keyfile;
1011
1012 /* load builtin plugins, except for the 'rb' utility module, which only
1013 * gets loaded if another plugin needs it.
1014 */
1015 if (peas_plugin_info_is_builtin (info) &&
1016 g_strcmp0 (peas_plugin_info_get_module_name (info), "rb") != 0) {
1017 peas_engine_load_plugin (shell->priv->plugin_engine, info);
1018 continue;
1019 }
1020
1021 /* have we seen this plugin before? */
1022 if (rb_str_in_strv (peas_plugin_info_get_module_name (info), (const char **)seen_plugins)) {
1023 continue;
1024 }
1025 if (new_plugins == NULL) {
1026 new_plugins = g_ptr_array_new_with_free_func (g_free);
1027 }
1028 g_ptr_array_add (new_plugins, g_strdup (peas_plugin_info_get_module_name (info)));
1029
1030 /* it's a new plugin, see if it wants to be enabled */
1031 kf_name = g_strdup_printf ("%s.plugin", peas_plugin_info_get_module_name (info));
1032 kf_path = g_build_filename (peas_plugin_info_get_module_dir (info), kf_name, NULL);
1033 g_free (kf_name);
1034
1035 keyfile = g_key_file_new ();
1036 if (g_key_file_load_from_file (keyfile, kf_path, G_KEY_FILE_NONE, NULL)) {
1037 if (g_key_file_get_boolean (keyfile, "RB", "InitiallyEnabled", NULL)) {
1038 rb_debug ("loading new plugin %s", peas_plugin_info_get_module_name (info));
1039 peas_engine_load_plugin (shell->priv->plugin_engine, info);
1040 } else {
1041 rb_debug ("new plugin %s not enabled", peas_plugin_info_get_module_name (info));
1042 }
1043 } else {
1044 rb_debug ("couldn't load plugin file %s", kf_path);
1045 }
1046 g_free (kf_path);
1047 g_key_file_free (keyfile);
1048 }
1049
1050 if (new_plugins != NULL) {
1051 GPtrArray *update;
1052 int i;
1053
1054 update = g_ptr_array_new_with_free_func (g_free);
1055 for (i = 0; i < g_strv_length (seen_plugins); i++) {
1056 g_ptr_array_add (update, g_strdup (seen_plugins[i]));
1057 }
1058 for (i = 0; i < new_plugins->len; i++) {
1059 g_ptr_array_add (update, g_strdup (g_ptr_array_index (new_plugins, i)));
1060 }
1061
1062 g_ptr_array_add (update, NULL);
1063 g_settings_set_strv (shell->priv->plugin_settings, "seen-plugins", (const char * const *)update->pdata);
1064
1065 g_ptr_array_free (new_plugins, TRUE);
1066 g_ptr_array_free (update, TRUE);
1067 }
1068
1069 g_strfreev (seen_plugins);
1070
1071 rb_profile_end ("loading plugins");
1072 }
1073
1074 static gboolean
1075 _scan_idle (RBShell *shell)
1076 {
1077 gboolean loaded, scanned;
1078
1079 GDK_THREADS_ENTER ();
1080 rb_removable_media_manager_scan (shell->priv->removable_media_manager);
1081 GDK_THREADS_LEAVE ();
1082
1083 if (shell->priv->no_registration == FALSE) {
1084 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned);
1085 g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", loaded, TRUE));
1086 }
1087
1088 return FALSE;
1089 }
1090
1091 static void
1092 rb_shell_startup (GApplication *app)
1093 {
1094 RBShell *shell = RB_SHELL (app);
1095 GtkAction *gtkaction;
1096 RBEntryView *view;
1097
1098 rb_debug ("Constructing shell");
1099 rb_profile_start ("constructing shell");
1100
1101 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSidePane");
1102 g_settings_bind (shell->priv->settings, "display-page-tree-visible",
1103 gtkaction, "active",
1104 G_SETTINGS_BIND_DEFAULT);
1105 g_settings_bind (shell->priv->settings, "display-page-tree-visible",
1106 shell->priv->sidebar_container, "visible",
1107 G_SETTINGS_BIND_DEFAULT);
1108
1109 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSongPositionSlider");
1110 g_settings_bind (shell->priv->settings, "show-song-position-slider",
1111 gtkaction, "active",
1112 G_SETTINGS_BIND_DEFAULT);
1113 g_settings_bind (shell->priv->settings, "show-song-position-slider",
1114 shell->priv->header, "show-position-slider",
1115 G_SETTINGS_BIND_DEFAULT);
1116
1117 gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewAlbumArt");
1118 g_settings_bind (shell->priv->settings, "show-album-art",
1119 gtkaction, "active",
1120 G_SETTINGS_BIND_DEFAULT);
1121 g_settings_bind (shell->priv->settings, "show-album-art",
1122 shell->priv->header, "show-album-art",
1123 G_SETTINGS_BIND_DEFAULT);
1124
1125 rb_debug ("shell: syncing with settings");
1126 rb_shell_sync_pane_visibility (shell);
1127
1128 g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error",
1129 G_CALLBACK (rb_shell_db_save_error_cb), shell, 0);
1130
1131 construct_sources (shell);
1132
1133 construct_load_ui (shell);
1134
1135 construct_plugins (shell);
1136
1137 rb_shell_sync_window_state (shell, FALSE);
1138 rb_shell_sync_party_mode (shell);
1139
1140 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
1141
1142 /* by now we've added the built in sources and any sources from plugins,
1143 * so we can consider the fixed page groups loaded
1144 */
1145 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_LIBRARY));
1146 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_STORES));
1147
1148 rb_missing_plugins_init (GTK_WINDOW (shell->priv->window));
1149
1150 g_idle_add ((GSourceFunc)_scan_idle, shell);
1151
1152 /* GO GO GO! */
1153 rb_debug ("loading database");
1154 rhythmdb_load (shell->priv->db);
1155
1156 rb_debug ("shell: syncing window state");
1157 rb_shell_sync_paned (shell);
1158
1159 /* set initial visibility */
1160 rb_shell_set_visibility (shell, TRUE, TRUE);
1161
1162 gdk_notify_startup_complete ();
1163
1164 view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source));
1165 if (view != NULL) {
1166 gtk_widget_grab_focus (GTK_WIDGET (view));
1167 }
1168
1169 rb_profile_end ("constructing shell");
1170
1171 /* window-based usage counting doesn't work for us, just hold the app until
1172 * we're asked to quit.
1173 */
1174 g_application_hold (app);
1175
1176 (* G_APPLICATION_CLASS (rb_shell_parent_class)->startup) (app);
1177 }
1178
1179 static gboolean
1180 rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status)
1181 {
1182 RBShell *shell;
1183 GError *error = NULL;
1184 gboolean scanned;
1185 gboolean loaded;
1186 GPtrArray *files;
1187 int n_files;
1188 int i;
1189
1190 n_files = g_strv_length (*args) - 1;
1191
1192 shell = RB_SHELL (app);
1193 if (shell->priv->no_registration) {
1194 if (n_files > 0) {
1195 g_warning ("Unable to open files on the commandline with --no-registration");
1196 }
1197 rb_shell_startup (app);
1198 return TRUE;
1199 }
1200
1201 if (!g_application_register (app, NULL, &error)) {
1202 g_critical ("%s", error->message);
1203 g_error_free (error);
1204 *exit_status = 1;
1205 return TRUE;
1206 }
1207
1208 if (n_files <= 0) {
1209 g_application_activate (app);
1210 *exit_status = 0;
1211 return TRUE;
1212 }
1213
1214 files = g_ptr_array_new_with_free_func (g_object_unref);
1215 for (i = 0; i < n_files; i++) {
1216 g_ptr_array_add (files, g_file_new_for_commandline_arg ((*args)[i + 1]));
1217 }
1218
1219 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (app), "LoadURI"), "(bb)", &loaded, &scanned);
1220 if (loaded) {
1221 rb_debug ("opening files immediately");
1222 g_application_open (app, (GFile **)files->pdata, files->len, "");
1223 g_ptr_array_free (files, TRUE);
1224 } else {
1225 rb_debug ("opening files once db is loaded");
1226 g_signal_connect (app, "action-state-changed::LoadURI", G_CALLBACK (load_state_changed_cb), files);
1227 }
1228
1229 return TRUE;
1230 }
1231 static void
1232 rb_shell_class_init (RBShellClass *klass)
1233 {
1234 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1235 GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
1236
1237 object_class->set_property = rb_shell_set_property;
1238 object_class->get_property = rb_shell_get_property;
1239 object_class->finalize = rb_shell_finalize;
1240 object_class->constructed = rb_shell_constructed;
1241
1242 app_class->activate = rb_shell_activate;
1243 app_class->open = rb_shell_open;
1244 app_class->local_command_line = rb_shell_local_command_line;
1245 app_class->startup = rb_shell_startup;
1246
1247 klass->visibility_changing = rb_shell_visibility_changing;
1248
1249 /**
1250 * RBShell:no-registration:
1251 *
1252 * If %TRUE, disable single-instance features.
1253 */
1254 g_object_class_install_property (object_class,
1255 PROP_NO_REGISTRATION,
1256 g_param_spec_boolean ("no-registration",
1257 "no-registration",
1258 "Whether or not to register",
1259 FALSE,
1260 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1261 /**
1262 * RBShell:no-update:
1263 *
1264 * If %TRUE, don't update the database.
1265 */
1266 g_object_class_install_property (object_class,
1267 PROP_NO_UPDATE,
1268 g_param_spec_boolean ("no-update",
1269 "no-update",
1270 "Whether or not to update the library",
1271 FALSE,
1272 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1273 /**
1274 * RBShell:dry-run:
1275 *
1276 * If TRUE, don't write back file metadata changes.
1277 */
1278 g_object_class_install_property (object_class,
1279 PROP_DRY_RUN,
1280 g_param_spec_boolean ("dry-run",
1281 "dry-run",
1282 "Whether or not this is a dry run",
1283 FALSE,
1284 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1285 /**
1286 * RBShell:rhythmdb-file:
1287 *
1288 * The path to the rhythmdb file
1289 */
1290 g_object_class_install_property (object_class,
1291 PROP_RHYTHMDB_FILE,
1292 g_param_spec_string ("rhythmdb-file",
1293 "rhythmdb-file",
1294 "The RhythmDB file to use",
1295 "rhythmdb.xml",
1296 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1297
1298 /**
1299 * RBShell:playlists-file:
1300 *
1301 * The path to the playlist file
1302 */
1303 g_object_class_install_property (object_class,
1304 PROP_PLAYLISTS_FILE,
1305 g_param_spec_string ("playlists-file",
1306 "playlists-file",
1307 "The playlists file to use",
1308 "playlists.xml",
1309 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1310 /**
1311 * RBShell:selected-page:
1312 *
1313 * The currently selected display page
1314 */
1315 g_object_class_install_property (object_class,
1316 PROP_SELECTED_PAGE,
1317 g_param_spec_object ("selected-page",
1318 "selected-page",
1319 "Display page which is currently selected",
1320 RB_TYPE_DISPLAY_PAGE,
1321 G_PARAM_READABLE));
1322 /**
1323 * RBShell:db:
1324 *
1325 * The #RhythmDB instance
1326 */
1327 g_object_class_install_property (object_class,
1328 PROP_DB,
1329 g_param_spec_object ("db",
1330 "RhythmDB",
1331 "RhythmDB object",
1332 RHYTHMDB_TYPE,
1333 G_PARAM_READABLE));
1334 /**
1335 * RBShell:ui-manager:
1336 *
1337 * The #GtkUIManager instance
1338 */
1339 g_object_class_install_property (object_class,
1340 PROP_UI_MANAGER,
1341 g_param_spec_object ("ui-manager",
1342 "GtkUIManager",
1343 "GtkUIManager object",
1344 GTK_TYPE_UI_MANAGER,
1345 G_PARAM_READABLE));
1346 /**
1347 * RBShell:clipboard:
1348 *
1349 * The #RBShellClipboard instance
1350 */
1351 g_object_class_install_property (object_class,
1352 PROP_CLIPBOARD,
1353 g_param_spec_object ("clipboard",
1354 "RBShellClipboard",
1355 "RBShellClipboard object",
1356 RB_TYPE_SHELL_CLIPBOARD,
1357 G_PARAM_READABLE));
1358 /**
1359 * RBShell:playlist-manager:
1360 *
1361 * The #RBPlaylistManager instance
1362 */
1363 g_object_class_install_property (object_class,
1364 PROP_PLAYLIST_MANAGER,
1365 g_param_spec_object ("playlist-manager",
1366 "RBPlaylistManager",
1367 "RBPlaylistManager object",
1368 RB_TYPE_PLAYLIST_MANAGER,
1369 G_PARAM_READABLE));
1370 /**
1371 * RBShell:shell-player:
1372 *
1373 * The #RBShellPlayer instance
1374 */
1375 g_object_class_install_property (object_class,
1376 PROP_SHELL_PLAYER,
1377 g_param_spec_object ("shell-player",
1378 "RBShellPlayer",
1379 "RBShellPlayer object",
1380 RB_TYPE_SHELL_PLAYER,
1381 G_PARAM_READABLE));
1382 /**
1383 * RBShell:removable-media-manager:
1384 *
1385 * The #RBRemovableMediaManager instance
1386 */
1387 g_object_class_install_property (object_class,
1388 PROP_REMOVABLE_MEDIA_MANAGER,
1389 g_param_spec_object ("removable-media-manager",
1390 "RBRemovableMediaManager",
1391 "RBRemovableMediaManager object",
1392 RB_TYPE_REMOVABLE_MEDIA_MANAGER,
1393 G_PARAM_READABLE));
1394 /**
1395 * RBShell:window:
1396 *
1397 * The main Rhythmbox window.
1398 */
1399 g_object_class_install_property (object_class,
1400 PROP_WINDOW,
1401 g_param_spec_object ("window",
1402 "GtkWindow",
1403 "GtkWindow object",
1404 GTK_TYPE_WINDOW,
1405 G_PARAM_READABLE));
1406 /**
1407 * RBShell:prefs:
1408 *
1409 * The #RBShellPreferences instance
1410 */
1411 g_object_class_install_property (object_class,
1412 PROP_PREFS,
1413 g_param_spec_object ("prefs",
1414 "RBShellPreferences",
1415 "RBShellPreferences object",
1416 RB_TYPE_SHELL_PREFERENCES,
1417 G_PARAM_READABLE));
1418 /**
1419 * RBShell:queue-source:
1420 *
1421 * The play queue source
1422 */
1423 g_object_class_install_property (object_class,
1424 PROP_QUEUE_SOURCE,
1425 g_param_spec_object ("queue-source",
1426 "queue-source",
1427 "Queue source",
1428 RB_TYPE_PLAY_QUEUE_SOURCE,
1429 G_PARAM_READABLE));
1430 /**
1431 * RBShell:library-source:
1432 *
1433 * The library source
1434 */
1435 g_object_class_install_property (object_class,
1436 PROP_LIBRARY_SOURCE,
1437 g_param_spec_object ("library-source",
1438 "library-source",
1439 "Library source",
1440 RB_TYPE_LIBRARY_SOURCE,
1441 G_PARAM_READABLE));
1442 /**
1443 * RBShell:display-page-model:
1444 *
1445 * The model underlying the display page tree
1446 */
1447 g_object_class_install_property (object_class,
1448 PROP_DISPLAY_PAGE_MODEL,
1449 g_param_spec_object ("display-page-model",
1450 "display-page-model",
1451 "RBDisplayPageModel",
1452 RB_TYPE_DISPLAY_PAGE_MODEL,
1453 G_PARAM_READABLE));
1454
1455 /**
1456 * RBShell:display-page-tree:
1457 *
1458 * The #RBDisplayPageTree instance
1459 */
1460 g_object_class_install_property (object_class,
1461 PROP_DISPLAY_PAGE_TREE,
1462 g_param_spec_object ("display-page-tree",
1463 "display-page-tree",
1464 "RBDisplayPageTree",
1465 RB_TYPE_DISPLAY_PAGE_TREE,
1466 G_PARAM_READABLE));
1467
1468 /**
1469 * RBShell:visibility:
1470 *
1471 * Whether the main window is currently visible.
1472 */
1473 g_object_class_install_property (object_class,
1474 PROP_VISIBILITY,
1475 g_param_spec_boolean ("visibility",
1476 "visibility",
1477 "Current window visibility",
1478 TRUE,
1479 G_PARAM_READWRITE));
1480 /**
1481 * RBShell:track-transfer-queue:
1482 *
1483 * The #RBTrackTransferQueue instance
1484 */
1485 g_object_class_install_property (object_class,
1486 PROP_TRACK_TRANSFER_QUEUE,
1487 g_param_spec_object ("track-transfer-queue",
1488 "RBTrackTransferQueue",
1489 "RBTrackTransferQueue object",
1490 RB_TYPE_TRACK_TRANSFER_QUEUE,
1491 G_PARAM_READABLE));
1492 /**
1493 * RBShell:autostarted:
1494 *
1495 * Whether Rhythmbox was automatically started by the session manager
1496 */
1497 g_object_class_install_property (object_class,
1498 PROP_AUTOSTARTED,
1499 g_param_spec_boolean ("autostarted",
1500 "autostarted",
1501 "TRUE if autostarted",
1502 FALSE,
1503 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1504 /**
1505 * RBShell:disable-plugins:
1506 *
1507 * If %TRUE, disable plugins
1508 */
1509 g_object_class_install_property (object_class,
1510 PROP_DISABLE_PLUGINS,
1511 g_param_spec_boolean ("disable-plugins",
1512 "disable-plugins",
1513 "Whether or not to disable plugins",
1514 FALSE,
1515 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1516
1517 /**
1518 * RBShell::visibility-changed:
1519 * @shell: the #RBShell
1520 * @visibile: new visibility
1521 *
1522 * Emitted after the visibility of the main Rhythmbox window has
1523 * changed.
1524 */
1525 rb_shell_signals[VISIBILITY_CHANGED] =
1526 g_signal_new ("visibility_changed",
1527 G_OBJECT_CLASS_TYPE (object_class),
1528 G_SIGNAL_RUN_LAST,
1529 G_STRUCT_OFFSET (RBShellClass, visibility_changed),
1530 NULL, NULL,
1531 g_cclosure_marshal_VOID__BOOLEAN,
1532 G_TYPE_NONE,
1533 1,
1534 G_TYPE_BOOLEAN);
1535 /**
1536 * RBShell::visibility-changing:
1537 * @shell: the #RBShell
1538 * @initial: if %TRUE, this is the initial visibility for the window
1539 * @visible: new shell visibility
1540 *
1541 * Emitted before the visibility of the main window changes. The return
1542 * value overrides the visibility setting. If multiple signal handlers
1543 * are attached, the last one wins.
1544 */
1545 rb_shell_signals[VISIBILITY_CHANGING] =
1546 g_signal_new ("visibility_changing",
1547 G_OBJECT_CLASS_TYPE (object_class),
1548 G_SIGNAL_RUN_LAST,
1549 G_STRUCT_OFFSET (RBShellClass, visibility_changing),
1550 NULL, NULL,
1551 rb_marshal_BOOLEAN__BOOLEAN_BOOLEAN,
1552 G_TYPE_BOOLEAN,
1553 2,
1554 G_TYPE_BOOLEAN,
1555 G_TYPE_BOOLEAN);
1556
1557 /**
1558 * RBShell::create-song-info:
1559 * @shell: the #RBShell
1560 * @song_info: the new #RBSongInfo window
1561 * @multi: if %TRUE, the song info window is for multiple entries
1562 *
1563 * Emitted when creating a new #RBSongInfo window. Signal handlers can
1564 * add pages to the song info window notebook to display additional
1565 * information.
1566 */
1567 rb_shell_signals[CREATE_SONG_INFO] =
1568 g_signal_new ("create_song_info",
1569 G_OBJECT_CLASS_TYPE (object_class),
1570 G_SIGNAL_RUN_LAST,
1571 G_STRUCT_OFFSET (RBShellClass, create_song_info),
1572 NULL, NULL,
1573 rb_marshal_VOID__OBJECT_BOOLEAN,
1574 G_TYPE_NONE,
1575 2,
1576 RB_TYPE_SONG_INFO, G_TYPE_BOOLEAN);
1577 /**
1578 * RBShell::notify-playing-entry:
1579 * @shell: the #RBShell
1580 *
1581 * Emitted when a notification should be displayed showing the current
1582 * playing entry.
1583 */
1584 rb_shell_signals[NOTIFY_PLAYING_ENTRY] =
1585 g_signal_new ("notify-playing-entry",
1586 G_OBJECT_CLASS_TYPE (object_class),
1587 G_SIGNAL_RUN_LAST,
1588 0,
1589 NULL, NULL,
1590 g_cclosure_marshal_VOID__BOOLEAN,
1591 G_TYPE_NONE,
1592 1,
1593 G_TYPE_BOOLEAN);
1594 /**
1595 * RBShell::notify-custom:
1596 * @shell: the #RBShell
1597 * @timeout: length of time (in seconds) to display the notification
1598 * @primary: main notification text
1599 * @secondary: secondary notification text
1600 * @image_uri: URI for an image to include in the notification (optional)
1601 * @requested: if %TRUE, the notification was triggered by an explicit user action
1602 *
1603 * Emitted when a custom notification should be displayed to the user.
1604 */
1605 rb_shell_signals[NOTIFY_CUSTOM] =
1606 g_signal_new ("notify-custom",
1607 G_OBJECT_CLASS_TYPE (object_class),
1608 G_SIGNAL_RUN_LAST,
1609 0,
1610 NULL, NULL,
1611 rb_marshal_VOID__UINT_STRING_STRING_STRING_BOOLEAN,
1612 G_TYPE_NONE,
1613 5,
1614 G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
1615 g_type_class_add_private (klass, sizeof (RBShellPrivate));
1616 }
1617
1618 static void
1619 rb_shell_init (RBShell *shell)
1620 {
1621 shell->priv = G_TYPE_INSTANCE_GET_PRIVATE (shell, RB_TYPE_SHELL, RBShellPrivate);
1622
1623 rb_user_data_dir ();
1624 rb_refstring_system_init ();
1625
1626 #ifdef USE_UNINSTALLED_DIRS
1627 rb_file_helpers_init (TRUE);
1628 #else
1629 rb_file_helpers_init (FALSE);
1630 #endif
1631 rb_stock_icons_init ();
1632
1633 rb_shell_session_init (shell);
1634
1635 g_setenv ("PULSE_PROP_media.role", "music", TRUE);
1636 }
1637
1638 static void
1639 rb_shell_set_property (GObject *object,
1640 guint prop_id,
1641 const GValue *value,
1642 GParamSpec *pspec)
1643 {
1644 RBShell *shell = RB_SHELL (object);
1645
1646 switch (prop_id)
1647 {
1648 case PROP_NO_REGISTRATION:
1649 shell->priv->no_registration = g_value_get_boolean (value);
1650 break;
1651 case PROP_NO_UPDATE:
1652 shell->priv->no_update = g_value_get_boolean (value);
1653 break;
1654 case PROP_DRY_RUN:
1655 shell->priv->dry_run = g_value_get_boolean (value);
1656 if (shell->priv->dry_run)
1657 shell->priv->no_registration = TRUE;
1658 break;
1659 case PROP_RHYTHMDB_FILE:
1660 g_free (shell->priv->rhythmdb_file);
1661 shell->priv->rhythmdb_file = g_value_dup_string (value);
1662 break;
1663 case PROP_PLAYLISTS_FILE:
1664 g_free (shell->priv->playlists_file);
1665 shell->priv->playlists_file = g_value_dup_string (value);
1666 break;
1667 case PROP_VISIBILITY:
1668 rb_shell_set_visibility (shell, FALSE, g_value_get_boolean (value));
1669 break;
1670 case PROP_AUTOSTARTED:
1671 shell->priv->autostarted = g_value_get_boolean (value);
1672 break;
1673 case PROP_DISABLE_PLUGINS:
1674 shell->priv->disable_plugins = g_value_get_boolean (value);
1675 break;
1676 default:
1677 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1678 break;
1679 }
1680 }
1681
1682 static void
1683 rb_shell_get_property (GObject *object,
1684 guint prop_id,
1685 GValue *value,
1686 GParamSpec *pspec)
1687 {
1688 RBShell *shell = RB_SHELL (object);
1689
1690 switch (prop_id)
1691 {
1692 case PROP_NO_REGISTRATION:
1693 g_value_set_boolean (value, shell->priv->no_registration);
1694 break;
1695 case PROP_NO_UPDATE:
1696 g_value_set_boolean (value, shell->priv->no_update);
1697 break;
1698 case PROP_DRY_RUN:
1699 g_value_set_boolean (value, shell->priv->dry_run);
1700 break;
1701 case PROP_RHYTHMDB_FILE:
1702 g_value_set_string (value, shell->priv->rhythmdb_file);
1703 break;
1704 case PROP_PLAYLISTS_FILE:
1705 g_value_set_string (value, shell->priv->playlists_file);
1706 break;
1707 case PROP_DB:
1708 g_value_set_object (value, shell->priv->db);
1709 break;
1710 case PROP_UI_MANAGER:
1711 g_value_set_object (value, shell->priv->ui_manager);
1712 break;
1713 case PROP_CLIPBOARD:
1714 g_value_set_object (value, shell->priv->clipboard_shell);
1715 break;
1716 case PROP_PLAYLIST_MANAGER:
1717 g_value_set_object (value, shell->priv->playlist_manager);
1718 break;
1719 case PROP_SHELL_PLAYER:
1720 g_value_set_object (value, shell->priv->player_shell);
1721 break;
1722 case PROP_REMOVABLE_MEDIA_MANAGER:
1723 g_value_set_object (value, shell->priv->removable_media_manager);
1724 break;
1725 case PROP_SELECTED_PAGE:
1726 g_value_set_object (value, shell->priv->selected_page);
1727 break;
1728 case PROP_WINDOW:
1729 g_value_set_object (value, shell->priv->window);
1730 break;
1731 case PROP_PREFS:
1732 /* create the preferences window the first time we need it */
1733 if (shell->priv->prefs == NULL) {
1734 GtkWidget *content;
1735
1736 shell->priv->prefs = rb_shell_preferences_new (shell->priv->sources);
1737
1738 gtk_window_set_transient_for (GTK_WINDOW (shell->priv->prefs),
1739 GTK_WINDOW (shell->priv->window));
1740 content = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->prefs));
1741 gtk_widget_show_all (content);
1742 }
1743 g_value_set_object (value, shell->priv->prefs);
1744 break;
1745 case PROP_QUEUE_SOURCE:
1746 g_value_set_object (value, shell->priv->queue_source);
1747 break;
1748 case PROP_LIBRARY_SOURCE:
1749 g_value_set_object (value, shell->priv->library_source);
1750 break;
1751 case PROP_DISPLAY_PAGE_MODEL:
1752 g_value_set_object (value, shell->priv->display_page_model);
1753 break;
1754 case PROP_DISPLAY_PAGE_TREE:
1755 g_value_set_object (value, shell->priv->display_page_tree);
1756 break;
1757 case PROP_VISIBILITY:
1758 g_value_set_boolean (value, rb_shell_get_visibility (shell));
1759 break;
1760 case PROP_TRACK_TRANSFER_QUEUE:
1761 g_value_set_object (value, shell->priv->track_transfer_queue);
1762 break;
1763 case PROP_AUTOSTARTED:
1764 g_value_set_boolean (value, shell->priv->autostarted);
1765 break;
1766 case PROP_DISABLE_PLUGINS:
1767 g_value_set_boolean (value, shell->priv->disable_plugins);
1768 break;
1769 default:
1770 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1771 break;
1772 }
1773 }
1774
1775 static gboolean
1776 rb_shell_sync_state (RBShell *shell)
1777 {
1778 if (shell->priv->dry_run) {
1779 rb_debug ("in dry-run mode, not syncing state");
1780 return FALSE;
1781 }
1782
1783 if (!shell->priv->load_complete) {
1784 rb_debug ("load incomplete, not syncing state");
1785 return FALSE;
1786 }
1787
1788 rb_debug ("saving playlists");
1789 rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
1790 TRUE);
1791
1792 rb_debug ("saving db");
1793 rhythmdb_save (shell->priv->db);
1794 return FALSE;
1795 }
1796
1797 static gboolean
1798 idle_save_playlist_manager (RBShell *shell)
1799 {
1800 GDK_THREADS_ENTER ();
1801 rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
1802 FALSE);
1803 GDK_THREADS_LEAVE ();
1804
1805 return TRUE;
1806 }
1807
1808 static void
1809 rb_shell_shutdown (RBShell *shell)
1810 {
1811 GdkDisplay *display;
1812
1813 if (shell->priv->shutting_down)
1814 return;
1815 shell->priv->shutting_down = TRUE;
1816
1817 /* Hide the main window and tray icon as soon as possible */
1818 display = gtk_widget_get_display (shell->priv->window);
1819 gtk_widget_hide (shell->priv->window);
1820 gdk_display_sync (display);
1821
1822 if (shell->priv->plugin_engine != NULL) {
1823 g_object_unref (shell->priv->plugin_engine);
1824 shell->priv->plugin_engine = NULL;
1825 }
1826 if (shell->priv->activatable != NULL) {
1827 g_object_unref (shell->priv->activatable);
1828 shell->priv->activatable = NULL;
1829 }
1830 if (shell->priv->plugin_settings != NULL) {
1831 g_object_unref (shell->priv->plugin_settings);
1832 shell->priv->plugin_settings = NULL;
1833 }
1834 }
1835
1836 static void
1837 rb_shell_finalize (GObject *object)
1838 {
1839 RBShell *shell = RB_SHELL (object);
1840
1841 rb_debug ("Finalizing shell");
1842
1843 rb_shell_player_stop (shell->priv->player_shell);
1844
1845 if (shell->priv->settings != NULL) {
1846 rb_settings_delayed_sync (shell->priv->settings, NULL, NULL, NULL);
1847 g_object_unref (shell->priv->settings);
1848 }
1849
1850 g_free (shell->priv->cached_title);
1851
1852 if (shell->priv->save_playlist_id > 0) {
1853 g_source_remove (shell->priv->save_playlist_id);
1854 shell->priv->save_playlist_id = 0;
1855 }
1856
1857 if (shell->priv->queue_sidebar != NULL) {
1858 g_object_unref (shell->priv->queue_sidebar);
1859 }
1860
1861 if (shell->priv->playlist_manager != NULL) {
1862 rb_debug ("shutting down playlist manager");
1863 rb_playlist_manager_shutdown (shell->priv->playlist_manager);
1864
1865 rb_debug ("unreffing playlist manager");
1866 g_object_unref (shell->priv->playlist_manager);
1867 }
1868
1869 if (shell->priv->removable_media_manager != NULL) {
1870 rb_debug ("unreffing removable media manager");
1871 g_object_unref (shell->priv->removable_media_manager);
1872 g_object_unref (shell->priv->track_transfer_queue);
1873 }
1874
1875 if (shell->priv->podcast_manager != NULL) {
1876 rb_debug ("unreffing podcast manager");
1877 g_object_unref (shell->priv->podcast_manager);
1878 }
1879
1880 if (shell->priv->clipboard_shell != NULL) {
1881 rb_debug ("unreffing clipboard shell");
1882 g_object_unref (shell->priv->clipboard_shell);
1883 }
1884
1885 if (shell->priv->prefs != NULL) {
1886 rb_debug ("destroying prefs");
1887 gtk_widget_destroy (shell->priv->prefs);
1888 }
1889
1890 g_free (shell->priv->rhythmdb_file);
1891
1892 g_free (shell->priv->playlists_file);
1893
1894 rb_debug ("destroying window");
1895 gtk_widget_destroy (shell->priv->window);
1896
1897 g_list_free (shell->priv->sources);
1898 shell->priv->sources = NULL;
1899
1900 if (shell->priv->sources_hash != NULL) {
1901 g_hash_table_destroy (shell->priv->sources_hash);
1902 }
1903
1904 if (shell->priv->db != NULL) {
1905 rb_debug ("shutting down DB");
1906 rhythmdb_shutdown (shell->priv->db);
1907
1908 rb_debug ("unreffing DB");
1909 g_object_unref (shell->priv->db);
1910 }
1911 if (shell->priv->art_store != NULL) {
1912 g_object_unref (shell->priv->art_store);
1913 shell->priv->art_store = NULL;
1914 }
1915
1916 rb_file_helpers_shutdown ();
1917 rb_stock_icons_shutdown ();
1918 rb_refstring_system_shutdown ();
1919
1920 G_OBJECT_CLASS (rb_shell_parent_class)->finalize (object);
1921
1922 rb_debug ("shell shutdown complete");
1923 }
1924
1925 /**
1926 * rb_shell_new:
1927 * @autostarted: %TRUE if autostarted by the session manager
1928 * @argc: a pointer to the number of command line arguments
1929 * @argv: a pointer to the array of command line arguments
1930 *
1931 * Creates the Rhythmbox shell. This is effectively a singleton, so it doesn't
1932 * make sense to call this from anywhere other than main.c.
1933 *
1934 * Return value: the #RBShell instance
1935 */
1936 RBShell *
1937 rb_shell_new (gboolean autostarted, int *argc, char ***argv)
1938 {
1939 GOptionContext *context;
1940 gboolean debug = FALSE;
1941 char *debug_match = NULL;
1942 gboolean no_update = FALSE;
1943 gboolean no_registration = FALSE;
1944 gboolean dry_run = FALSE;
1945 gboolean disable_plugins = FALSE;
1946 char *rhythmdb_file = NULL;
1947 char *playlists_file = NULL;
1948 GError *error = NULL;
1949
1950 const GOptionEntry options [] = {
1951 { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, N_("Enable debug output"), NULL },
1952 { "debug-match", 'D', 0, G_OPTION_ARG_STRING, &debug_match, N_("Enable debug output matching a specified string"), NULL },
1953 { "no-update", 0, 0, G_OPTION_ARG_NONE, &no_update, N_("Do not update the library with file changes"), NULL },
1954 { "no-registration", 'n', 0, G_OPTION_ARG_NONE, &no_registration, N_("Do not register the shell"), NULL },
1955 { "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Don't save any data permanently (implies --no-registration)"), NULL },
1956 { "disable-plugins", 0, 0, G_OPTION_ARG_NONE, &disable_plugins, N_("Disable loading of plugins"), NULL },
1957 { "rhythmdb-file", 0, 0, G_OPTION_ARG_STRING, &rhythmdb_file, N_("Path for database file to use"), NULL },
1958 { "playlists-file", 0, 0, G_OPTION_ARG_STRING, &playlists_file, N_("Path for playlists file to use"), NULL },
1959 { NULL }
1960 };
1961
1962 context = g_option_context_new (NULL);
1963 g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
1964 g_option_context_add_group (context, gst_init_get_option_group ());
1965 g_option_context_add_group (context, egg_sm_client_get_option_group ());
1966 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1967
1968 if (g_option_context_parse (context, argc, argv, &error) == FALSE) {
1969 g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
1970 error->message, (*argv)[0]);
1971 g_error_free (error);
1972 g_option_context_free (context);
1973 exit (1);
1974 }
1975 g_option_context_free (context);
1976
1977 if (!debug && debug_match)
1978 rb_debug_init_match (debug_match);
1979 else
1980 rb_debug_init (debug);
1981
1982 return g_object_new (RB_TYPE_SHELL,
1983 "application-id", "org.gnome.Rhythmbox3",
1984 "flags", G_APPLICATION_HANDLES_OPEN,
1985 "autostarted", autostarted,
1986 "no-registration", no_registration,
1987 "no-update", no_update,
1988 "dry-run", dry_run,
1989 "rhythmdb-file", rhythmdb_file,
1990 "playlists-file", playlists_file,
1991 "disable-plugins", disable_plugins,
1992 NULL);
1993 }
1994
1995 static void
1996 load_uri_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
1997 {
1998 const char *uri;
1999 gboolean play;
2000
2001 g_variant_get (parameters, "(&sb)", &uri, &play);
2002
2003 rb_shell_load_uri (shell, uri, play, NULL);
2004 }
2005
2006 static void
2007 activate_source_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
2008 {
2009 const char *source;
2010 guint play;
2011
2012 g_variant_get (parameters, "(&su)", &source, &play);
2013 rb_shell_activate_source_by_uri (shell, source, play, NULL);
2014 }
2015
2016 static void
2017 quit_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
2018 {
2019 rb_shell_quit (shell, NULL);
2020 }
2021
2022 static void
2023 rb_shell_constructed (GObject *object)
2024 {
2025 RBShell *shell;
2026 GSimpleAction *action;
2027
2028 gtk_init (NULL, NULL);
2029
2030 RB_CHAIN_GOBJECT_METHOD (rb_shell_parent_class, constructed, object);
2031
2032 shell = RB_SHELL (object);
2033
2034 /* create application actions */
2035 action = g_simple_action_new_stateful ("LoadURI", G_VARIANT_TYPE ("(sb)"), g_variant_new ("(bb)", FALSE, FALSE));
2036 g_signal_connect_object (action, "activate", G_CALLBACK (load_uri_action_cb), shell, 0);
2037 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
2038 g_object_unref (action);
2039
2040 action = g_simple_action_new ("ActivateSource", G_VARIANT_TYPE ("(su)"));
2041 g_signal_connect_object (action, "activate", G_CALLBACK (activate_source_action_cb), shell, 0);
2042 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
2043 g_object_unref (action);
2044
2045 action = g_simple_action_new ("Quit", NULL);
2046 g_signal_connect_object (action, "activate", G_CALLBACK (quit_action_cb), shell, 0);
2047 g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
2048 g_object_unref (action);
2049
2050 /* construct enough of the rest of it to display the window if required */
2051
2052 shell->priv->settings = g_settings_new ("org.gnome.rhythmbox");
2053
2054 shell->priv->actiongroup = gtk_action_group_new ("MainActions");
2055 gtk_action_group_set_translation_domain (shell->priv->actiongroup,
2056 GETTEXT_PACKAGE);
2057 gtk_action_group_add_actions (shell->priv->actiongroup,
2058 rb_shell_actions,
2059 rb_shell_n_actions, shell);
2060 gtk_action_group_add_toggle_actions (shell->priv->actiongroup,
2061 rb_shell_toggle_entries,
2062 rb_shell_n_toggle_entries,
2063 shell);
2064
2065 /* Translators: this is the short label for the 'add music' action */
2066 gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicAdd"), C_("Library", "Import"));
2067 /* Translators: this is the short label for the 'view all tracks' action */
2068 gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "ViewAll"), _("Show All"));
2069
2070 construct_db (shell);
2071
2072 construct_widgets (shell);
2073 }
2074
2075 static gboolean
2076 rb_shell_window_state_cb (GtkWidget *widget,
2077 GdkEventWindowState *event,
2078 RBShell *shell)
2079 {
2080 shell->priv->iconified = ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) != 0);
2081
2082 if (event->changed_mask & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) {
2083 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0,
2084 rb_shell_get_visibility (shell));
2085 }
2086
2087 /* don't save maximized state when is hidden */
2088 if (!gtk_widget_get_visible (shell->priv->window))
2089 return FALSE;
2090
2091 if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
2092 gboolean maximised = ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0);
2093
2094 if (maximised != g_settings_get_boolean (shell->priv->settings, "maximized")) {
2095 g_settings_set_boolean (shell->priv->settings,
2096 "maximized",
2097 maximised);
2098 }
2099 rb_shell_sync_window_state (shell, TRUE);
2100 rb_shell_sync_paned (shell);
2101 }
2102
2103 return FALSE;
2104 }
2105
2106 static gboolean
2107 rb_shell_visibility_changing (RBShell *shell, gboolean initial, gboolean visible)
2108 {
2109 return visible;
2110 }
2111
2112 static gboolean
2113 rb_shell_get_visibility (RBShell *shell)
2114 {
2115 GdkWindowState state;
2116
2117 if (!gtk_widget_get_realized (shell->priv->window))
2118 return FALSE;
2119 if (shell->priv->iconified)
2120 return FALSE;
2121
2122 state = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (shell->priv->window)));
2123 if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED))
2124 return FALSE;
2125
2126 return TRUE;
2127 }
2128
2129 static void
2130 rb_shell_set_visibility (RBShell *shell,
2131 gboolean initial,
2132 gboolean visible)
2133 {
2134 gboolean really_visible;
2135
2136 rb_profile_start ("changing shell visibility");
2137
2138 if (visible == rb_shell_get_visibility (shell)) {
2139 rb_profile_end ("changing shell visibility");
2140 return;
2141 }
2142
2143 really_visible = visible;
2144 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGING], 0, initial, visible, &really_visible);
2145
2146 if (really_visible) {
2147 rb_debug ("showing main window");
2148 rb_shell_sync_window_state (shell, FALSE);
2149
2150 gtk_widget_show (GTK_WIDGET (shell->priv->window));
2151 gtk_window_deiconify (GTK_WINDOW (shell->priv->window));
2152
2153 if (gtk_widget_get_realized (GTK_WIDGET (shell->priv->window)))
2154 rb_shell_present (shell, gtk_get_current_event_time (), NULL);
2155 else
2156 gtk_widget_show_all (GTK_WIDGET (shell->priv->window));
2157
2158 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0, visible);
2159 } else {
2160 rb_debug ("hiding main window");
2161 shell->priv->iconified = TRUE;
2162 gtk_window_iconify (GTK_WINDOW (shell->priv->window));
2163
2164 g_signal_emit (shell, rb_shell_signals[VISIBILITY_CHANGED], 0, FALSE);
2165 }
2166
2167 rb_profile_end ("changing shell visibility");
2168 }
2169
2170 static void
2171 sync_window_settings (GSettings *settings, RBShell *shell)
2172 {
2173 int width, height;
2174 int oldwidth, oldheight;
2175 int oldx, oldy;
2176 int x, y;
2177 int pos;
2178
2179 gtk_window_get_size (GTK_WINDOW (shell->priv->window), &width, &height);
2180
2181 g_settings_get (shell->priv->settings, "size", "(ii)", &oldwidth, &oldheight);
2182 if ((width != oldwidth) || (height != oldheight)) {
2183 rb_debug ("storing window size of %d:%d", width, height);
2184 g_settings_set (shell->priv->settings, "size", "(ii)", width, height);
2185 }
2186
2187 gtk_window_get_position (GTK_WINDOW(shell->priv->window), &x, &y);
2188 g_settings_get (shell->priv->settings, "position", "(ii)", &oldx, &oldy);
2189 if ((x != oldx) || (y != oldy)) {
2190 rb_debug ("storing window position of %d:%d", x, y);
2191 g_settings_set (shell->priv->settings, "position", "(ii)", x, y);
2192 }
2193
2194 pos = gtk_paned_get_position (GTK_PANED (shell->priv->paned));
2195 rb_debug ("paned position %d", pos);
2196
2197 if (pos != g_settings_get_int (shell->priv->settings, "paned-position")) {
2198 g_settings_set_int (shell->priv->settings, "paned-position", pos);
2199 }
2200
2201 pos = gtk_paned_get_position (GTK_PANED (shell->priv->right_paned));
2202 rb_debug ("right_paned position %d", pos);
2203
2204 if (pos != g_settings_get_int (shell->priv->settings, "right-paned-position")) {
2205 g_settings_set_int (shell->priv->settings, "right-paned-position", pos);
2206 }
2207
2208 pos = gtk_paned_get_position (GTK_PANED (shell->priv->queue_paned));
2209 rb_debug ("sidebar paned position %d", pos);
2210
2211 if (pos != g_settings_get_int (shell->priv->settings, "display-page-tree-height")) {
2212 g_settings_set_int (shell->priv->settings, "display-page-tree-height", pos);
2213 }
2214 }
2215
2216 static gboolean
2217 rb_shell_window_configure_cb (GtkWidget *win,
2218 GdkEventConfigure *event,
2219 RBShell *shell)
2220 {
2221 if (g_settings_get_boolean (shell->priv->settings, "maximized") || shell->priv->iconified)
2222 return FALSE;
2223
2224 rb_settings_delayed_sync (shell->priv->settings,
2225 (RBDelayedSyncFunc) sync_window_settings,
2226 g_object_ref (shell),
2227 g_object_unref);
2228 return FALSE;
2229 }
2230
2231 static gboolean
2232 rb_shell_window_delete_cb (GtkWidget *win,
2233 GdkEventAny *event,
2234 RBShell *shell)
2235 {
2236 if (shell->priv->party_mode) {
2237 return TRUE;
2238 }
2239
2240 rb_shell_quit (shell, NULL);
2241
2242 return TRUE;
2243 }
2244
2245 static gboolean
2246 rb_shell_key_press_event_cb (GtkWidget *win,
2247 GdkEventKey *event,
2248 RBShell *shell)
2249 {
2250 #ifndef HAVE_MMKEYS
2251 return FALSE;
2252 #else
2253
2254 gboolean retval = TRUE;
2255
2256 switch (event->keyval) {
2257 case XF86XK_Back:
2258 rb_shell_player_do_previous (shell->priv->player_shell, NULL);
2259 break;
2260 case XF86XK_Forward:
2261 rb_shell_player_do_next (shell->priv->player_shell, NULL);
2262 break;
2263 default:
2264 retval = FALSE;
2265 }
2266
2267 return retval;
2268 #endif /* !HAVE_MMKEYS */
2269 }
2270
2271 static void
2272 rb_shell_sync_window_state (RBShell *shell,
2273 gboolean dont_maximise)
2274 {
2275 GdkGeometry hints;
2276 int width, height;
2277 int x, y;
2278
2279 rb_profile_start ("syncing window state");
2280
2281 if (!dont_maximise) {
2282 if (g_settings_get_boolean (shell->priv->settings, "maximized"))
2283 gtk_window_maximize (GTK_WINDOW (shell->priv->window));
2284 else
2285 gtk_window_unmaximize (GTK_WINDOW (shell->priv->window));
2286 }
2287
2288 g_settings_get (shell->priv->settings, "size", "(ii)", &width, &height);
2289
2290 gtk_window_set_default_size (GTK_WINDOW (shell->priv->window), width, height);
2291 gtk_window_resize (GTK_WINDOW (shell->priv->window), width, height);
2292 gtk_window_set_geometry_hints (GTK_WINDOW (shell->priv->window),
2293 NULL,
2294 &hints,
2295 0);
2296
2297 g_settings_get (shell->priv->settings, "position", "(ii)", &x, &y);
2298 gtk_window_move (GTK_WINDOW (shell->priv->window), x, y);
2299 rb_profile_end ("syncing window state");
2300 }
2301
2302 static void
2303 display_page_selected_cb (RBDisplayPageTree *display_page_tree,
2304 RBDisplayPage *page,
2305 RBShell *shell)
2306 {
2307 rb_debug ("page selected");
2308 rb_shell_select_page (shell, page);
2309 }
2310
2311 gboolean
2312 rb_shell_activate_source (RBShell *shell, RBSource *source, guint play, GError **error)
2313 {
2314 RhythmDBEntry *entry;
2315 /* FIXME
2316 *
2317 * this doesn't work correctly yet, but it's still an improvement on the
2318 * previous behaviour.
2319 *
2320 * with crossfading enabled, this fades out the current song, but
2321 * doesn't start the new one.
2322 */
2323
2324 /* Select the new one, and optionally start it playing */
2325 rb_shell_select_page (shell, RB_DISPLAY_PAGE (source));
2326
2327 switch (play) {
2328 case RB_SHELL_ACTIVATION_SELECT:
2329 return TRUE;
2330
2331 case RB_SHELL_ACTIVATION_PLAY:
2332 entry = rb_shell_player_get_playing_entry (shell->priv->player_shell);
2333 if (entry != NULL) {
2334 rhythmdb_entry_unref (entry);
2335 return TRUE;
2336 }
2337 /* fall through */
2338 case RB_SHELL_ACTIVATION_ALWAYS_PLAY:
2339 rb_shell_player_set_playing_source (shell->priv->player_shell, source);
2340 return rb_shell_player_playpause (shell->priv->player_shell, FALSE, error);
2341
2342 default:
2343 return FALSE;
2344 }
2345 }
2346
2347 static void
2348 rb_shell_db_save_error_cb (RhythmDB *db,
2349 const char *uri, const GError *error,
2350 RBShell *shell)
2351 {
2352 rb_error_dialog (GTK_WINDOW (shell->priv->window),
2353 _("Error while saving song information"),
2354 "%s", error->message);
2355 }
2356
2357 /**
2358 * rb_shell_get_source_by_entry_type:
2359 * @shell: the #RBShell
2360 * @type: entry type for which to find a source
2361 *
2362 * Looks up and returns the source that owns entries of the specified
2363 * type.
2364 *
2365 * Return value: (transfer none): source instance, if any
2366 */
2367 RBSource *
2368 rb_shell_get_source_by_entry_type (RBShell *shell,
2369 RhythmDBEntryType *type)
2370 {
2371 return g_hash_table_lookup (shell->priv->sources_hash, type);
2372 }
2373
2374 /**
2375 * rb_shell_register_entry_type_for_source:
2376 * @shell: the #RBShell
2377 * @source: the #RBSource to register
2378 * @type: the #RhythmDBEntryType to register for
2379 *
2380 * Registers a source as the owner of entries of the specified type.
2381 * The main effect of this is that calling #rb_shell_get_source_by_entry_type
2382 * with the same entry type will return the source. A source should only
2383 * be registered as the owner of a single entry type.
2384 */
2385 void
2386 rb_shell_register_entry_type_for_source (RBShell *shell,
2387 RBSource *source,
2388 RhythmDBEntryType *type)
2389 {
2390 if (shell->priv->sources_hash == NULL) {
2391 shell->priv->sources_hash = g_hash_table_new (g_direct_hash,
2392 g_direct_equal);
2393 }
2394 g_assert (g_hash_table_lookup (shell->priv->sources_hash, type) == NULL);
2395 g_hash_table_insert (shell->priv->sources_hash, type, source);
2396 }
2397
2398 /**
2399 * rb_shell_append_display_page:
2400 * @shell: the #RBShell
2401 * @page: the new #RBDisplayPage
2402 * @parent: (allow-none): the parent page for the new page
2403 *
2404 * Adds a new display page to the shell.
2405 */
2406 void
2407 rb_shell_append_display_page (RBShell *shell, RBDisplayPage *page, RBDisplayPage *parent)
2408 {
2409 if (RB_IS_SOURCE (page)) {
2410 shell->priv->sources = g_list_append (shell->priv->sources, RB_SOURCE (page));
2411 }
2412
2413 g_signal_connect_object (G_OBJECT (page), "deleted",
2414 G_CALLBACK (rb_shell_display_page_deleted_cb), shell, 0);
2415
2416 gtk_notebook_append_page (GTK_NOTEBOOK (shell->priv->notebook),
2417 GTK_WIDGET (page),
2418 gtk_label_new (""));
2419 gtk_widget_show (GTK_WIDGET (page));
2420
2421 rb_display_page_model_add_page (shell->priv->display_page_model, page, parent);
2422 }
2423
2424 static void
2425 rb_shell_playlist_added_cb (RBPlaylistManager *mgr,
2426 RBSource *source,
2427 RBShell *shell)
2428 {
2429 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_PLAYLISTS);
2430 }
2431
2432 static void
2433 rb_shell_playlist_created_cb (RBPlaylistManager *mgr,
2434 RBSource *source,
2435 RBShell *shell)
2436 {
2437 g_settings_set_boolean (shell->priv->settings, "display-page-tree-visible", TRUE);
2438
2439 rb_shell_sync_window_state (shell, FALSE);
2440 }
2441
2442 static void
2443 rb_shell_medium_added_cb (RBRemovableMediaManager *mgr,
2444 RBSource *source,
2445 RBShell *shell)
2446 {
2447 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_DEVICES);
2448 }
2449
2450 static void
2451 rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell)
2452 {
2453
2454 rb_debug ("display page deleted");
2455
2456 if (RB_IS_SOURCE (page)) {
2457 RhythmDBEntryType *entry_type;
2458 RBSource *source = RB_SOURCE (page);
2459
2460 /* remove from the map if the source owns the type */
2461 g_object_get (source, "entry-type", &entry_type, NULL);
2462 if (rb_shell_get_source_by_entry_type (shell, entry_type) == source) {
2463 g_hash_table_remove (shell->priv->sources_hash, entry_type);
2464 }
2465 g_object_unref (entry_type);
2466
2467 if (source == rb_shell_player_get_playing_source (shell->priv->player_shell) ||
2468 source == rb_shell_player_get_active_source (shell->priv->player_shell)) {
2469 rb_shell_player_stop (shell->priv->player_shell);
2470 }
2471
2472 shell->priv->sources = g_list_remove (shell->priv->sources, source);
2473 }
2474
2475 if (page == shell->priv->selected_page) {
2476 if (page != RB_DISPLAY_PAGE (shell->priv->library_source)) {
2477 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
2478 } else {
2479 rb_shell_select_page (shell, NULL);
2480 }
2481 }
2482
2483 rb_display_page_model_remove_page (shell->priv->display_page_model, page);
2484 gtk_notebook_remove_page (GTK_NOTEBOOK (shell->priv->notebook),
2485 gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
2486 GTK_WIDGET (page)));
2487 }
2488
2489 static void
2490 rb_shell_playing_source_changed_cb (RBShellPlayer *player,
2491 RBSource *source,
2492 RBShell *shell)
2493 {
2494 rb_debug ("playing source changed");
2495 if (source != RB_SOURCE (shell->priv->queue_source)) {
2496 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
2497 }
2498 }
2499
2500 static void
2501 rb_shell_playing_from_queue_cb (RBShellPlayer *player,
2502 GParamSpec *param,
2503 RBShell *shell)
2504 {
2505 gboolean from_queue;
2506
2507 g_object_get (player, "playing-from-queue", &from_queue, NULL);
2508 if (!g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar")) {
2509 RBSource *source;
2510 source = rb_shell_player_get_playing_source (shell->priv->player_shell);
2511 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
2512 } else {
2513 RBSource *source;
2514 RhythmDBEntry *entry;
2515 RhythmDBEntryType *entry_type;
2516
2517 /* if playing from the queue, show the playing entry as playing in the
2518 * registered source for its type, so it makes sense when 'jump to current'
2519 * jumps to it there.
2520 */
2521 entry = rb_shell_player_get_playing_entry (shell->priv->player_shell);
2522 if (entry == NULL)
2523 return;
2524
2525 entry_type = rhythmdb_entry_get_entry_type (entry);
2526 source = rb_shell_get_source_by_entry_type (shell, entry_type);
2527 if (source != NULL) {
2528 RBEntryViewState state;
2529 RBEntryView *songs;
2530
2531 songs = rb_source_get_entry_view (source);
2532 if (songs != NULL) {
2533 state = from_queue ? RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_NOT_PLAYING;
2534 rb_entry_view_set_state (songs, state);
2535 }
2536 }
2537 rhythmdb_entry_unref (entry);
2538
2539 source = rb_shell_player_get_active_source (shell->priv->player_shell);
2540 rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
2541 }
2542 }
2543
2544 static void
2545 rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
2546 {
2547 int pagenum;
2548
2549 if (shell->priv->selected_page == page)
2550 return;
2551
2552 rb_debug ("selecting page %p", page);
2553
2554 if (shell->priv->selected_page) {
2555 rb_display_page_deselected (shell->priv->selected_page);
2556 gtk_ui_manager_remove_ui (shell->priv->ui_manager, shell->priv->source_ui_merge_id);
2557 }
2558
2559 shell->priv->selected_page = page;
2560 rb_display_page_selected (shell->priv->selected_page);
2561
2562 /* show page */
2563 pagenum = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
2564 GTK_WIDGET (page));
2565 gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook), pagenum);
2566
2567 g_signal_handlers_block_by_func (shell->priv->display_page_tree,
2568 G_CALLBACK (display_page_selected_cb),
2569 shell);
2570 rb_display_page_tree_select (shell->priv->display_page_tree, page);
2571 g_signal_handlers_unblock_by_func (shell->priv->display_page_tree,
2572 G_CALLBACK (display_page_selected_cb),
2573 shell);
2574
2575 /* update services */
2576 if (RB_IS_SOURCE (page)) {
2577 RBSource *source = RB_SOURCE (page);
2578 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source);
2579 rb_shell_player_set_selected_source (shell->priv->player_shell, source);
2580 g_object_set (shell->priv->playlist_manager, "source", source, NULL);
2581 g_object_set (shell->priv->removable_media_manager, "source", source, NULL);
2582 } else {
2583 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, NULL);
2584 rb_shell_player_set_selected_source (shell->priv->player_shell, NULL); /* ? */
2585
2586 /* clear playlist-manager:source? */
2587 /* clear removable-media-manager:source? */
2588 }
2589 rb_statusbar_set_page (shell->priv->statusbar, page);
2590
2591 g_object_notify (G_OBJECT (shell), "selected-page");
2592 }
2593
2594 static void
2595 rb_shell_player_window_title_changed_cb (RBShellPlayer *player,
2596 const char *window_title,
2597 RBShell *shell)
2598 {
2599 rb_shell_set_window_title (shell, window_title);
2600 }
2601
2602 static void
2603 rb_shell_set_window_title (RBShell *shell,
2604 const char *window_title)
2605 {
2606 if (window_title == NULL) {
2607 rb_debug ("clearing title");
2608
2609 g_free (shell->priv->cached_title);
2610 shell->priv->cached_title = NULL;
2611
2612 gtk_window_set_title (GTK_WINDOW (shell->priv->window),
2613 _("Rhythmbox"));
2614 }
2615 else {
2616 gboolean playing;
2617 char *title;
2618
2619 rb_shell_player_get_playing (shell->priv->player_shell, &playing, NULL);
2620
2621 if (shell->priv->cached_title &&
2622 !strcmp (shell->priv->cached_title, window_title) &&
2623 playing == shell->priv->cached_playing) {
2624 return;
2625 }
2626 g_free (shell->priv->cached_title);
2627 shell->priv->cached_title = g_strdup (window_title);
2628 shell->priv->cached_playing = playing;
2629
2630 rb_debug ("setting title to \"%s\"", window_title);
2631 if (!playing) {
2632 /* Translators: %s is the song name */
2633 title = g_strdup_printf (_("%s (Paused)"), window_title);
2634 gtk_window_set_title (GTK_WINDOW (shell->priv->window),
2635 title);
2636 g_free (title);
2637 } else {
2638 gtk_window_set_title (GTK_WINDOW (shell->priv->window),
2639 window_title);
2640 }
2641 }
2642 }
2643
2644 static void
2645 rb_shell_view_statusbar_changed_cb (GtkAction *action,
2646 RBShell *shell)
2647 {
2648 g_settings_set_boolean (shell->priv->settings,
2649 "statusbar-hidden",
2650 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
2651
2652 rb_shell_sync_statusbar_visibility (shell);
2653 }
2654
2655 static void
2656 rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
2657 RBShell *shell)
2658 {
2659 gboolean queue_as_sidebar = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
2660 /* maybe use a settings binding? */
2661 g_settings_set_boolean (shell->priv->settings,
2662 "queue-as-sidebar",
2663 queue_as_sidebar);
2664
2665 if (queue_as_sidebar &&
2666 shell->priv->selected_page == RB_DISPLAY_PAGE (shell->priv->queue_source)) {
2667 /* queue no longer exists as a source, so change to the library */
2668 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
2669 }
2670
2671 rb_shell_playing_from_queue_cb (shell->priv->player_shell, NULL, shell);
2672
2673 rb_shell_sync_pane_visibility (shell);
2674 }
2675
2676 static void
2677 rb_shell_view_party_mode_changed_cb (GtkAction *action,
2678 RBShell *shell)
2679 {
2680 shell->priv->party_mode = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
2681 rb_shell_sync_party_mode (shell);
2682 }
2683
2684 static void
2685 rb_shell_cmd_about (GtkAction *action,
2686 RBShell *shell)
2687 {
2688 const char **tem;
2689 GString *comment;
2690
2691 const char *authors[] = {
2692 "",
2693 #include "MAINTAINERS.tab"
2694 "",
2695 NULL,
2696 #include "MAINTAINERS.old.tab"
2697 "",
2698 NULL,
2699 #include "AUTHORS.tab"
2700 NULL
2701 };
2702
2703 const char *documenters[] = {
2704 #include "DOCUMENTERS.tab"
2705 NULL
2706 };
2707
2708 const char *translator_credits = _("translator-credits");
2709
2710 const char *license[] = {
2711 N_("Rhythmbox is free software; you can redistribute it and/or modify\n"
2712 "it under the terms of the GNU General Public License as published by\n"
2713 "the Free Software Foundation; either version 2 of the License, or\n"
2714 "(at your option) any later version.\n"),
2715 N_("Rhythmbox is distributed in the hope that it will be useful,\n"
2716 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
2717 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
2718 "GNU General Public License for more details.\n"),
2719 N_("You should have received a copy of the GNU General Public License\n"
2720 "along with Rhythmbox; if not, write to the Free Software Foundation, Inc.,\n"
2721 "51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n")
2722 };
2723
2724 char *license_trans;
2725
2726 authors[0] = _("Maintainers:");
2727 for (tem = authors; *tem != NULL; tem++)
2728 ;
2729 *tem = _("Former Maintainers:");
2730 for (; *tem != NULL; tem++)
2731 ;
2732 *tem = _("Contributors:");
2733
2734 comment = g_string_new (_("Music management and playback software for GNOME."));
2735
2736 license_trans = g_strconcat (_(license[0]), "\n", _(license[1]), "\n",
2737 _(license[2]), "\n", NULL);
2738
2739 gtk_show_about_dialog (GTK_WINDOW (shell->priv->window),
2740 "version", VERSION,
2741 "copyright", "Copyright \xc2\xa9 2005 - 2009 The Rhythmbox authors\nCopyright \xc2\xa9 2003 - 2005 Colin Walters\nCopyright \xc2\xa9 2002, 2003 Jorn Baayen",
2742 "license", license_trans,
2743 "website-label", _("Rhythmbox Website"),
2744 "website", "http://www.gnome.org/projects/rhythmbox",
2745 "comments", comment->str,
2746 "authors", (const char **) authors,
2747 "documenters", (const char **) documenters,
2748 "translator-credits", strcmp (translator_credits, "translator-credits") != 0 ? translator_credits : NULL,
2749 "logo-icon-name", "rhythmbox",
2750 NULL);
2751 g_string_free (comment, TRUE);
2752 g_free (license_trans);
2753 }
2754
2755 /**
2756 * rb_shell_toggle_visibility:
2757 * @shell: the #RBShell
2758 *
2759 * Toggles the visibility of the main Rhythmbox window.
2760 */
2761 void
2762 rb_shell_toggle_visibility (RBShell *shell)
2763 {
2764 gboolean visible;
2765
2766 visible = rb_shell_get_visibility (shell);
2767
2768 rb_shell_set_visibility (shell, FALSE, !visible);
2769 }
2770
2771 static void
2772 rb_shell_cmd_quit (GtkAction *action,
2773 RBShell *shell)
2774 {
2775 rb_shell_quit (shell, NULL);
2776 }
2777
2778 static void
2779 rb_shell_cmd_contents (GtkAction *action,
2780 RBShell *shell)
2781 {
2782 GError *error = NULL;
2783
2784 gtk_show_uri (gtk_widget_get_screen (shell->priv->window),
2785 "ghelp:rhythmbox",
2786 gtk_get_current_event_time (),
2787 &error);
2788
2789 if (error != NULL) {
2790 rb_error_dialog (NULL, _("Couldn't display help"),
2791 "%s", error->message);
2792 g_error_free (error);
2793 }
2794 }
2795
2796 static void
2797 rb_shell_cmd_preferences (GtkAction *action,
2798 RBShell *shell)
2799 {
2800 RBShellPreferences *prefs;
2801
2802 g_object_get (shell, "prefs", &prefs, NULL);
2803
2804 gtk_window_present (GTK_WINDOW (prefs));
2805 g_object_unref (prefs);
2806 }
2807
2808 static gboolean
2809 rb_shell_plugins_window_delete_cb (GtkWidget *window,
2810 GdkEventAny *event,
2811 gpointer data)
2812 {
2813 gtk_widget_hide (window);
2814
2815 return TRUE;
2816 }
2817
2818 static void
2819 rb_shell_plugins_response_cb (GtkDialog *dialog,
2820 int response_id,
2821 gpointer data)
2822 {
2823 if (response_id == GTK_RESPONSE_CLOSE)
2824 gtk_widget_hide (GTK_WIDGET (dialog));
2825 }
2826
2827 static void
2828 rb_shell_cmd_plugins (GtkAction *action,
2829 RBShell *shell)
2830 {
2831 if (shell->priv->plugins == NULL) {
2832 GtkWidget *content_area;
2833 GtkWidget *manager;
2834
2835 shell->priv->plugins = gtk_dialog_new_with_buttons (_("Configure Plugins"),
2836 GTK_WINDOW (shell->priv->window),
2837 GTK_DIALOG_DESTROY_WITH_PARENT,
2838 GTK_STOCK_CLOSE,
2839 GTK_RESPONSE_CLOSE,
2840 NULL);
2841 content_area = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->plugins));
2842 gtk_container_set_border_width (GTK_CONTAINER (shell->priv->plugins), 5);
2843 gtk_box_set_spacing (GTK_BOX (content_area), 2);
2844
2845 g_signal_connect_object (G_OBJECT (shell->priv->plugins),
2846 "delete_event",
2847 G_CALLBACK (rb_shell_plugins_window_delete_cb),
2848 NULL, 0);
2849 g_signal_connect_object (G_OBJECT (shell->priv->plugins),
2850 "response",
2851 G_CALLBACK (rb_shell_plugins_response_cb),
2852 NULL, 0);
2853
2854 manager = peas_gtk_plugin_manager_new (NULL);
2855 gtk_widget_show_all (GTK_WIDGET (manager));
2856 gtk_box_pack_start (GTK_BOX (content_area), manager, TRUE, TRUE, 0);
2857 gtk_window_set_default_size (GTK_WINDOW (shell->priv->plugins), 600, 400);
2858 }
2859
2860 gtk_window_present (GTK_WINDOW (shell->priv->plugins));
2861 }
2862
2863 static void
2864 rb_shell_cmd_add_music (GtkAction *action, RBShell *shell)
2865 {
2866 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
2867 rb_library_source_show_import_dialog (shell->priv->library_source);
2868 }
2869
2870 static gboolean
2871 quit_timeout (gpointer dummy)
2872 {
2873 GDK_THREADS_ENTER ();
2874 rb_debug ("quit damn you");
2875 gtk_main_quit ();
2876 GDK_THREADS_LEAVE ();
2877 return FALSE;
2878 }
2879
2880 /**
2881 * rb_shell_quit:
2882 * @shell: the #RBShell
2883 * @error: not used
2884 *
2885 * Begins the process of shutting down Rhythmbox. This function will
2886 * return. The error parameter and return value only exist because this
2887 * function is part of the DBus interface.
2888 *
2889 * Return value: not important
2890 */
2891 gboolean
2892 rb_shell_quit (RBShell *shell,
2893 GError **error)
2894 {
2895 rb_debug ("Quitting");
2896
2897 /* Stop the playing source, if any */
2898 rb_shell_player_stop (shell->priv->player_shell);
2899
2900 rb_podcast_manager_shutdown (shell->priv->podcast_manager);
2901
2902 rb_shell_shutdown (shell);
2903 rb_shell_sync_state (shell);
2904
2905 g_application_release (G_APPLICATION (shell));
2906
2907 g_timeout_add_seconds (10, quit_timeout, NULL);
2908 return TRUE;
2909 }
2910
2911 static gboolean
2912 idle_handle_load_complete (RBShell *shell)
2913 {
2914 gboolean loaded, scanned;
2915 GDK_THREADS_ENTER ();
2916
2917 rb_debug ("load complete");
2918
2919 rb_playlist_manager_load_playlists (shell->priv->playlist_manager);
2920 rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_PLAYLISTS));
2921 shell->priv->load_complete = TRUE;
2922 shell->priv->save_playlist_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_playlist_manager, shell);
2923
2924 if (shell->priv->no_registration == FALSE) {
2925 g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned);
2926 g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", TRUE, scanned));
2927 }
2928
2929 rhythmdb_start_action_thread (shell->priv->db);
2930
2931 GDK_THREADS_LEAVE ();
2932
2933 return FALSE;
2934 }
2935
2936 static void
2937 rb_shell_load_complete_cb (RhythmDB *db,
2938 RBShell *shell)
2939 {
2940 g_idle_add ((GSourceFunc) idle_handle_load_complete, shell);
2941 }
2942
2943 static void
2944 rb_shell_sync_pane_visibility (RBShell *shell)
2945 {
2946 GtkAction *action;
2947 gboolean queue_as_sidebar = g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar");
2948
2949 if (shell->priv->queue_source != NULL) {
2950 g_object_set (shell->priv->queue_source, "visibility", !queue_as_sidebar, NULL);
2951 }
2952
2953 if (queue_as_sidebar) {
2954 gtk_widget_show (shell->priv->queue_sidebar);
2955 } else {
2956 gtk_widget_hide (shell->priv->queue_sidebar);
2957 }
2958
2959 action = gtk_action_group_get_action (shell->priv->actiongroup,
2960 "ViewQueueAsSidebar");
2961 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), queue_as_sidebar);
2962 }
2963
2964 static gboolean
2965 window_state_event_cb (GtkWidget *widget,
2966 GdkEventWindowState *event,
2967 RBShell *shell)
2968 {
2969 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
2970 rb_shell_present (shell, gtk_get_current_event_time (), NULL);
2971 }
2972
2973 return TRUE;
2974 }
2975
2976 static void
2977 rb_shell_sync_party_mode (RBShell *shell)
2978 {
2979 GtkAction *action;
2980
2981 /* party mode does not use gsettings as a model since it
2982 should not be persistent */
2983
2984 /* disable/enable quit action */
2985 action = gtk_action_group_get_action (shell->priv->actiongroup, "MusicQuit");
2986 g_object_set (action, "sensitive", !shell->priv->party_mode, NULL);
2987
2988 /* show/hide queue as sidebar ? */
2989
2990 g_object_set (shell->priv->player_shell, "queue-only", shell->priv->party_mode, NULL);
2991
2992 /* Set playlist manager source to the current source to update properties */
2993 if (shell->priv->selected_page && RB_IS_SOURCE (shell->priv->selected_page)) {
2994 RBSource *source = RB_SOURCE (shell->priv->selected_page);
2995 g_object_set (shell->priv->playlist_manager, "source", source, NULL);
2996 rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source);
2997 }
2998
2999 gtk_window_set_keep_above (GTK_WINDOW (shell->priv->window), shell->priv->party_mode);
3000 if (shell->priv->party_mode) {
3001 gtk_window_fullscreen (GTK_WINDOW (shell->priv->window));
3002 gtk_window_stick (GTK_WINDOW (shell->priv->window));
3003 g_signal_connect (shell->priv->window, "window-state-event", G_CALLBACK (window_state_event_cb), shell);
3004 } else {
3005 gtk_window_unstick (GTK_WINDOW (shell->priv->window));
3006 gtk_window_unfullscreen (GTK_WINDOW (shell->priv->window));
3007 g_signal_handlers_disconnect_by_func (shell->priv->window, window_state_event_cb, shell);
3008 }
3009 }
3010
3011 static void
3012 rb_shell_sync_statusbar_visibility (RBShell *shell)
3013 {
3014 gboolean visible;
3015 GtkAction *action;
3016
3017 visible = !g_settings_get_boolean (shell->priv->settings, "statusbar-hidden");
3018
3019 action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewStatusbar");
3020 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
3021
3022 gtk_widget_set_visible (GTK_WIDGET (shell->priv->statusbar), visible);
3023 }
3024
3025 static void
3026 rb_shell_sync_paned (RBShell *shell)
3027 {
3028 gtk_paned_set_position (GTK_PANED (shell->priv->right_paned),
3029 g_settings_get_int (shell->priv->settings, "right-paned-position"));
3030 gtk_paned_set_position (GTK_PANED (shell->priv->paned),
3031 g_settings_get_int (shell->priv->settings, "paned-position"));
3032 gtk_paned_set_position (GTK_PANED (shell->priv->queue_paned),
3033 g_settings_get_int (shell->priv->settings, "display-page-tree-height"));
3034 }
3035
3036 static void
3037 paned_size_allocate_cb (GtkWidget *widget,
3038 GtkAllocation *allocation,
3039 RBShell *shell)
3040 {
3041 rb_settings_delayed_sync (shell->priv->settings,
3042 (RBDelayedSyncFunc) sync_window_settings,
3043 g_object_ref (shell),
3044 g_object_unref);
3045 }
3046
3047 static void
3048 display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
3049 RBDisplayPage *page,
3050 GtkSelectionData *data,
3051 RBShell *shell)
3052 {
3053 if (page == NULL) {
3054 RBSource *source;
3055 source = rb_playlist_manager_new_playlist_from_selection_data (shell->priv->playlist_manager,
3056 data);
3057 page = RB_DISPLAY_PAGE (source);
3058 }
3059
3060 if (page != NULL) {
3061 rb_display_page_receive_drag (page, data);
3062 }
3063
3064 }
3065
3066 static void
3067 rb_shell_cmd_current_song (GtkAction *action,
3068 RBShell *shell)
3069 {
3070 rb_debug ("current song");
3071
3072 rb_shell_jump_to_current (shell);
3073 }
3074
3075 static void
3076 rb_shell_cmd_view_all (GtkAction *action,
3077 RBShell *shell)
3078 {
3079 if (RB_IS_SOURCE (shell->priv->selected_page)) {
3080 RBSource *source = RB_SOURCE (shell->priv->selected_page);
3081 rb_debug ("view all");
3082
3083 rb_source_reset_filters (source);
3084 }
3085 }
3086
3087 static void
3088 rb_shell_jump_to_entry_with_source (RBShell *shell,
3089 RBSource *source,
3090 RhythmDBEntry *entry)
3091 {
3092 RBEntryView *songs;
3093
3094 g_return_if_fail (entry != NULL);
3095
3096 if ((source == RB_SOURCE (shell->priv->queue_source) &&
3097 g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar")) ||
3098 source == NULL) {
3099 RhythmDBEntryType *entry_type;
3100 entry_type = rhythmdb_entry_get_entry_type (entry);
3101 source = rb_shell_get_source_by_entry_type (shell, entry_type);
3102 }
3103 if (source == NULL)
3104 return;
3105
3106 songs = rb_source_get_entry_view (source);
3107 rb_shell_select_page (shell, RB_DISPLAY_PAGE (source));
3108
3109 if (songs != NULL) {
3110 rb_entry_view_scroll_to_entry (songs, entry);
3111 rb_entry_view_select_entry (songs, entry);
3112 }
3113 }
3114
3115 static void
3116 rb_shell_play_entry (RBShell *shell,
3117 RhythmDBEntry *entry)
3118 {
3119 rb_shell_player_stop (shell->priv->player_shell);
3120 rb_shell_jump_to_entry_with_source (shell, NULL, entry);
3121 rb_shell_player_play_entry (shell->priv->player_shell, entry, NULL);
3122 }
3123
3124 static void
3125 rb_shell_jump_to_current (RBShell *shell)
3126 {
3127 RBSource *source;
3128 RhythmDBEntry *playing;
3129
3130 source = rb_shell_player_get_playing_source (shell->priv->player_shell);
3131
3132 g_return_if_fail (source != NULL);
3133
3134 playing = rb_shell_player_get_playing_entry (shell->priv->player_shell);
3135
3136 rb_shell_jump_to_entry_with_source (shell, source, playing);
3137 rhythmdb_entry_unref (playing);
3138 }
3139
3140 void
3141 rb_shell_notify_custom (RBShell *shell,
3142 guint timeout,
3143 const char *primary,
3144 const char *secondary,
3145 const char *image_uri,
3146 gboolean requested)
3147 {
3148 g_signal_emit (shell, rb_shell_signals[NOTIFY_CUSTOM], 0, timeout, primary, secondary, image_uri, requested);
3149 }
3150
3151 /**
3152 * rb_shell_do_notify:
3153 * @shell: the #RBShell
3154 * @requested: if %TRUE, the notification was requested by some explicit user action
3155 * @error: not used
3156 *
3157 * Displays a notification of the current playing track.
3158 *
3159 * Return value: not important
3160 */
3161 gboolean
3162 rb_shell_do_notify (RBShell *shell, gboolean requested, GError **error)
3163 {
3164 g_signal_emit (shell, rb_shell_signals[NOTIFY_PLAYING_ENTRY], 0, requested);
3165 return TRUE;
3166 }
3167
3168 /**
3169 * rb_shell_error_quark:
3170 *
3171 * Returns the #GQuark used for #RBShell errors
3172 *
3173 * Return value: shell error #GQuark
3174 */
3175 GQuark
3176 rb_shell_error_quark (void)
3177 {
3178 static GQuark quark = 0;
3179 if (!quark)
3180 quark = g_quark_from_static_string ("rb_shell_error");
3181
3182 return quark;
3183 }
3184
3185 static void
3186 session_save_state_cb (EggSMClient *client,
3187 GKeyFile *key_file,
3188 RBShell *shell)
3189 {
3190 rb_debug ("session save-state");
3191 rb_shell_sync_state (shell);
3192 }
3193
3194 static void
3195 session_quit_cb (EggSMClient *client,
3196 RBShell *shell)
3197 {
3198 rb_debug ("session quit");
3199 rb_shell_quit (shell, NULL);
3200 }
3201
3202 static void
3203 rb_shell_session_init (RBShell *shell)
3204 {
3205 EggSMClient *sm_client;
3206
3207 sm_client = egg_sm_client_get ();
3208 g_signal_connect (sm_client, "save-state", G_CALLBACK (session_save_state_cb), shell);
3209 g_signal_connect (sm_client, "quit", G_CALLBACK (session_quit_cb), shell);
3210 }
3211
3212 /**
3213 * rb_shell_guess_source_for_uri:
3214 * @shell: the #RBSource
3215 * @uri: the URI to guess a source for
3216 *
3217 * Attempts to locate the source that should handle the specified URI.
3218 * This iterates through all sources, calling #rb_source_want_uri,
3219 * returning the source that returns the highest value.
3220 *
3221 * Return value: (transfer none): the most appropriate #RBSource for the uri
3222 */
3223 RBSource *
3224 rb_shell_guess_source_for_uri (RBShell *shell,
3225 const char *uri)
3226 {
3227 GList *t;
3228 RBSource *best = NULL;
3229 guint strength = 0;
3230
3231 for (t = shell->priv->sources; t != NULL; t = t->next) {
3232 guint s;
3233 RBSource *source;
3234
3235 source = (RBSource *)t->data;
3236 if (rb_source_uri_is_source (source, uri))
3237 return source;
3238
3239 s = rb_source_want_uri (source, uri);
3240 if (s > strength) {
3241 gchar *name;
3242
3243 g_object_get (source, "name", &name, NULL);
3244 rb_debug ("source %s returned strength %u for uri %s",
3245 name, s, uri);
3246 g_free (name);
3247
3248 strength = s;
3249 best = source;
3250 }
3251 }
3252
3253 return best;
3254 }
3255
3256 /* Load a URI representing an element of the given type, with
3257 * optional metadata
3258 */
3259 /**
3260 * rb_shell_add_uri:
3261 * @shell: the #RBShell
3262 * @uri: the URI to add
3263 * @title: optional title value for the URI
3264 * @genre: optional genre value for the URI
3265 * @error: returns error information
3266 *
3267 * Adds the specified URI to the Rhythmbox database. Whether the
3268 * title and genre specified are actually used is up to the source
3269 * that handles the URI
3270 *
3271 * Return value: TRUE if the URI was added successfully
3272 */
3273 gboolean
3274 rb_shell_add_uri (RBShell *shell,
3275 const char *uri,
3276 const char *title,
3277 const char *genre,
3278 GError **error)
3279 {
3280 RBSource *source;
3281
3282 source = rb_shell_guess_source_for_uri (shell, uri);
3283 if (source == NULL) {
3284 g_set_error (error,
3285 RB_SHELL_ERROR,
3286 RB_SHELL_ERROR_NO_SOURCE_FOR_URI,
3287 _("No registered source can handle URI %s"),
3288 uri);
3289 return FALSE;
3290 }
3291
3292 rb_source_add_uri (source, uri, title, genre, NULL, NULL, NULL);
3293 return TRUE;
3294 }
3295
3296 typedef struct {
3297 RBShell *shell;
3298 char *uri;
3299 gboolean play;
3300 RBSource *playlist_source;
3301 gboolean can_use_playlist;
3302 gboolean source_is_entry;
3303 } PlaylistParseData;
3304
3305 static void
3306 handle_playlist_entry_cb (TotemPlParser *playlist,
3307 const char *uri,
3308 GHashTable *metadata,
3309 PlaylistParseData *data)
3310 {
3311 RBSource *source;
3312
3313 /*
3314 * Track whether the same playlist-handling source
3315 * wants all the URIs from the playlist; if it does,
3316 * then we'll just give the playlist URI to the source.
3317 */
3318 if (data->can_use_playlist == FALSE)
3319 return;
3320
3321 source = rb_shell_guess_source_for_uri (data->shell, uri);
3322 if (data->playlist_source == NULL) {
3323 if (source != NULL && rb_source_try_playlist (source)) {
3324 data->playlist_source = RB_SOURCE (g_object_ref (source));
3325 data->source_is_entry = rb_source_uri_is_source (source, uri);
3326 } else {
3327 data->can_use_playlist = FALSE;
3328 }
3329 } else if (data->playlist_source != source) {
3330 g_object_unref (data->playlist_source);
3331 data->playlist_source = NULL;
3332 data->can_use_playlist = FALSE;
3333 data->source_is_entry = FALSE;
3334 }
3335 }
3336
3337 static void
3338 shell_load_uri_done (RBSource *source, const char *uri, RBShell *shell)
3339 {
3340 RhythmDBEntry *entry;
3341
3342 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
3343 if (entry) {
3344 rb_shell_play_entry (shell, entry);
3345 } else {
3346 rb_debug ("unable to find entry for uri %s", uri);
3347 }
3348 }
3349
3350 static void
3351 load_uri_finish (RBShell *shell, RBSource *entry_source, RhythmDBEntry *entry, gboolean play)
3352 {
3353 if (play == FALSE) {
3354 rb_debug ("didn't want to do anything anyway");
3355 } else if (entry != NULL) {
3356 rb_debug ("found an entry to play");
3357 rb_shell_play_entry (shell, entry);
3358 } else if (entry_source != NULL) {
3359 char *name;
3360 GError *error = NULL;
3361
3362 g_object_get (entry_source, "name", &name, NULL);
3363 if (rb_shell_activate_source (shell, entry_source, RB_SHELL_ACTIVATION_ALWAYS_PLAY, &error) == FALSE) {
3364 rb_debug ("couldn't activate source %s: %s", name, error->message);
3365 g_clear_error (&error);
3366 } else {
3367 rb_debug ("activated source '%s'", name);
3368 }
3369 g_free (name);
3370 } else {
3371 rb_debug ("couldn't do anything");
3372 }
3373 }
3374
3375 static void
3376 load_uri_parser_finished_cb (GObject *parser, GAsyncResult *res, PlaylistParseData *data)
3377 {
3378 TotemPlParserResult result;
3379 RBSource *entry_source = NULL;
3380 GError *error = NULL;
3381
3382 result = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (parser), res, &error);
3383 g_object_unref (parser);
3384
3385 if (error != NULL) {
3386 rb_debug ("parsing %s as a playlist failed: %s", data->uri, error->message);
3387 g_clear_error (&error);
3388 } else if (result == TOTEM_PL_PARSER_RESULT_UNHANDLED) {
3389 rb_debug ("%s unhandled", data->uri);
3390 } else if (result == TOTEM_PL_PARSER_RESULT_IGNORED) {
3391 rb_debug ("%s ignored", data->uri);
3392 }
3393
3394 if (result == TOTEM_PL_PARSER_RESULT_SUCCESS) {
3395
3396 if (data->can_use_playlist && data->playlist_source) {
3397 rb_debug ("adding playlist %s to source", data->uri);
3398 rb_source_add_uri (data->playlist_source, data->uri, NULL, NULL, NULL, NULL, NULL);
3399
3400 /* FIXME: We need some way to determine whether the URI as
3401 * given will appear in the db, or whether something else will.
3402 * This hack assumes we'll never add local playlists to the db
3403 * directly.
3404 */
3405 if (rb_uri_is_local (data->uri) && (data->source_is_entry == FALSE)) {
3406 data->play = FALSE;
3407 }
3408
3409 if (data->source_is_entry != FALSE) {
3410 entry_source = data->playlist_source;
3411 }
3412 } else {
3413 rb_debug ("adding %s as a static playlist", data->uri);
3414 if (!rb_playlist_manager_parse_file (data->shell->priv->playlist_manager,
3415 data->uri,
3416 &error)) {
3417 rb_debug ("unable to parse %s as a static playlist: %s", data->uri, error->message);
3418 g_clear_error (&error);
3419 }
3420 data->play = FALSE; /* maybe we should play the new playlist? */
3421 }
3422 } else {
3423 RBSource *source;
3424
3425 source = rb_shell_guess_source_for_uri (data->shell, data->uri);
3426 if (source != NULL) {
3427 char *name;
3428 g_object_get (source, "name", &name, NULL);
3429 if (rb_source_uri_is_source (source, data->uri)) {
3430 rb_debug ("%s identifies source %s", data->uri, name);
3431 entry_source = source;
3432 } else if (data->play) {
3433 rb_debug ("adding %s to source %s, will play it when it shows up", data->uri, name);
3434 rb_source_add_uri (source, data->uri, NULL, NULL, (RBSourceAddCallback) shell_load_uri_done, g_object_ref (data->shell), g_object_unref);
3435 data->play = FALSE;
3436 } else {
3437 rb_debug ("just adding %s to source %s", data->uri, name);
3438 rb_source_add_uri (source, data->uri, NULL, NULL, NULL, NULL, NULL);
3439 }
3440 g_free (name);
3441 } else {
3442 rb_debug ("couldn't find a source for %s, trying to add it anyway", data->uri);
3443 if (!rb_shell_add_uri (data->shell, data->uri, NULL, NULL, &error)) {
3444 rb_debug ("couldn't do it: %s", error->message);
3445 g_clear_error (&error);
3446 }
3447 }
3448 }
3449
3450 load_uri_finish (data->shell, entry_source, NULL, data->play);
3451
3452 if (data->playlist_source != NULL) {
3453 g_object_unref (data->playlist_source);
3454 }
3455 g_object_unref (data->shell);
3456 g_free (data->uri);
3457 g_free (data);
3458 }
3459
3460 /**
3461 * rb_shell_load_uri:
3462 * @shell: the #RBShell
3463 * @uri: the URI to load
3464 * @play: if TRUE, start playing the URI (if possible)
3465 * @error: returns error information
3466 *
3467 * Loads a URI representing a single song, a directory, a playlist, or
3468 * an internet radio station, and optionally starts playing it.
3469 *
3470 * For playlists containing only stream URLs, we either add the playlist
3471 * itself (if it's remote) or each URL from it (if it's local). The main
3472 * reason for this is so clicking on stream playlist links in web browsers
3473 * works properly - the playlist file will be downloaded to /tmp/, and
3474 * we can't add that to the database, so we need to add the stream URLs
3475 * instead.
3476 *
3477 * Return value: TRUE if the URI was added successfully
3478 */
3479 gboolean
3480 rb_shell_load_uri (RBShell *shell,
3481 const char *uri,
3482 gboolean play,
3483 GError **error)
3484 {
3485 RhythmDBEntry *entry;
3486
3487 /* If the URI points to a Podcast, pass it on to the Podcast source */
3488 if (rb_uri_could_be_podcast (uri, NULL)) {
3489 rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source));
3490 rb_podcast_source_add_feed (shell->priv->podcast_source, uri);
3491 return TRUE;
3492 }
3493
3494 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
3495
3496 if (entry == NULL) {
3497 TotemPlParser *parser;
3498 PlaylistParseData *data;
3499
3500 data = g_new0 (PlaylistParseData, 1);
3501 data->shell = g_object_ref (shell);
3502 data->uri = g_strdup (uri);
3503 data->play = play;
3504 data->can_use_playlist = TRUE;
3505 data->source_is_entry = FALSE;
3506 data->playlist_source = NULL;
3507
3508 rb_debug ("adding uri %s, play %d", uri, play);
3509 parser = totem_pl_parser_new ();
3510
3511 g_signal_connect_data (parser, "entry-parsed",
3512 G_CALLBACK (handle_playlist_entry_cb),
3513 data, NULL, 0);
3514
3515 totem_pl_parser_add_ignored_mimetype (parser, "x-directory/normal");
3516 totem_pl_parser_add_ignored_mimetype (parser, "inode/directory");
3517 totem_pl_parser_add_ignored_scheme (parser, "cdda");
3518 g_object_set (parser, "recurse", FALSE, NULL);
3519 if (rb_debug_matches ("totem_pl_parser_parse_async", "totem-pl-parser.c")) {
3520 g_object_set (parser, "debug", TRUE, NULL);
3521 }
3522
3523 totem_pl_parser_parse_async (parser, uri, FALSE, NULL, (GAsyncReadyCallback)load_uri_parser_finished_cb, data);
3524 } else {
3525 load_uri_finish (shell, NULL, entry, play);
3526 }
3527
3528 return TRUE;
3529 }
3530
3531 /**
3532 * rb_shell_get_party_mode:
3533 * @shell: the #RBShell
3534 *
3535 * Returns %TRUE if the shell is in party mode
3536 *
3537 * Return value: %TRUE if the shell is in party mode
3538 */
3539 gboolean
3540 rb_shell_get_party_mode (RBShell *shell)
3541 {
3542 return shell->priv->party_mode;
3543 }
3544
3545 /**
3546 * rb_shell_present:
3547 * @shell: the #RBShell
3548 * @timestamp: GTK timestamp to use (for focus-stealing prevention)
3549 * @error: not used
3550 *
3551 * Attempts to display the main window to the user. See #gtk_window_present for details.
3552 *
3553 * Return value: not used.
3554 */
3555 gboolean
3556 rb_shell_present (RBShell *shell,
3557 guint32 timestamp,
3558 GError **error)
3559 {
3560 rb_profile_start ("presenting shell");
3561
3562 rb_debug ("presenting with timestamp %u", timestamp);
3563 gtk_widget_show (GTK_WIDGET (shell->priv->window));
3564 gtk_window_present_with_time (GTK_WINDOW (shell->priv->window), timestamp);
3565 gtk_window_set_skip_taskbar_hint (GTK_WINDOW (shell->priv->window), FALSE);
3566
3567 rb_profile_end ("presenting shell");
3568
3569 return TRUE;
3570 }
3571
3572 /**
3573 * rb_shell_activate_source_by_uri:
3574 * @shell: the #RBShell
3575 * @source_uri: URI for the source to activate
3576 * @play: 0: select source, 1: play source if not playing, 2: play source
3577 * @error: returns error information
3578 *
3579 * Searches for a source matching @source_uri and if found, selects it,
3580 * and depending on the value of @play, may start playing from it.
3581 * Device-based sources will match the device node or mount point URI.
3582 * Other types of sources may have their own URI scheme or format.
3583 * This is part of the DBus interface.
3584 *
3585 * Return value: %TRUE if successful
3586 */
3587 gboolean
3588 rb_shell_activate_source_by_uri (RBShell *shell,
3589 const char *source_uri,
3590 guint play,
3591 GError **error)
3592 {
3593 GList *t;
3594 GFile *f;
3595 char *uri;
3596
3597 /* ensure the argument is actually a URI */
3598 f = g_file_new_for_commandline_arg (source_uri);
3599 uri = g_file_get_uri (f);
3600 g_object_unref (f);
3601
3602 for (t = shell->priv->sources; t != NULL; t = t->next) {
3603 RBSource *source;
3604
3605 source = (RBSource *)t->data;
3606 if (rb_source_uri_is_source (source, uri)) {
3607 rb_debug ("found source for uri %s", uri);
3608 g_free (uri);
3609 return rb_shell_activate_source (shell, source, play, error);
3610 }
3611 }
3612
3613 g_set_error (error,
3614 RB_SHELL_ERROR,
3615 RB_SHELL_ERROR_NO_SOURCE_FOR_URI,
3616 _("No registered source matches URI %s"),
3617 uri);
3618 g_free (uri);
3619 return FALSE;
3620 }
3621
3622 /**
3623 * rb_shell_get_song_properties:
3624 * @shell: the #RBShell
3625 * @uri: the URI to query
3626 * @properties: (out callee-allocates) (element-type utf8 GObject.Value) returns the properties of the specified URI
3627 * @error: returns error information
3628 *
3629 * Gathers and returns all metadata (including extra metadata such as album
3630 * art URIs and lyrics) for the specified URI.
3631 *
3632 * Return value: %TRUE if the URI is found in the database
3633 */
3634 gboolean
3635 rb_shell_get_song_properties (RBShell *shell,
3636 const char *uri,
3637 GHashTable **properties,
3638 GError **error)
3639 {
3640 RhythmDBEntry *entry;
3641 RBStringValueMap *map;
3642
3643 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
3644
3645 if (entry == NULL) {
3646 g_set_error (error,
3647 RB_SHELL_ERROR,
3648 RB_SHELL_ERROR_NO_SUCH_URI,
3649 _("Unknown song URI: %s"),
3650 uri);
3651 return FALSE;
3652 }
3653
3654 map = rhythmdb_entry_gather_metadata (shell->priv->db, entry);
3655 *properties = rb_string_value_map_steal_hashtable (map);
3656 g_object_unref (map);
3657
3658 return (*properties != NULL);
3659 }
3660
3661 /**
3662 * rb_shell_set_song_property:
3663 * @shell: the #RBShell
3664 * @uri: the URI to modify
3665 * @propname: the name of the property to modify
3666 * @value: the new value to set
3667 * @error: returns error information
3668 *
3669 * Attempts to set a property of a database entry identified by its URI.
3670 * If the URI identifies a file and the property is one associated with a
3671 * file metadata tag, the new value will be written to the file.
3672 *
3673 * Return value: %TRUE if the property was set successfully.
3674 */
3675 gboolean
3676 rb_shell_set_song_property (RBShell *shell,
3677 const char *uri,
3678 const char *propname,
3679 const GValue *value,
3680 GError **error)
3681 {
3682 RhythmDBEntry *entry;
3683 GType proptype;
3684 int propid;
3685
3686 entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
3687
3688 if (entry == NULL) {
3689 g_set_error (error,
3690 RB_SHELL_ERROR,
3691 RB_SHELL_ERROR_NO_SUCH_URI,
3692 _("Unknown song URI: %s"),
3693 uri);
3694 return FALSE;
3695 }
3696
3697 if ((propid = rhythmdb_propid_from_nice_elt_name (shell->priv->db, (guchar *) propname)) < 0) {
3698 g_set_error (error,
3699 RB_SHELL_ERROR,
3700 RB_SHELL_ERROR_NO_SUCH_PROPERTY,
3701 _("Unknown property %s"),
3702 propname);
3703 return FALSE;
3704 }
3705
3706 proptype = rhythmdb_get_property_type (shell->priv->db, propid);
3707 if (G_VALUE_TYPE (value) != proptype) {
3708 GValue convert = {0,};
3709 g_value_init (&convert, proptype);
3710 if (g_value_transform (value, &convert) == FALSE) {
3711 g_value_unset (&convert);
3712 g_set_error (error,
3713 RB_SHELL_ERROR,
3714 RB_SHELL_ERROR_INVALID_PROPERTY_TYPE,
3715 _("Invalid property type %s for property %s"),
3716 g_type_name (G_VALUE_TYPE (value)),
3717 propname);
3718 return FALSE;
3719 } else {
3720 rhythmdb_entry_set (shell->priv->db, entry, propid, &convert);
3721 g_value_unset (&convert);
3722 }
3723 } else {
3724 rhythmdb_entry_set (shell->priv->db, entry, propid, value);
3725 }
3726 rhythmdb_commit (shell->priv->db);
3727 return TRUE;
3728 }
3729
3730 static void
3731 rb_shell_volume_widget_changed_cb (GtkScaleButton *vol,
3732 gdouble volume,
3733 RBShell *shell)
3734 {
3735 if (!shell->priv->syncing_volume) {
3736 g_object_set (shell->priv->player_shell, "volume", volume, NULL);
3737 }
3738 }
3739
3740 static void
3741 rb_shell_player_volume_changed_cb (RBShellPlayer *player,
3742 GParamSpec *arg,
3743 RBShell *shell)
3744 {
3745 float volume;
3746
3747 g_object_get (player, "volume", &volume, NULL);
3748 shell->priv->syncing_volume = TRUE;
3749 gtk_scale_button_set_value (GTK_SCALE_BUTTON (shell->priv->volume_button), volume);
3750 shell->priv->syncing_volume = FALSE;
3751
3752 }
3753
3754 static GtkBox*
3755 rb_shell_get_box_for_ui_location (RBShell *shell, RBShellUILocation location)
3756 {
3757 GtkBox *box = NULL;
3758
3759 switch (location) {
3760 case RB_SHELL_UI_LOCATION_SIDEBAR:
3761 box = shell->priv->sidebar_container;
3762 break;
3763 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR:
3764 box = shell->priv->right_sidebar_container;
3765 break;
3766 case RB_SHELL_UI_LOCATION_MAIN_TOP:
3767 box = shell->priv->top_container;
3768 break;
3769 case RB_SHELL_UI_LOCATION_MAIN_BOTTOM:
3770 box = shell->priv->bottom_container;
3771 break;
3772 default:
3773 break;
3774 }
3775
3776 return box;
3777 }
3778
3779 /**
3780 * rb_shell_add_widget:
3781 * @shell: the #RBShell
3782 * @widget: the #GtkWidget to insert into the main window
3783 * @location: the location at which to insert the widget
3784 * @expand: whether the widget should be given extra space
3785 * @fill: whether the widget should fill all space allocated to it
3786 *
3787 * Adds a widget to the main Rhythmbox window. See #gtk_box_pack_start for
3788 * details on how the expand and fill parameters work.
3789 */
3790 void
3791 rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location, gboolean expand, gboolean fill)
3792 {
3793 GtkBox *box;
3794
3795 switch (location) {
3796 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR:
3797 if (!shell->priv->right_sidebar_widget_count)
3798 gtk_widget_show (GTK_WIDGET (shell->priv->right_sidebar_container));
3799 shell->priv->right_sidebar_widget_count++;
3800 default:
3801 box = rb_shell_get_box_for_ui_location (shell, location);
3802 g_return_if_fail (box != NULL);
3803
3804 gtk_box_pack_start (box, widget, expand, fill, 0);
3805 break;
3806 }
3807 }
3808
3809 /**
3810 * rb_shell_remove_widget:
3811 * @shell: the #RBShell
3812 * @widget: the #GtkWidget to remove from the main window
3813 * @location: the UI location to which the widget was originally added
3814 *
3815 * Removes a widget added with #rb_shell_add_widget from the main window.
3816 */
3817 void
3818 rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location)
3819 {
3820 GtkBox *box;
3821
3822 switch (location) {
3823 case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR:
3824 shell->priv->right_sidebar_widget_count--;
3825 if (!shell->priv->right_sidebar_widget_count)
3826 gtk_widget_hide (GTK_WIDGET (shell->priv->right_sidebar_container));
3827 default:
3828 box = rb_shell_get_box_for_ui_location (shell, location);
3829 g_return_if_fail (box != NULL);
3830
3831 gtk_container_remove (GTK_CONTAINER (box), widget);
3832 break;
3833 }
3834 }
3835
3836 /* This should really be standard. */
3837 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3838
3839 GType
3840 rb_shell_activation_type_get_type (void)
3841 {
3842 static GType etype = 0;
3843
3844 if (etype == 0) {
3845 static const GEnumValue values[] = {
3846 ENUM_ENTRY (RB_SHELL_ACTIVATION_SELECT, "select"),
3847 ENUM_ENTRY (RB_SHELL_ACTIVATION_PLAY, "play"),
3848 ENUM_ENTRY (RB_SHELL_ACTIVATION_ALWAYS_PLAY, "always-play"),
3849 { 0, 0, 0 }
3850 };
3851
3852 etype = g_enum_register_static ("RBShellActivationType", values);
3853 }
3854
3855 return etype;
3856 }
3857
3858 GType
3859 rb_shell_ui_location_get_type (void)
3860 {
3861 static GType etype = 0;
3862
3863 if (etype == 0) {
3864 static const GEnumValue values[] = {
3865 ENUM_ENTRY (RB_SHELL_UI_LOCATION_SIDEBAR, "sidebar"),
3866 ENUM_ENTRY (RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR, "right-sidebar"),
3867 ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_TOP, "main-top"),
3868 ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_BOTTOM, "main-bottom"),
3869 { 0, 0, 0 }
3870 };
3871
3872 etype = g_enum_register_static ("RBShellUILocation", values);
3873 }
3874
3875 return etype;
3876 }
3877
3878 GType
3879 rb_shell_error_get_type (void)
3880 {
3881 static GType etype = 0;
3882
3883 if (etype == 0) {
3884 static const GEnumValue values[] = {
3885 ENUM_ENTRY(RB_SHELL_ERROR_NO_SUCH_URI, "no-such-uri"),
3886 ENUM_ENTRY(RB_SHELL_ERROR_NO_SUCH_PROPERTY, "no-such-property"),
3887 ENUM_ENTRY(RB_SHELL_ERROR_IMMUTABLE_PROPERTY, "immutable-property"),
3888 ENUM_ENTRY(RB_SHELL_ERROR_INVALID_PROPERTY_TYPE, "invalid-property-type"),
3889 ENUM_ENTRY(RB_SHELL_ERROR_NO_SOURCE_FOR_URI, "no-source-for-uri"),
3890 { 0, 0, 0 }
3891 };
3892 etype = g_enum_register_static ("RBShellErrorType", values);
3893 }
3894
3895 return etype;
3896 }