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