RSS

(root)/calliope : /src/ui/calliope.c (revision 450)

Line Revision Contents
1 17 /*  Calliope Music Player
2 185  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3 17  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15 267  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 17  */
17 24
18 #include <string.h>
19 17 #include <glib/gstdio.h>
20 #include <gdk/gdkkeysyms.h>
21 #include "format.h"
22 #include "process.h"
23 #include "main.h"
24 #include "conftool.h"
25 #include "files.h"
26 #include "filesearch.h"
27 191 #include "gstreamer.h"
28 17 #include "library.h"
29 #include "cdsource.h"
30 #include "filesource.h"
31 24 #include "conftool-ui.h"
32 17 #include "calliope.h"
33 #include "uimanager.h"
34 #include "browser.h"
35 #include "info.h"
36 #include "player.h"
37 #include "addmusicdialog.h"
38 450 #include "status-dialog.h"
39 17
40 // FIXME: should be in conftool!
41 static int run_dialog_with_panel(GtkWidget *dialog, GtkWidget *notebook, GtkWidget *panel, const char *title) {
42         int page_num=-1;
43         if (panel!=NULL) {
44                 gtk_widget_show(panel);
45                 // FIXME: should be possible to get the panel title from its dialog title
46                 page_num=gtk_notebook_append_page(GTK_NOTEBOOK(notebook), panel, gtk_label_new(title));
47                 gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 0);
48 267         };
49
50 17         int response=gtk_dialog_run(GTK_DIALOG(dialog));
51 267
52 17         if (page_num!=-1) {
53                 gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), page_num);
54                 gtk_widget_hide(panel);
55         };
56 267         gtk_widget_hide(dialog);
57
58 17         return response;
59 };
60
61 // FIXME
62 //      - update ui based on signals from gst - not signals sent or user actions
63
64 267 //FIXME: Does calliope even need to be an object ?? maybe a singleton ??
65 17 //  singletons are horrible and a hack used in languages that for some reason
66 //  can't have global functions.
67
68 24 G_DEFINE_TYPE(Calliope, calliope, GTK_TYPE_WINDOW);
69 17
70 // FIXME: should this really be a class ?? Name one scenario we would want a
71 // calliope process with two discrete calliope objects.
72
73 // FIXME: remember at the moment THIS IS DUPLICATED IN ADDMUSICDIALOG !! because it can't librarylist without it :(
74 struct CalliopePrivate {
75 189         gboolean disposed;
76 267
77 17         AddMusicDialog *add_music_dialog;
78
79 267         // To keep a dynamic menu of drives and disks, we need to store the
80 17         // GtkActions and the ui entry ID's for each device.
81         //
82 28         //GnomeVFSVolumeMonitor *vmon;
83         //GtkActionGroup *cd_actions;
84         //GHashTable *drive_ui_entry_table, *volume_ui_entry_table;
85 267
86 17         // FIXME: take out, conftool should deal
87         //GConfChangeSet *changeset;
88 267
89 402         char *default_view_config_string;
90
91 17         MusicSource *selected_source;
92 267         int selected_source_status_changed_handler_id;
93
94 24         MusicSourceView *info_closure_view; GtkTreePath *info_closure_path;
95         int info_closure_idle_id;
96 17
97 267         GtkWidget *player, *browser, *info;
98 17         GtkWidget *appbar;
99         GtkWidget *open_dir_file_chooser;
100 267
101 193         GtkWidget *master_vbox;
102 17 };
103
104 187 static void dispose(GObject *self);
105 static void finalize(GObject *self);
106 17
107 350 //static void init_device_menu(Calliope *self);
108 17
109 static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, Calliope *self);
110 24 static void browser_selection_changed(Browser *browser, MusicSourceView *selected_view, GtkTreePath *selected_path, Calliope *self);
111 17 static void selected_source_notify(Browser *browser, GParamSpec *pspec, Calliope *self);
112 static void selected_source_status_changed(MusicSource *source, const char *status, Calliope *self);
113 28 /*static void drive_connected(GnomeVFSVolumeMonitor *vmon, GnomeVFSDrive *drive, Calliope *self);
114 17 static void drive_disconnected(GnomeVFSVolumeMonitor *vmon, GnomeVFSDrive *drive, Calliope *self);
115 static void volume_mounted(GnomeVFSVolumeMonitor *vmon, GnomeVFSVolume *volume, Calliope *self);
116 static void volume_unmounted(GnomeVFSVolumeMonitor *vmon, GnomeVFSVolume *volume, Calliope *self);
117 28 */
118 356 #ifndef LIBRARY_DISABLE
119 446 static void new_action            (GtkAction *action, Calliope *self);
120 static void open_action           (GtkAction *action, Calliope *self);
121 static void add_music_action      (GtkAction *action, Calliope *self);
122 356 #endif
123 350 //static void play_cd_action(GtkAction *action, Calliope *self);
124 //static void import_cd_action(GtkAction *action, Calliope *self);
125 446 static void open_directory_action (GtkAction *action, Calliope *self);
126 static void close_action          (GtkAction *action, Calliope *self);
127 static void settings_action       (GtkAction *action, Calliope *self);
128 static void status_action         (GtkAction *action, Calliope *self);
129 static void exit_action           (GtkAction *action, Calliope *self);
130 17 // FIXME: for debugging only
131 446 static void dump_action          (GtkAction *action, Calliope *self);
132 17
133 static const char *ui_descr=
134         "<menubar name='MainMenu'>"
135         "<menu action='CalliopeMenu'>"
136 356 #ifndef LIBRARY_DISABLE
137 17         "       <menuitem action='New'/>"
138         "       <menuitem action='Open'/>"
139         "       <menuitem action='AddMusic'/>"
140 356 #endif
141 17         "       <separator/>"
142         "       <placeholder name='CDList'/>"
143         "       <menuitem action='OpenDir'/>"
144 446         "       <menuitem action='Close'/>"
145 17         "       <separator/>"
146         "       <menuitem action='Settings'/>"
147 446         "       <menuitem action='Status'/>"
148 17         "       <menuitem action='Dump'/>"
149         "       <menuitem action='Exit'/>"
150         "</menu></menubar>";
151 267
152 17 static GtkActionEntry actions[] = {
153 446     { "CalliopeMenu", NULL, "_Calliope" },
154 356 #ifndef LIBRARY_DISABLE
155 446     { "New",      GTK_STOCK_NEW,        "_Create New Database", "",           NULL, G_CALLBACK(new_action)            },
156     { "Open",     GTK_STOCK_OPEN,       "_Open Database",       "",           NULL, G_CALLBACK(open_action)           },
157     { "AddMusic", GTK_STOCK_ADD,        "_Add Music...",        "",           NULL, G_CALLBACK(add_music_action)      },
158 356 #endif
159 446     { "OpenDir",  GTK_STOCK_DIRECTORY,  "_Open Directory...",   "",           NULL, G_CALLBACK(open_directory_action) },
160     { "Close",    GTK_STOCK_CLOSE,      "_Close Source",        "<ctrl>W",    NULL, G_CALLBACK(close_action)          },
161     { "Settings", GTK_STOCK_PROPERTIES, "Se_ttings",            "",           NULL, G_CALLBACK(settings_action)       },
162     { "Status",   GTK_STOCK_INFO,       "_Status",              "",           NULL, G_CALLBACK(status_action)         },
163     { "Dump",     NULL,                 "_Dump Source",         "",           NULL, G_CALLBACK(dump_action)           },
164     { "Exit",     GTK_STOCK_QUIT,       "E_xit",                "<control>X", NULL, G_CALLBACK(exit_action)           }
165 17 };
166
167 static void calliope_class_init(CalliopeClass *cl) {
168 189         G_OBJECT_CLASS(cl)->dispose = dispose;
169 187         G_OBJECT_CLASS(cl)->finalize = finalize;
170         g_type_class_add_private (cl, sizeof(CalliopePrivate));
171 17 }
172
173 static void calliope_init(Calliope *self) {
174 140         SP = G_TYPE_INSTANCE_GET_PRIVATE(self, CALLIOPE_TYPE, CalliopePrivate);
175 267
176 189         SP->disposed = FALSE;
177 267
178 402         SP->default_view_config_string = NULL;
179
180 267         ui_manager_add_actions (ui, actions, G_N_ELEMENTS(actions), self, "actions-main");
181 188         ui_manager_add_ui_from_string (ui, ui_descr, "ui-main");
182 17
183 267         SP->selected_source = NULL;
184         SP->selected_source_status_changed_handler_id=0;
185 24         SP->info_closure_idle_id=0;
186 17
187 28         //init_device_menu(self);
188 267
189 187         // FIXME: our implementation of saving geometry is of course imperfect. Things like, what if the
190 267         // user has us on a 2nd monitor which isn't there on next run? I would love to leave window
191 187         // management up to the OS or a parent class, but this seems to be beyond them.
192 267         // I think the best hope is a GtkAppWindow class, which links to gconf? Or something, and can
193 187         // save and restore geometry info etc. How could we merge this with GtkUnique as well? I guess
194         // the GtkUnique could contain the GtkAppWindow. Yeah do that!
195 267         //
196 187         GSList *geometry = gconf_client_get_list (gconf, KEY_GEOMETRY, GCONF_VALUE_INT, NULL);
197 267         if (geometry!=NULL && g_slist_length(geometry) == 4) {
198 187                 GSList *node;
199 267                 int x = GPOINTER_TO_INT((node=geometry)->data),
200 187                     y = GPOINTER_TO_INT((node=node->next)->data),
201                         w = GPOINTER_TO_INT((node=node->next)->data),
202                         h = GPOINTER_TO_INT((node=node->next)->data);
203 267                 //GdkWindowState state = GPOINTER_TO_INT((node=node->next)->data);
204 187                 //printf ("Setting geometry: (%i, %i) %ix%i\n", x, y, w, h); fflush (stdout);
205 267
206 187                 gtk_window_move (GTK_WINDOW(self), x, y);
207                 gtk_window_set_default_size (GTK_WINDOW(self), w, h);
208                 //gdk_window_set_state (
209                 if (w < 0 || h < 0)
210                         gtk_window_maximize (GTK_WINDOW(self));
211         }
212         g_slist_free (geometry);
213
214 24         char *icon_file_path=g_build_filename("data", "calliope-icon.png", NULL);
215         if (!g_file_test(icon_file_path, G_FILE_TEST_EXISTS)) {
216                 g_free(icon_file_path);
217                 icon_file_path=g_build_filename(PACKAGE_DATA_DIR, "pixmaps", "calliope-icon.png", NULL);
218         };
219 267
220 24         if (g_file_test(icon_file_path, G_FILE_TEST_EXISTS)) {
221                 gtk_window_set_default_icon_from_file(icon_file_path, NULL);
222 17                 g_free(icon_file_path);
223         }
224 267
225         gtk_window_set_title(GTK_WINDOW(self), "Calliope");
226 24         gtk_window_add_accel_group(GTK_WINDOW(self), gtk_ui_manager_get_accel_group(GTK_UI_MANAGER(ui)));
227 267         g_signal_connect(self, "key-press-event", G_CALLBACK(key_press_event), self);
228 17         g_signal_connect(self, "destroy", gtk_main_quit, NULL);
229
230 24         SP->appbar=gtk_statusbar_new();
231 267
232 17         // FIXME: info needs a better name.
233 24         SP->info=info_new();
234 17
235         SP->browser=browser_new();
236 267         g_signal_connect(SP->browser, "selection-changed", G_CALLBACK(browser_selection_changed), self);
237 17         g_signal_connect(SP->browser, "notify::selected-source", G_CALLBACK(selected_source_notify), self);
238 267         selected_source_notify(BROWSER(SP->browser), NULL, self);
239 17
240 189         SP->player = player_new(BROWSER(SP->browser));
241 267
242 17         GtkWidget *hpaned=gtk_hpaned_new();
243         gtk_paned_add1(GTK_PANED(hpaned), SP->browser);
244 24         gtk_paned_add2(GTK_PANED(hpaned), SP->info);
245 267
246 193         SP->master_vbox = gtk_vbox_new(0, 0);
247         GtkWidget *menu = gtk_ui_manager_get_widget(GTK_UI_MANAGER(ui), "/MainMenu");
248         gtk_box_pack_start (GTK_BOX(SP->master_vbox), menu, 0, 1, 0);
249 189         if (SP->player!=NULL)
250 193                 gtk_box_pack_start (GTK_BOX(SP->master_vbox), SP->player, 0, 0, 0);
251         gtk_box_pack_start(GTK_BOX(SP->master_vbox), hpaned, 1, 1, 0);
252         gtk_box_pack_start(GTK_BOX(SP->master_vbox), SP->appbar, 0, 1, 0);
253         gtk_container_add (GTK_CONTAINER(self), SP->master_vbox);
254 17
255         // FIXME: I would like to receive drags from nautilus, azereus etc.
256         /*const GtkTargetEntry target[]={
257                 {       "dogs", 0, 1    }
258 267         };
259 17         gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, , GDK_ACTION_LINK);*/
260
261         //SP->changeset=gconf_change_set_new();
262 120         //GObject *peditor;
263 267
264 24         SP->add_music_dialog=NULL;
265 17         SP->add_music_dialog=add_music_dialog_new(BROWSER(SP->browser)->library_liststore);
266 267
267 17         /// play-dir dialog
268 267         //
269 120         //GtkWidget *dialog=WID("play_dir_dialog");
270 267         // FIXME: now this doesn't work !!
271 17         //peditor=gconf_peditor_new_boolean(SP->changeset, KEY_FILE_SOURCE_RECURSE, WID("play_dir_recurse_opt"), NULL);
272 267
273 17         /// play dir dialog
274 267         // FIXME: this isn't a folder chooser any more :(
275 40         SP->open_dir_file_chooser=WID("play_dir_location");
276 17 }
277
278 189 // Never forget dispose can get called multiple times!!!
279 static void dispose(GObject *object) {
280         Calliope *self = CALLIOPE(object);
281         if (SP->disposed==TRUE)
282                 return;
283
284 187         // Save window position.
285         int x, y, w, h;
286 267
287 187         // FIXME:
288 267         /* If you are saving and restoring your application's window positions, you should know that
289 187          * it's impossible for applications to do this without getting it somewhat wrong because
290          *  applications do not have sufficient knowledge of window manager state. The Correct Mechanism
291 267          * is to support the session management protocol (see the "GnomeClient" object in the GNOME
292          * libraries for example) and allow the window manager to save your window sizes and positions.
293 187          * */
294         // However, this is still valid on Windows.
295 267
296         GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(self));
297 187         if (gdk_window!=NULL) {
298                 // Window can be NULL if not yet realized.
299 267
300 187                 gtk_window_get_position (GTK_WINDOW(self), &x, &y);
301                 if (gdk_window_get_state(gdk_window) & GDK_WINDOW_STATE_MAXIMIZED)
302                         w = h = -1;
303                 else gtk_window_get_size (GTK_WINDOW(self), &w, &h);
304                 GSList *list = g_slist_build (4, x, y, w, h);
305                 //printf ("Saving geometry: (%i, %i) %ix%i.\n", x, y, w, h); fflush (stdout);
306 267
307 187                 GError *error = NULL;
308                 gconf_client_set_list (gconf, KEY_GEOMETRY, GCONF_VALUE_INT, list, &error);
309                 g_slist_free (list);
310 267
311 187                 if (error!=NULL)
312                         printf ("Unable to save window geometry - %s.\n", error->message);
313 267         };
314
315         ui_manager_remove_ui (ui, "ui-main");
316         ui_manager_remove_actions (ui, "actions-main", self);
317
318 193         // Destroy player before we are disposed. Some crazy bug happens otherwise where removing the UI
319         // actions causes a crash inside GTK+, even if the action group is empty .. but only when adding
320 267         // the entries again, or something. To reproduce it, move this next line of code to after
321         // parent_class->dispose is called, and run main-test. Should give you a GTK_IS_CONTAINER fail
322 193         // for some bizarre reason.
323         // FIXME: should really try and find out what causes this, but I have already wasted days of my
324         // life trying.
325         gtk_container_remove (GTK_CONTAINER(SP->master_vbox), SP->player);
326 267
327 189         SP->disposed = TRUE;
328 193
329 267         G_OBJECT_CLASS(calliope_parent_class)->dispose (object);
330 187 };
331
332 17 static void finalize(GObject *object) {
333         Calliope *self=CALLIOPE(object);
334 188
335 24         if (SP->add_music_dialog!=NULL)
336                 add_music_dialog_free(SP->add_music_dialog);
337 267
338         //g_hash_table_unref(SP->volume_ui_entry_table);
339         //g_hash_table_unref(SP->drive_ui_entry_table);
340 17
341 402         if (SP->default_view_config_string != NULL) g_free (SP->default_view_config_string);
342
343 17         G_OBJECT_CLASS(calliope_parent_class)->finalize(object);
344 193
345         //g_object_ref_sink (SP->player);
346         //g_object_unref (SP->player);
347 17 }
348
349 267 // FIXME: somehow can we make the device menu code shorter ?? or spin it off into
350 17 // lib ?
351
352 350 //static void init_device_menu(Calliope *self) {
353 267         /*SP->vmon=gnome_vfs_get_volume_monitor();
354 17         SP->drive_ui_entry_table=g_hash_table_new_full(g_int_hash, g_int_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);
355         SP->volume_ui_entry_table=g_hash_table_new_full(g_int_hash, g_int_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);
356 267
357 17         SP->cd_actions=gtk_action_group_new("CD actions");
358 267         gtk_ui_manager_insert_action_group(GTK_UI_MANAGER(ui), SP->cd_actions, 0);
359
360 17         g_signal_connect(SP->vmon, "volume-mounted", G_CALLBACK(volume_mounted), self);
361         g_signal_connect(SP->vmon, "volume-unmounted", G_CALLBACK(volume_unmounted), self);
362         g_signal_connect(SP->vmon, "drive-connected", G_CALLBACK(drive_connected), self);
363         g_signal_connect(SP->vmon, "drive-disconnected", G_CALLBACK(drive_disconnected), self);
364 267
365 17         GList *drives=gnome_vfs_volume_monitor_get_connected_drives(SP->vmon), *drive_node=drives;
366         while(drive_node!=NULL) {
367                 GnomeVFSDrive *drive=drive_node->data;
368                 GnomeVFSDeviceType drive_type=gnome_vfs_drive_get_device_type(drive);
369 28                 if (drive_type==GNOME_VFS_DEVICE_TYPE_CDROM ||
370                  drive_type==GNOME_VFS_DEVICE_TYPE_AUDIO_CD // FIXME: does this ever happen ??
371                 ) {
372 17                         drive_connected(SP->vmon, drive, self);
373 267
374 17                         GList *volumes=gnome_vfs_drive_get_mounted_volumes(drive), *volume_node=volumes;
375                         while (volume_node!=NULL) {
376 267                                 volume_mounted(SP->vmon, volume_node->data, self);
377 17                                 g_object_unref(volume_node->data);
378                                 volume_node=volume_node->next;
379 267                         };
380                         g_list_free(volumes);
381 17                 };
382                 g_object_unref(drive);
383                 drive_node=drive_node->next;
384         };
385 28         g_list_free(drives);*/
386 350 //};
387 17
388 // Normally ctrl+pagedn/up is taken by treeview to move around somehow, however in
389 // a notebook it makes more sense to have these keys move tabs so we pass them to
390 // the notebook.
391 267 //
392 17 static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, Calliope  *self) {
393         // FIXME: I think we could use key binding sets for this instead. I have tried
394         // before though and I can't make them work.
395 267
396 17         if (event->state & GDK_CONTROL_MASK) {
397 267
398 17                 if (event->keyval==GDK_Page_Up) {
399                         browser_move_page(BROWSER(SP->browser), GTK_DIR_LEFT);
400                         return TRUE;
401                 } else if (event->keyval==GDK_Page_Down) {
402                         browser_move_page(BROWSER(SP->browser), GTK_DIR_RIGHT);
403                         return TRUE;
404                 };
405 267
406 17                 // Volume change requires alt as well as ctrl simply because the treeview
407                 // takes ctrl+up/down as cursor control.
408                 // FIXME: Actually at the moment it doesn't.
409                 // FIXME: completely redesign pc keyboards to be more logical.
410                 //
411 //              if (event->state & GDK_ALT_MASK) {
412                         if (event->keyval==GDK_Up) {
413                                 player_change_volume(PLAYER(SP->player), GTK_DIR_UP);
414                                 return TRUE;
415                         } else if (event->keyval==GDK_Down) {
416                                 player_change_volume(PLAYER(SP->player), GTK_DIR_DOWN);
417                                 return TRUE;
418                         };
419 267         //      };
420 17         };
421
422         return FALSE;
423 };
424
425 24 static gboolean update_info_idle(Calliope *self) {
426 79         info_set_path(INFO(SP->info), SP->info_closure_view, SP->info_closure_path);
427 24         g_object_unref(SP->info_closure_view);
428 267         gtk_tree_path_free(SP->info_closure_path);
429 24         SP->info_closure_idle_id=0;
430         return FALSE;
431 };
432
433 static void browser_selection_changed(Browser *browser, MusicSourceView *selected_view, GtkTreePath *selected_path, Calliope *self) {
434         if (SP->info_closure_idle_id!=0) {
435                 g_object_unref(SP->info_closure_view);
436                 gtk_tree_path_free(SP->info_closure_path);
437         };
438 267
439 24         //printf("Got path: %s\n", gtk_tree_path_to_string(selected_path));fflush(stdout);
440 267
441 24         // We reference the view, so that source and view are guaranteed to be kept alive until the
442         // idle executes
443         // FIXME: surely a weak ref would be more appropriate .. ?
444         if (selected_view!=NULL && selected_path!=NULL) {
445                 SP->info_closure_view=selected_view; g_object_ref(selected_view);
446                 SP->info_closure_path=gtk_tree_path_copy(selected_path);
447 267
448 24                 if (SP->info_closure_idle_id==0) {
449                         SP->info_closure_idle_id=g_idle_add((GSourceFunc)update_info_idle, self);
450                 };
451         } else {
452                 if (SP->info_closure_idle_id!=0) {
453 267                         g_source_remove(SP->info_closure_idle_id);
454 24                         SP->info_closure_idle_id=0;
455                 }
456         }
457 };
458
459
460 17 // FIXME: this totally doesn't work especially on my laptop. Another problem is
461 // when I am on the laptop and I remove the drive the entire system crashes.
462
463 28 /*static void drive_connected(GnomeVFSVolumeMonitor *vmon, GnomeVFSDrive *drive, Calliope *self) {
464 17 //printf("drive connected\n");
465         GnomeVFSDeviceType drive_type=gnome_vfs_drive_get_device_type(drive);
466 28         if (drive_type!=GNOME_VFS_DEVICE_TYPE_CDROM && drive_type!=GNOME_VFS_DEVICE_TYPE_AUDIO_CD) return;
467 17
468         char *label=g_strdup_printf("Play '%s'", gnome_vfs_drive_get_display_name(drive)),
469                 *icon_name=gnome_vfs_drive_get_icon(drive);
470 267         UIEntry *ui_entry=ui_manager_add_menu_item(ui, SP->cd_actions,
471           "/MainMenu/CalliopeMenu/CDList", label, icon_name, GTK_STOCK_CDROM);
472 17         g_free(label); g_free(icon_name);
473         gtk_action_set_sensitive(ui_entry->action, TRUE);
474 267
475         g_object_set_data_full(G_OBJECT(ui_entry->action), "device-path",
476 17           gnome_vfs_drive_get_device_path(drive), (GDestroyNotify)g_free);
477         g_signal_connect(ui_entry->action, "activate", G_CALLBACK(play_cd_action), self);
478         gtk_action_set_sensitive(ui_entry->action, TRUE);
479         int *key=g_new(int, 1); *key=gnome_vfs_drive_get_id(drive);
480         g_hash_table_insert(SP->drive_ui_entry_table, key, ui_entry);
481 };
482
483 static void drive_disconnected(GnomeVFSVolumeMonitor *vmon, GnomeVFSDrive *drive, Calliope *self) {
484         printf("drive-disconnected: drive %X\n", drive);
485         int drive_id=gnome_vfs_drive_get_id(drive);
486         ui_manager_remove_entry(ui, g_hash_table_lookup(SP->drive_ui_entry_table, &drive_id));
487         g_hash_table_remove(SP->drive_ui_entry_table, &drive_id);
488 };
489
490 static void volume_mounted(GnomeVFSVolumeMonitor *vmon, GnomeVFSVolume *volume, Calliope *self) {
491         printf("Calliope::volume-mounted: volume %X, ", volume);
492         GnomeVFSDeviceType drive_type=gnome_vfs_volume_get_device_type(volume);
493 28         if (drive_type!=GNOME_VFS_DEVICE_TYPE_CDROM && drive_type!=GNOME_VFS_DEVICE_TYPE_AUDIO_CD) return;
494 17
495         int drive_id=gnome_vfs_drive_get_id(gnome_vfs_volume_get_drive(volume));
496         printf("drive_id %i\n", drive_id);
497
498         // Hide drive action
499         UIEntry *ui_entry=g_hash_table_lookup(SP->drive_ui_entry_table, &drive_id);
500         gtk_action_set_visible(ui_entry->action, FALSE);
501
502         char *label=g_strdup_printf("Play '%s'", gnome_vfs_volume_get_display_name(volume)),
503                 *icon_name=gnome_vfs_volume_get_icon(volume);
504 267         ui_entry=ui_manager_add_menu_item(ui, SP->cd_actions,
505 17           "/MainMenu/CalliopeMenu/CDList", label, icon_name, GTK_STOCK_CDROM);
506         g_free(label); g_free(icon_name);
507 267
508         g_object_set_data_full(G_OBJECT(ui_entry->action), "device-path",
509 17           gnome_vfs_volume_get_device_path(volume), (GDestroyNotify)g_free);
510         g_signal_connect(ui_entry->action, "activate", G_CALLBACK(play_cd_action), self);
511         gtk_action_set_sensitive(ui_entry->action, TRUE);
512         int *key=g_new(int, 1); *key=gnome_vfs_volume_get_id(volume);
513         g_hash_table_insert(SP->volume_ui_entry_table, key, ui_entry);
514 };
515
516 // FIXME: in theory here we need to check if the drive is now empty and if so,
517 // make the drive entry visible. In practice gnome-vfs seems to remove the
518 // drive too and connect a new one with a different id. I don't have any idea
519 267 // why.
520 17 static void volume_unmounted(GnomeVFSVolumeMonitor *vmon, GnomeVFSVolume *volume, Calliope *self) {
521 //printf("volume unmounted\n");
522         int volume_id=gnome_vfs_volume_get_id(volume);
523 267         ui_manager_remove_entry(ui, g_hash_table_lookup(SP->volume_ui_entry_table, &volume_id));
524 17         g_hash_table_remove(SP->volume_ui_entry_table, &volume_id);
525 };
526
527 28 */
528 172
529 // FIXME: it seems like tracking selected source ourselves is more difficult than just
530 // asking browser for it a lot.
531 static void selected_source_destroyed (Calliope *self, MusicSource *ex_source) {
532         if (SP->selected_source==ex_source)
533                 SP->selected_source = NULL;
534 };
535
536 17 static void selected_source_notify(Browser *browser, GParamSpec *pspec, Calliope *self) {
537 24         MusicSource *source;
538 172         browser_get_selected (browser, &source, NULL);
539 267
540         if (SP->selected_source!=NULL)
541 172                 g_signal_handler_disconnect (SP->selected_source, SP->selected_source_status_changed_handler_id);
542 267
543 172         SP->selected_source = source;
544 267
545 24         int cxt=gtk_statusbar_get_context_id(GTK_STATUSBAR(SP->appbar), "Database state"); // FIXME: wtf is this for
546 17         if (source==NULL)
547 24                 gtk_statusbar_push(GTK_STATUSBAR(SP->appbar), cxt, "No database loaded.");
548 17         else {
549 267                 char *msg=music_source_get_summary(source);
550 24                 gtk_statusbar_push(GTK_STATUSBAR(SP->appbar), cxt, msg);
551 267                 g_free(msg);
552 17
553                 SP->selected_source_status_changed_handler_id=g_signal_connect(source, "status-changed", G_CALLBACK(selected_source_status_changed), self);
554 172                 g_object_weak_ref (G_OBJECT(SP->selected_source), (GWeakNotify)selected_source_destroyed, self);
555 17         };
556 };
557
558 static void selected_source_status_changed(MusicSource *source, const char *status, Calliope *self) {
559 24         // FIXME: need to pop old msgs each time- just use 1 context I guess. I think.
560         int cxt=gtk_statusbar_get_context_id(GTK_STATUSBAR(SP->appbar), "Source info"); // FIXME: wtf is this for
561         gtk_statusbar_push(GTK_STATUSBAR(SP->appbar), cxt, status);
562 17 };
563
564 Calliope *calliope_new() {
565 24         Calliope *self=g_object_new(CALLIOPE_TYPE, NULL);
566         GTK_WINDOW(self)->type=GTK_WINDOW_TOPLEVEL;     // Very important!
567 267         gtk_widget_show_all(GTK_WIDGET(self));
568 178         //printf ("%X, %X\n", self, GTK_OBJECT(self));fflush(stdout);
569 17         return self;
570 };
571
572 356 #ifndef LIBRARY_DISABLE
573 142 MusicView *calliope_open_library (Calliope *self, const char *file) {
574         MusicSource *library = library_new(file);
575 158         GtkWidget *view = NULL;
576 142         if (library!=NULL) {
577 402                 view = browser_add_source(BROWSER(SP->browser), MUSIC_SOURCE(library),
578                                           SP->default_view_config_string);
579 267                 g_object_unref(library);
580 142         };
581 158         return MUSIC_VIEW(view);
582 17 };
583 356 #endif
584 17
585 void calliope_play_cd(Calliope *self, const char *device_path) {
586         // FIXME: make sure this isn't leaked
587         if (device_path==NULL) device_path="/dev/cdrom";
588         CdSource *cd=cd_source_new(device_path);
589         if (cd!=NULL)
590 402                 browser_add_source (BROWSER(SP->browser), MUSIC_SOURCE(cd), SP->default_view_config_string);
591 267         g_object_unref(cd);
592 17
593         // FIXME: lookup disk id already and change name
594         //static void cd_source_name_changed(MusicSource *source, char *name, GtkAction *action) {
595 267         //char *label=g_strdup_printf("Play '%s'",
596 17
597 };
598
599 267 MusicView *calliope_open_directory (Calliope *self, const char *path,
600 421                                     const char *view_config, GConfChangeSet *changeset) {
601 140         FileSource *source = file_source_new(path, changeset);
602 158         GtkWidget *view = NULL;
603 267
604 142         if (source!=NULL) {
605 421                 if (view_config == NULL) view_config = SP->default_view_config_string;
606
607                 view = browser_add_source (BROWSER(SP->browser), MUSIC_SOURCE(source), view_config);
608 267                 g_object_unref (source);
609 421
610                 return MUSIC_VIEW(view);
611         };
612
613         return NULL;
614 17 };
615
616 356 #ifndef LIBRARY_DISABLE
617 17 void calliope_import_directory(Calliope *self, const char *path, GConfChangeSet *changeset) {
618         Library *dest;
619         // FIXME: this won't work with no libraries open
620         GtkTreeModel *library_model=GTK_TREE_MODEL(BROWSER(SP->browser)->library_liststore);
621         //if (!gtk_tree_model_iter_has_child(library_model, NULL)) return;
622 267
623 17         GtkTreeIter iter; gtk_tree_model_iter_nth_child(library_model, &iter, NULL, 0);
624         gtk_tree_model_get(library_model, &iter, BROWSER_LIBRARY_LISTSTORE_COLUMN_SOURCE, &dest, -1);
625 267
626         Process *import_process = file_import_process_new(MUSIC_SOURCE(dest), changeset, NULL);
627 191         music_search_process_new (path, audio_extensions_list, NULL, import_process, NULL);
628 17 };
629 356 #endif
630 17
631 void calliope_add_music(Calliope *self, gboolean search) {
632         add_music_dialog_show(SP->add_music_dialog);
633 267
634 17         if (search)
635 267                 add_music_dialog_search_path(SP->add_music_dialog, "/");
636 17 };
637
638 319 void calliope_close_source (Calliope *self, MusicView *view) {
639 350         browser_close_source (BROWSER(SP->browser), music_view_get_source(view));
640 319 };
641
642 402
643 void calliope_set_default_view_config (Calliope *self, const char *config_string) {
644         if (SP->default_view_config_string != NULL) g_free (SP->default_view_config_string);
645         SP->default_view_config_string = g_strdup(config_string);
646 };
647
648 17 /////////// Actions
649 ///
650
651 356 #ifndef LIBRARY_DISABLE
652 17 static void change_library(Calliope *self, gboolean create) {
653         // FIXME: would it be more efficient to create this at the start of the
654         // program and reuse it? Who knows  ??
655         GtkWidget *file_dlg=gtk_file_chooser_dialog_new(
656           create?"Create new database file":"Select database file",
657 267           GTK_WINDOW(self), create?GTK_FILE_CHOOSER_ACTION_SAVE:GTK_FILE_CHOOSER_ACTION_OPEN,
658 17           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, create?GTK_STOCK_SAVE:GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
659         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_dlg), TRUE);
660 267
661 17         GtkWidget *store=gtk_check_button_new_with_label("Set as the default database");
662         gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(file_dlg), store);
663 267
664 17         if (gtk_dialog_run(GTK_DIALOG(file_dlg))==GTK_RESPONSE_OK) {
665                 // FIXME: check if file is writable before blindly accepting, or test
666                 // & disable modification ??
667                 // FIXME: Also give errors (visible ones) if the file does not load
668 267                 char *file_name=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_dlg));
669                 if (create && g_file_test(file_name, G_FILE_TEST_EXISTS))
670                         g_unlink(file_name);
671                 calliope_open_library(self, file_name);
672                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(store))) {
673                         gconf_client_set_bool (gconf, KEY_DEFAULT_LIBRARY_ENABLE, TRUE, NULL);
674                         gconf_client_set_string (gconf, KEY_DEFAULT_LIBRARY_FILE_NAME, file_name, NULL);
675                 };
676 17                 g_free(file_name);
677         };
678
679 267         gtk_widget_destroy(file_dlg);
680 17 };
681
682 static void new_action(GtkAction *action, Calliope *self) {
683 267         change_library(self, 1);                };
684
685 17 static void open_action(GtkAction *action, Calliope *self) {
686 267         change_library(self, 0);                };
687
688 17 static void add_music_action(GtkAction *action, Calliope *self) {
689 267         calliope_add_music(self, FALSE);                };
690 356 #endif
691 267
692 17 static void settings_action(GtkAction *action, Calliope *self) {
693         run_global_settings_dialog();   };
694 267
695 446 static void status_action (GtkAction *action, Calliope *self) {
696         status_dialog_run ();
697 };
698
699 17
700 350 //static void play_cd_action(GtkAction *action, Calliope *self) {
701 //      calliope_play_cd(self, g_object_get_data(G_OBJECT(action), "device-path"));     };
702 17
703 static void open_directory_action(GtkAction *action, Calliope *self) {
704 199         GConfChangeSet *changeset = gconf_change_set_new();
705 267         const char *recent_path = gconf_client_get_string(gconf, KEY_FILE_SOURCE_RECENT_DIRECTORY,
706 199                                                           NULL);
707 17         if (recent_path!=NULL)
708 267                 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER(SP->open_dir_file_chooser), recent_path);
709
710         if (run_dialog_with_panel(WID("play_dir_dialog"), WID("play_dir_notebook"),
711 199                                   get_import_settings_panel(changeset), import_settings_panel_title)
712 267                   == GTK_RESPONSE_OK) {
713 17                 // FIXME: this gives an error as engine has a client - but what to do !!
714                 if (!gconf_engine_commit_change_set(gconf_engine_get_default(), changeset,
715 267                                                     TRUE, NULL))
716 199                         g_warning("Error while committing change set");
717                 char *path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(SP->open_dir_file_chooser));
718 421                 calliope_open_directory (self, path, NULL, changeset);
719 199                 gconf_client_set_string (gconf, KEY_FILE_SOURCE_RECENT_DIRECTORY, path, NULL);
720                 g_free (path);
721 17         };
722 199         gconf_change_set_unref (changeset);
723 17 };
724
725 static void close_action(GtkAction *action, Calliope *self) {
726 267         browser_close_current_source (BROWSER(SP->browser));
727 172 };
728 17
729 // FIXME: debugging only
730 static void dump_action(GtkAction *action, Calliope *self) {
731 24         MusicSource *source;
732         browser_get_selected(BROWSER(SP->browser), &source, NULL);
733 206         _music_source_dump(source, TRUE);
734 17 };
735 267
736 17 static void exit_action(GtkAction *action, Calliope *self) {
737 267         gtk_object_destroy(GTK_OBJECT(self));
738 17 };
739 24

Loggerhead is a web-based interface for Bazaar branches