File dialog improvements
- From: Артем Попов <tfwo mail ru>
- To: beast gnome org
- Subject: File dialog improvements
- Date: Wed, 08 Sep 2004 13:07:17 +0700
While messing with beast lately, I've been trying to add "save changes?"
dialog when closing window. This turned out into quite an adventure of
studying the beast source... I guess now I understand the meaning behind
"Bedevilled" in the name :) Well, I finally implemented the
functionality (and updated to filechooser). Patches attached, but
there're still several points:
* The project item_name (retreived with bse_item_get_name) is set to
"Untitled.bse" on startup, but I think the directory path is stored in
it later as well. I did not find a clear way to tell whenever a file has
been saved to disk for the first time (for Save/Not-Save-As operation),
so I added a boolean "first_time_save_flag" in bstapp.h/BstApp and a
boolean "save_self_contained" to preserve self_contained ("fully include
wave files") flag when saving the file later. first_time_save_flag is
set to TRUE in bst_app_init, but to FALSE when opening project. I still
don't know how to check if waves were included with the newly opened
project...
* It seems the only way to clear the project "dirty" flag (which means
"unsaved" as stated in the code) is to clear the undo history with
bse_project_clear_undo (get_property returns value based on undo
history). This means, that undo is impossible after saving the project
(generally ok, but undo after saving is even better, though not a must).
* I used the native gtk file chooser dialog, because BstFileDialog
(GxkDialog) misses gtk_dialog_run (they inherit from GtkWindow, not
GtkDialog), so it seems impossible [I may be wrong here] to escape the
main loop for some time... bst_choice_modal failed to block the loop as
well :( Besides, the new GtkFileChooser looks cooler. The chooser
requires gtk 2.4, but I beleive it's ok. 2.4 has been around in many
distributions already.
Things not implemented (yet):
* Need to update merge dialog, etc.
* Need a way to split project filename and it's path for
messages/titles/etc.
* When quitting with more than one unsaved file, a new dialog should
popup asking which unsaved files to save (like the one in gedit).
Generally polish the quit procedure.
* Need to generate error strings from bse errors.
* Polish strings in the new dialogs and mark them as translatable.
* For the "save unsaved file?"-dialog to satisfy hig requirements 100%,
there must be label with time passed since last save/creation.
* The "warning: file changed" and "replace file?" dialogs need markup
(again for hig).
I also wonder what's the reason behind so much gxk/bst extra stuff? Why
not use pure gtk where available/appropriate?
--Artem
--- bstapp.c.orig 2004-08-24 08:50:24.000000000 +0700
+++ bstapp.c 2004-09-08 12:35:08.000000000 +0700
@@ -31,6 +31,7 @@
#include "bstprojectctrl.h"
#include "bstprofiler.h"
+#include <unistd.h>
/* --- prototypes --- */
static void bst_app_destroy (GtkObject *object);
@@ -45,6 +46,10 @@
static gboolean app_action_check (gpointer data,
gulong action);
+static gint bst_app_close_project (BstApp *app);
+static void bst_app_quit (BstApp *app);
+static void bst_app_open_project (BstApp *app);
+static gboolean bst_app_save_project (BstApp *app, gboolean save_as);
/* --- menus --- */
enum {
@@ -295,6 +300,9 @@
"swapped_signal_after::switch-page", gxk_widget_update_actions, self,
"signal_after::switch-page", gxk_widget_viewable_changed, NULL,
NULL);
+
+ self->first_time_save_flag = TRUE; // if true, do "save as" on "save"
+ self->save_self_contained = FALSE;
}
static void
@@ -548,9 +556,7 @@
g_return_val_if_fail (BST_IS_APP (widget), FALSE);
app = BST_APP (widget);
-
- gtk_widget_destroy (widget);
-
+ bst_app_close_project (app);
return TRUE;
}
@@ -747,14 +753,7 @@
SfiProxy proxy;
GtkWidget *any;
case BST_ACTION_EXIT:
- if (bst_app_class)
- {
- GSList *slist, *free_slist = g_slist_copy (bst_app_class->apps);
-
- for (slist = free_slist; slist; slist = slist->next)
- gxk_toplevel_delete (slist->data);
- g_slist_free (free_slist);
- }
+ bst_app_quit (self); // moved to quit procedure for futher work
break;
case BST_ACTION_CLOSE_PROJECT:
gxk_toplevel_delete (widget);
@@ -773,7 +772,7 @@
}
break;
case BST_ACTION_OPEN_PROJECT:
- bst_file_dialog_popup_open_project (self);
+ bst_app_open_project (self);
break;
case BST_ACTION_MERGE_PROJECT:
bst_file_dialog_popup_merge_project (self, self->project);
@@ -782,8 +781,10 @@
bst_file_dialog_popup_import_midi (self, self->project);
break;
case BST_ACTION_SAVE_PROJECT:
+ bst_app_save_project (self, FALSE);
+ break;
case BST_ACTION_SAVE_PROJECT_AS:
- bst_file_dialog_popup_save_project (self, self->project);
+ bst_app_save_project (self, TRUE);
break;
case BST_ACTION_MERGE_EFFECT:
bst_file_dialog_popup_merge_effect (self, self->project);
@@ -1050,3 +1051,217 @@
return FALSE;
}
}
+
+// file dialog love below
+
+// to reduce typing
+static void show_error_dialog (GtkWindow *parent, gchar *message_format, ...)
+{
+ va_list args;
+ va_start (args, message_format);
+ GtkWidget *error_dialog = gtk_message_dialog_new_with_markup (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ message_format, args);
+ va_end (args);
+
+ gtk_dialog_run (GTK_DIALOG (error_dialog));
+ gtk_widget_destroy (error_dialog);
+}
+
+static void bst_app_open_project (BstApp *app)
+{
+ GtkWidget *file_dialog;
+ BseErrorType error;
+
+ file_dialog = gtk_file_chooser_dialog_new ("Open Project", GTK_WINDOW (app),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ if (gtk_dialog_run (GTK_DIALOG (file_dialog)) == GTK_RESPONSE_ACCEPT) {
+ gchar *filename;
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_dialog));
+
+ SfiProxy project = bse_server_use_new_project (BSE_SERVER, filename);
+ error = bse_project_restore_from_file (project, filename);
+
+ if (error)
+ show_error_dialog (GTK_WINDOW (file_dialog), "Error opening project '%s'", filename);
+ else {
+ BstApp *app;
+ bse_project_get_wave_repo (project);
+ app = bst_app_new (project);
+ app->first_time_save_flag = FALSE;
+ // FIXME: need to check if waves are included with the project
+ // and set save_self_contained flag appropriately
+ gxk_status_window_push (app);
+ bst_status_eprintf (error, _("Opening project '%s'"), filename);
+ gxk_status_window_pop ();
+ gxk_idle_show_widget (GTK_WIDGET (app));
+ }
+
+ bse_item_unuse (project);
+ g_free (filename);
+ }
+ gtk_widget_destroy (file_dialog);
+}
+
+static gboolean bst_app_save_project (BstApp *app, gboolean save_as)
+{
+ gchar *filename;
+ gboolean self_contained = app->save_self_contained;
+
+ GtkWidget *file_dialog;
+ GtkWidget *replace_dialog;
+ GtkWidget *toggle;
+ BseErrorType error;
+
+ if (save_as || app->first_time_save_flag) {
+ file_dialog = gtk_file_chooser_dialog_new ("Save Project", GTK_WINDOW (app),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ // FIXME: sometimes (when loading demo in particular)
+ // set_current_name sets full path
+ /*if (app->first_time_save_flag)
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_dialog), bse_item_get_name (app->project));
+ else
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (file_dialog), bse_item_get_name (app->project));*/
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (file_dialog), bse_item_get_name (app->project));
+
+ // FIXME: add tooltip about this
+ toggle = gtk_check_button_new_with_label ("Include wave files with the project");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), app->save_self_contained);
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (file_dialog), toggle);
+
+ // use loop to handle file exists situation, etc.
+ do {
+ if (gtk_dialog_run (GTK_DIALOG (file_dialog)) == GTK_RESPONSE_ACCEPT) {
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_dialog));
+ } else {
+ gtk_widget_destroy (file_dialog);
+ return FALSE;
+ }
+
+ // overwrite directory fix
+ // if the file exists and is a directory, issue a error
+ // FIXME: there're also tests like IS_REGULAR and IS_SYMLINK.
+ // Should they also be included?
+ if (g_file_test (filename, G_FILE_TEST_EXISTS) &&
+ g_file_test (filename, G_FILE_TEST_IS_DIR)) {
+ show_error_dialog (GTK_WINDOW (file_dialog), "File %s is a directory!", filename);
+ continue; // return to file selection loop
+ }
+
+ // if the file exists, either overwrite it, choose another file or cancel
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ replace_dialog = gtk_message_dialog_new (GTK_WINDOW (file_dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "File Exists. Overwrite?");
+ gtk_dialog_add_buttons (GTK_DIALOG (replace_dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ "Replace", GTK_RESPONSE_ACCEPT,
+ NULL);
+ if (gtk_dialog_run (GTK_DIALOG (replace_dialog)) == GTK_RESPONSE_ACCEPT)
+ gtk_widget_hide (file_dialog);
+ gtk_widget_destroy (replace_dialog);
+ } else {
+ gtk_widget_hide (file_dialog);
+ }
+ } while (GTK_WIDGET_VISIBLE (file_dialog));
+
+ self_contained = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle));
+ gtk_widget_destroy (file_dialog);
+ } else {
+ filename = bse_item_get_name (app->project);
+ }
+
+ // now we can unlink the file, because this is what the user wants
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ if (unlink (filename) < 0) {
+ show_error_dialog (GTK_WINDOW (app), "Failed to delete %s!", filename);
+ return FALSE;
+ }
+ }
+
+ error = bse_project_store_bse (app->project, 0, filename, self_contained);
+
+ if (error != BSE_ERROR_NONE) {
+ show_error_dialog (GTK_WINDOW (app), "A bse error occured when saving %s!", filename);
+ return FALSE;
+ }
+
+ bse_item_set_name (app->project, filename);
+ if (app->first_time_save_flag)
+ app->first_time_save_flag = FALSE;
+ app->save_self_contained = self_contained;
+
+ //g_free (filename); // commented out because otherwise causes segfault
+ bse_project_clear_undo (app->project);
+ return TRUE;
+}
+
+// this is convenience method for CloseProject/DeleteWindow actions
+static gint bst_app_close_project (BstApp *app)
+{
+ gboolean dirty;
+ gint response;
+ GtkWidget *dialog;
+
+ bse_proxy_get (app->project, "dirty", &dirty, NULL);
+
+ if (dirty) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (&app->window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ "Save changes to project before closing?");
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ GTK_STOCK_NO, GTK_RESPONSE_NO,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_YES,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ switch (response) {
+ case GTK_RESPONSE_NO:
+ gtk_widget_destroy (GTK_WIDGET (app));
+ return 0;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ return -1;
+ break;
+ case GTK_RESPONSE_YES:
+ if (!bst_app_save_project (app, FALSE))
+ return -1;
+ break;
+ }
+ }
+ gtk_widget_destroy (GTK_WIDGET (app));
+ return 1;
+}
+
+static void bst_app_quit (BstApp *app)
+{
+ if (bst_app_class) {
+ GSList *slist, *free_slist = g_slist_copy (bst_app_class->apps);
+
+ for (slist = free_slist; slist; slist = slist->next) {
+ bst_app_close_project (BST_APP (slist->data));
+ //gxk_toplevel_delete (slist->data);
+ }
+
+ g_slist_free (free_slist);
+ }
+}
--- bstapp.h.orig 2004-08-23 14:46:42.000000000 +0700
+++ bstapp.h 2004-09-08 12:40:07.932412992 +0700
@@ -53,6 +53,9 @@
GtkWidget *rack_dialog;
GtkWidget *rack_editor;
GtkWidget *pcontrols;
+
+ gboolean first_time_save_flag;
+ gboolean save_self_contained;
};
struct _BstAppClass
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]