mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
645 lines
17 KiB
645 lines
17 KiB
/********************************************************************\
|
|
* FileDialog.c -- file-handling utility dialogs for gnucash. *
|
|
* *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1998, 1999, 2000 Linas Vepstas *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, write to the Free Software *
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
|
|
\********************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <guile/gh.h>
|
|
#include <string.h>
|
|
|
|
#include "Backend.h"
|
|
#include "FileBox.h"
|
|
#include "FileDialog.h"
|
|
#include "Group.h"
|
|
#include "file-history.h"
|
|
#include "gnc-component-manager.h"
|
|
#include "gnc-engine-util.h"
|
|
#include "gnc-event.h"
|
|
#include "gnc-ui.h"
|
|
#include "messages.h"
|
|
|
|
/* FIXME: this is wrong. This file should not need this include. */
|
|
#include "gnc-book-p.h"
|
|
|
|
/** GLOBALS *********************************************************/
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static short module = MOD_GUI;
|
|
|
|
static GNCBook *current_book = NULL;
|
|
|
|
|
|
/* ======================================================== */
|
|
|
|
static gboolean
|
|
show_book_error (GNCBackendError io_error, const char *newfile)
|
|
{
|
|
gboolean uh_oh = TRUE;
|
|
const char *fmt;
|
|
char *buf = NULL;
|
|
|
|
if (NULL == newfile) { newfile = _("(null)"); }
|
|
|
|
switch (io_error)
|
|
{
|
|
case ERR_BACKEND_NO_ERR:
|
|
uh_oh = FALSE;
|
|
break;
|
|
|
|
case ERR_BACKEND_NO_BACKEND:
|
|
fmt = _("The URL \n %s\n"
|
|
"is not supported by this version of GnuCash.");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_BACKEND_LOCKED:
|
|
fmt = _("The URL \n %s\n"
|
|
"is in use by another user.");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_BACKEND_MISC:
|
|
fmt = _("An error occurred while processing\n %s\n");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_BAD_READ:
|
|
fmt = _("There was an error reading the file.\n"
|
|
"Do you want to continue?");
|
|
if (gnc_verify_dialog (fmt, TRUE)) { uh_oh = FALSE; }
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_EMPTY:
|
|
fmt = _("The file \n %s\n is empty.");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_NOT_FOUND:
|
|
fmt = _("The file \n %s\n could not be found.");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_TOO_NEW:
|
|
fmt = _("This file appears to be from a newer version\n"
|
|
"of GnuCash. You must upgrade GnuCash to read\n"
|
|
"this file.");
|
|
gnc_error_dialog (fmt);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_TOO_OLD:
|
|
fmt = _("This file is from an older version of GnuCash.\n"
|
|
"Do you want to continue?");
|
|
if (gnc_verify_dialog (fmt, TRUE)) { uh_oh = FALSE; }
|
|
break;
|
|
|
|
case ERR_SQL_BAD_LOCATION:
|
|
fmt = _("Can't parse the database URL\n %s\n");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
case ERR_SQL_CANT_CONNECT:
|
|
fmt = _("Can't connect to the database\n %s\n"
|
|
"The host, username or password were incorrect.");
|
|
buf = g_strdup_printf (fmt, newfile);
|
|
gnc_error_dialog (buf);
|
|
break;
|
|
|
|
default:
|
|
PERR("FIXME: Unhandled error %d", io_error);
|
|
fmt = _("An unknown I/O error occurred.");
|
|
gnc_error_dialog (fmt);
|
|
break;
|
|
}
|
|
|
|
if (buf) g_free(buf);
|
|
return uh_oh;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static gboolean
|
|
gncLockFailHandler (const char *file)
|
|
{
|
|
const char *format = _("Gnucash could not obtain the lock for\n"
|
|
" %s.\n"
|
|
"That database may be in use by another user,\n"
|
|
"in which case you should not open the database.\n"
|
|
"\nDo you want to proceed with opening the database?");
|
|
char *message;
|
|
gboolean result;
|
|
|
|
if (file == NULL)
|
|
return FALSE;
|
|
|
|
message = g_strdup_printf (format, file);
|
|
result = gnc_verify_dialog (message, FALSE);
|
|
g_free (message);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static gboolean
|
|
gncCreateFailHandler (const char *file)
|
|
{
|
|
const char *format = _("The database\n"
|
|
" %s\n"
|
|
"doesn't seem to exist. Do you want to create it?\n");
|
|
char *message;
|
|
gboolean result;
|
|
|
|
if (file == NULL)
|
|
return FALSE;
|
|
|
|
message = g_strdup_printf (format, file);
|
|
result = gnc_verify_dialog (message, FALSE);
|
|
g_free (message);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static void
|
|
gncAddHistory (GNCBook *book)
|
|
{
|
|
char *url;
|
|
|
|
if (!book) return;
|
|
|
|
url = xaccResolveURL (gnc_book_get_url (book));
|
|
|
|
if (strncmp (url, "file:", 5) == 0)
|
|
gnc_history_add_file (url + 5);
|
|
else
|
|
gnc_history_add_file (url);
|
|
|
|
g_free (url);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileNew (void)
|
|
{
|
|
GNCBook *book;
|
|
|
|
/* If user attempts to start a new session before saving results of
|
|
* the last one, prompt them to clean up their act. */
|
|
if (!gncFileQuerySave ())
|
|
return;
|
|
|
|
book = gncGetCurrentBook ();
|
|
|
|
/* close any ongoing file sessions, and free the accounts.
|
|
* disable events so we don't get spammed by redraws. */
|
|
gnc_engine_suspend_events ();
|
|
|
|
gnc_book_destroy (book);
|
|
current_book = NULL;
|
|
|
|
/* start a new book */
|
|
gncGetCurrentBook ();
|
|
|
|
gnc_engine_resume_events ();
|
|
gnc_gui_refresh_all ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
gboolean
|
|
gncFileQuerySave (void)
|
|
{
|
|
GNCBook *book = gncGetCurrentBook();
|
|
gncUIWidget app = gnc_get_ui_data();
|
|
|
|
/* If user wants to mess around before finishing business with
|
|
* the old file, give em a chance to figure out what's up.
|
|
* Pose the question as a "while" loop, so that if user screws
|
|
* up the file-selection dialog, we don't blow em out of the water;
|
|
* instead, give them another chance to say "no" to the verify box.
|
|
*/
|
|
while (gnc_book_not_saved(book)) {
|
|
GNCVerifyResult result;
|
|
const char *message = _("Changes have been made since the last "
|
|
"Save. Save the data to file?");
|
|
|
|
result = gnc_verify_cancel_dialog_parented (app, message, GNC_VERIFY_YES);
|
|
|
|
if (result == GNC_VERIFY_CANCEL)
|
|
return FALSE;
|
|
|
|
if (result == GNC_VERIFY_NO)
|
|
return TRUE;
|
|
|
|
gncFileSave ();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* private utilities for file open; done in two stages */
|
|
|
|
static void
|
|
gncPostFileOpen (const char * filename)
|
|
{
|
|
GNCBook *new_book;
|
|
gboolean uh_oh = FALSE;
|
|
AccountGroup *new_group;
|
|
char * newfile;
|
|
GNCBackendError io_err = ERR_BACKEND_NO_ERR;
|
|
|
|
if (!filename) return;
|
|
|
|
newfile = xaccResolveURL (filename);
|
|
if (!newfile)
|
|
{
|
|
show_book_error (ERR_FILEIO_FILE_NOT_FOUND, filename);
|
|
return;
|
|
}
|
|
|
|
/* disable events while moving over to the new set of accounts;
|
|
* the mass deletetion of accounts and transactions during
|
|
* switchover would otherwise cause excessive redraws. */
|
|
gnc_engine_suspend_events ();
|
|
|
|
/* Change the mouse to a busy cursor */
|
|
gnc_set_busy_cursor (NULL);
|
|
|
|
/* -------------- BEGIN CORE SESSION CODE ------------- */
|
|
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
|
|
gnc_book_destroy (current_book);
|
|
current_book = NULL;
|
|
|
|
/* load the accounts from the users datafile */
|
|
/* but first, check to make sure we've got a book going. */
|
|
new_book = gnc_book_new ();
|
|
|
|
new_group = NULL;
|
|
|
|
gnc_book_begin (new_book, newfile, FALSE, FALSE);
|
|
io_err = gnc_book_get_error (new_book);
|
|
|
|
/* if file appears to be locked, ask the user ... */
|
|
if (ERR_BACKEND_LOCKED == io_err)
|
|
{
|
|
if (gncLockFailHandler (newfile))
|
|
{
|
|
/* user told us to ignore locks. So ignore them. */
|
|
gnc_book_begin (new_book, newfile, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* if the database doesn't exist, ask the user ... */
|
|
else if (ERR_BACKEND_NO_SUCH_DB == io_err)
|
|
{
|
|
if (gncCreateFailHandler (newfile))
|
|
{
|
|
/* user told us to create a new database. Do it. */
|
|
gnc_book_begin (new_book, newfile, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
/* Check for errors again, since above may have cleared the lock */
|
|
/* If its still locked, don't bother with the message */
|
|
io_err = gnc_book_get_error (new_book);
|
|
if (ERR_BACKEND_LOCKED != io_err)
|
|
{
|
|
uh_oh = show_book_error (io_err, newfile);
|
|
}
|
|
|
|
if (!uh_oh)
|
|
{
|
|
gnc_book_load (new_book);
|
|
|
|
/* check for i/o error, put up appropriate error dialog */
|
|
io_err = gnc_book_get_error (new_book);
|
|
uh_oh = show_book_error (io_err, newfile);
|
|
|
|
new_group = gnc_book_get_group (new_book);
|
|
if (uh_oh) new_group = NULL;
|
|
|
|
/* Umm, came up empty-handed, but no error:
|
|
* The backend forgot to set an error. So make one up. */
|
|
if (!uh_oh && !new_group)
|
|
{
|
|
uh_oh = show_book_error (ERR_BACKEND_MISC, newfile);
|
|
}
|
|
}
|
|
|
|
gnc_unset_busy_cursor (NULL);
|
|
|
|
/* going down -- abandon ship */
|
|
if (uh_oh)
|
|
{
|
|
gnc_book_destroy (new_book);
|
|
|
|
/* well, no matter what, I think it's a good idea to have a
|
|
* topgroup around. For example, early in the gnucash startup
|
|
* sequence, the user opens a file; if this open fails for any
|
|
* reason, we don't want to leave them high & dry without a
|
|
* topgroup, because if the user continues, then bad things will
|
|
* happen. */
|
|
gncGetCurrentBook ();
|
|
|
|
g_free (newfile);
|
|
|
|
gnc_engine_resume_events ();
|
|
gnc_gui_refresh_all ();
|
|
|
|
return;
|
|
}
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
current_book = new_book;
|
|
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* clean up old stuff, and then we're outta here. */
|
|
gncAddHistory (new_book);
|
|
|
|
/* run a file-opened hook. For now, the main thing it will do
|
|
* is notice if legacy currencies are being imported. */
|
|
if (newfile)
|
|
{
|
|
gh_call2(gh_eval_str("gnc:hook-run-danglers"),
|
|
gh_eval_str("gnc:*file-opened-hook*"),
|
|
gh_str02scm(newfile));
|
|
}
|
|
g_free (newfile);
|
|
|
|
gnc_engine_resume_events ();
|
|
gnc_gui_refresh_all ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileOpen (void)
|
|
{
|
|
const char * newfile;
|
|
|
|
if (!gncFileQuerySave ())
|
|
return;
|
|
|
|
newfile = fileBox(_("Open"), NULL, gnc_history_get_last());
|
|
gncPostFileOpen (newfile);
|
|
|
|
/* This dialogue can show up early in the startup process. If the
|
|
* user fails to pick a file (by e.g. hitting the cancel button), we
|
|
* might be left with a null topgroup, which leads to nastiness when
|
|
* user goes to create their very first account. So create one. */
|
|
gncGetCurrentBook ();
|
|
}
|
|
|
|
void
|
|
gncFileOpenFile (const char * newfile)
|
|
{
|
|
if (!newfile) return;
|
|
|
|
if (!gncFileQuerySave ())
|
|
return;
|
|
|
|
gncPostFileOpen (newfile);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
static gboolean been_here_before = FALSE;
|
|
|
|
void
|
|
gncFileSave (void)
|
|
{
|
|
GNCBackendError io_err;
|
|
const char * newfile;
|
|
GNCBook *book;
|
|
ENTER (" ");
|
|
|
|
/* hack alert -- Somehow make sure all in-progress edits get committed! */
|
|
|
|
/* If we don't have a filename/path to save to get one. */
|
|
book = gncGetCurrentBook ();
|
|
|
|
if (!gnc_book_get_file_path (book))
|
|
{
|
|
gncFileSaveAs();
|
|
return;
|
|
}
|
|
|
|
/* use the current session to save to file */
|
|
gnc_set_busy_cursor(NULL);
|
|
gnc_book_save (book);
|
|
gnc_unset_busy_cursor(NULL);
|
|
|
|
/* Make sure everything's OK - disk could be full, file could have
|
|
become read-only etc. */
|
|
newfile = gnc_book_get_file_path (book);
|
|
io_err = gnc_book_get_error (book);
|
|
if (ERR_BACKEND_NO_ERR != io_err)
|
|
{
|
|
show_book_error (io_err, newfile);
|
|
|
|
if (been_here_before) return;
|
|
been_here_before = TRUE;
|
|
gncFileSaveAs(); /* been_here prevents infinite recursion */
|
|
been_here_before = FALSE;
|
|
return;
|
|
}
|
|
|
|
gncAddHistory (book);
|
|
|
|
gnc_book_mark_saved(book);
|
|
LEAVE (" ");
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileSaveAs (void)
|
|
{
|
|
AccountGroup *group;
|
|
GNCPriceDB *pdb;
|
|
GNCBook *new_book;
|
|
GNCBook *book;
|
|
const char *filename;
|
|
char *newfile;
|
|
const char *oldfile;
|
|
GNCBackendError io_err = ERR_BACKEND_NO_ERR;
|
|
|
|
ENTER(" ");
|
|
filename = fileBox(_("Save"), "*.gnc", gnc_history_get_last());
|
|
if (!filename) return;
|
|
|
|
/* Check to see if the user specified the same file as the current
|
|
* file. If so, then just do that, instead of the below, which
|
|
* assumes a truly new name was given. */
|
|
newfile = xaccResolveURL (filename);
|
|
if (!newfile)
|
|
{
|
|
show_book_error (ERR_FILEIO_FILE_NOT_FOUND, filename);
|
|
return;
|
|
}
|
|
|
|
book = gncGetCurrentBook ();
|
|
oldfile = gnc_book_get_file_path (book);
|
|
if (oldfile && (strcmp(oldfile, newfile) == 0))
|
|
{
|
|
g_free (newfile);
|
|
gncFileSave ();
|
|
return;
|
|
}
|
|
|
|
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
|
|
group = gnc_book_get_group(book);
|
|
pdb = gnc_book_get_pricedb(book);
|
|
|
|
new_book = gnc_book_new ();
|
|
gnc_book_begin (new_book, newfile, FALSE, FALSE);
|
|
|
|
io_err = gnc_book_get_error (new_book);
|
|
|
|
/* if file appears to be locked, ask the user ... */
|
|
if (ERR_BACKEND_LOCKED == io_err)
|
|
{
|
|
if (gncLockFailHandler (newfile))
|
|
{
|
|
/* user told us to ignore locks. So ignore them. */
|
|
gnc_book_begin (new_book, newfile, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* if the database doesn't exist, ask the user ... */
|
|
else if (ERR_BACKEND_NO_SUCH_DB == io_err)
|
|
{
|
|
if (gncCreateFailHandler (newfile))
|
|
{
|
|
/* user told us to create a new database. Do it. */
|
|
gnc_book_begin (new_book, newfile, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
/* check again for session errors (since above dialog may have
|
|
* cleared a file lock & moved things forward some more)
|
|
* This time, errors will be fatal.
|
|
*/
|
|
io_err = gnc_book_get_error (new_book);
|
|
if (ERR_BACKEND_NO_ERR != io_err)
|
|
{
|
|
show_book_error (io_err, newfile);
|
|
gnc_book_destroy (new_book);
|
|
g_free (newfile);
|
|
return;
|
|
}
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
gnc_book_set_group(book, NULL);
|
|
gnc_book_set_pricedb(book, NULL);
|
|
gnc_book_destroy (book);
|
|
current_book = new_book;
|
|
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* oops ... file already exists ... ask user what to do... */
|
|
if (gnc_book_save_may_clobber_data (new_book))
|
|
{
|
|
const char *format = _("The file \n %s\n already exists.\n"
|
|
"Are you sure you want to overwrite it?");
|
|
char *tmpmsg;
|
|
gboolean result;
|
|
|
|
tmpmsg = g_strdup_printf (format, newfile);
|
|
result = gnc_verify_dialog (tmpmsg, FALSE);
|
|
g_free (tmpmsg);
|
|
|
|
/* if user says cancel, we should break out */
|
|
if (!result)
|
|
{
|
|
g_free (newfile);
|
|
return;
|
|
}
|
|
|
|
/* Whoa-ok. Blow away the previous file. */
|
|
}
|
|
|
|
/* OK, save the data to the file ... */
|
|
gnc_book_set_group(new_book, group);
|
|
gnc_book_set_pricedb(new_book, pdb);
|
|
gncFileSave ();
|
|
|
|
g_free (newfile);
|
|
LEAVE (" ");
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileQuit (void)
|
|
{
|
|
GNCBook *book;
|
|
|
|
book = gncGetCurrentBook ();
|
|
|
|
/* disable events; otherwise the mass deletetion of accounts and
|
|
* transactions during shutdown would cause massive redraws */
|
|
gnc_engine_suspend_events ();
|
|
|
|
gnc_book_destroy (book);
|
|
current_book = NULL;
|
|
|
|
gnc_engine_resume_events ();
|
|
gnc_gui_refresh_all ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
AccountGroup *
|
|
gncGetCurrentGroup (void)
|
|
{
|
|
AccountGroup *grp;
|
|
GNCBook *book;
|
|
|
|
book = gncGetCurrentBook ();
|
|
|
|
grp = gnc_book_get_group (book);
|
|
return grp;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
GNCBook *
|
|
gncGetCurrentBook (void)
|
|
{
|
|
if (!current_book)
|
|
current_book = gnc_book_new ();
|
|
|
|
return current_book;
|
|
}
|
|
|
|
/********************* END OF FILE **********************************/
|