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.
510 lines
15 KiB
510 lines
15 KiB
/********************************************************************\
|
|
* FileDialog.c -- file-handling utility dialogs for gnucash. *
|
|
* *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1998, 1999 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. *
|
|
\********************************************************************/
|
|
|
|
/*
|
|
* hack alert -- the contents of this file are 99.997% motif independent.
|
|
* this file should be moved to a neutral directory, and recycled for use
|
|
* with the gtk/gnome code.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "FileBox.h"
|
|
#include "FileDialog.h"
|
|
#include "FileIO.h"
|
|
#include "Group.h"
|
|
#include "messages.h"
|
|
#include "Session.h"
|
|
#include "TransLog.h"
|
|
#include "Destroy.h"
|
|
#include "util.h"
|
|
#include "ui-callbacks.h"
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
// static short module = MOD_GUI;
|
|
|
|
/** GLOBALS *********************************************************/
|
|
Session *current_session = NULL;
|
|
AccountGroup *topgroup = NULL; /* the current top of the heriarchy */
|
|
|
|
/********************************************************************\
|
|
* fileMenubarCB -- handles file menubar choices *
|
|
\********************************************************************/
|
|
|
|
#define SHOW_IO_ERR_MSG(io_error) { \
|
|
switch (io_error) { \
|
|
case ERR_FILEIO_NO_ERROR: \
|
|
break; \
|
|
case ERR_FILEIO_FILE_NOT_FOUND: \
|
|
sprintf (buf, FILE_NOT_FOUND_MSG, newfile); \
|
|
errorBox (buf); \
|
|
uh_oh = 1; \
|
|
break; \
|
|
case ERR_FILEIO_FILE_EMPTY: \
|
|
sprintf (buf, FILE_EMPTY_MSG, newfile); \
|
|
errorBox (buf); \
|
|
uh_oh = 1; \
|
|
break; \
|
|
case ERR_FILEIO_FILE_TOO_NEW: \
|
|
errorBox ( FILE_TOO_NEW_MSG); \
|
|
uh_oh = 1; \
|
|
break; \
|
|
case ERR_FILEIO_FILE_TOO_OLD: \
|
|
if (!verifyBox( FILE_TOO_OLD_MSG )) { \
|
|
xaccFreeAccountGroup (newgrp); \
|
|
newgrp = NULL; \
|
|
uh_oh = 1; \
|
|
} \
|
|
break; \
|
|
case ERR_FILEIO_FILE_BAD_READ: \
|
|
if (!verifyBox( FILE_BAD_READ_MSG )) { \
|
|
xaccFreeAccountGroup (newgrp); \
|
|
newgrp = NULL; \
|
|
uh_oh = 1; \
|
|
} \
|
|
break; \
|
|
default: \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
|
|
#define SHOW_LOCK_ERR_MSG(session) \
|
|
{ \
|
|
int norr = xaccSessionGetError (session); \
|
|
if (ETXTBSY == norr) \
|
|
{ \
|
|
sprintf (buf, FMB_LOCKED_MSG, newfile); \
|
|
errorBox (buf); \
|
|
uh_oh = 1; \
|
|
} \
|
|
else \
|
|
if (norr) \
|
|
{ \
|
|
sprintf (buf, FMB_INVALID_MSG, newfile); \
|
|
errorBox (buf); \
|
|
uh_oh = 1; \
|
|
} \
|
|
} \
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileNew (void)
|
|
{
|
|
Session *sess;
|
|
AccountGroup *grp;
|
|
|
|
/* If user attempts to start a new session before saving
|
|
* results of the last one, prompt them to clean up thier
|
|
* act. */
|
|
gncFileQuerySave ();
|
|
|
|
sess = current_session;
|
|
grp = xaccSessionGetGroup (sess);
|
|
/* if session not yet started ... */
|
|
if (!grp) grp = topgroup;
|
|
|
|
/* when starting new everything, destroy old stuff first */
|
|
/* destroy open windows first, before destroying the group itself */
|
|
xaccGroupWindowDestroy (grp);
|
|
|
|
/* close any ongoing file sessions, if any */
|
|
xaccSessionEnd (sess);
|
|
xaccSessionDestroy (sess);
|
|
current_session = NULL;
|
|
topgroup = NULL;
|
|
|
|
/* disable logging while we move over to the new set of accounts to
|
|
* edit; the mass deletetion of accounts and transactions during
|
|
* switchover is not something we want to keep in a journal. */
|
|
xaccLogDisable();
|
|
xaccFreeAccountGroup (grp);
|
|
xaccLogEnable();
|
|
grp = xaccMallocAccountGroup();
|
|
topgroup = grp;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileQuerySave (void)
|
|
{
|
|
Session *sess;
|
|
AccountGroup *grp;
|
|
|
|
sess = current_session;
|
|
grp = xaccSessionGetGroup (sess);
|
|
/* if session not yet started ... */
|
|
if (!grp) grp = topgroup;
|
|
|
|
/* 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 ( xaccAccountGroupNotSaved (grp) )
|
|
{
|
|
if( verifyBox( FMB_SAVE_MSG) )
|
|
{
|
|
gncFileSave ();
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* private utilities for file open; done in two stages */
|
|
|
|
static void
|
|
gncPostFileOpen (const char * filename)
|
|
{
|
|
Session *newsess;
|
|
AccountGroup *oldgrp;
|
|
int io_error, uh_oh=0;
|
|
char buf[BUFSIZE];
|
|
AccountGroup *newgrp;
|
|
char * newfile;
|
|
|
|
if (!filename) return;
|
|
newfile = xaccResolveFilePath (filename);
|
|
if (!newfile) return;
|
|
|
|
/* -------------- BEGIN CORE SESSION CODE ------------- */
|
|
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
|
|
oldgrp = xaccSessionGetGroup (current_session);
|
|
/* if session not yet started ... */
|
|
if (!oldgrp) oldgrp = topgroup;
|
|
|
|
/* load the accounts from the users datafile */
|
|
/* but first, check to make sure we've got a session going ... */
|
|
newsess = xaccMallocSession ();
|
|
|
|
/* disable logging while we move over to the new set of accounts to
|
|
* edit; the mass deletetion of accounts and transactions during
|
|
* switchover is not something we want to keep in a journal. */
|
|
xaccLogDisable();
|
|
newgrp = xaccSessionBeginFile (newsess, newfile);
|
|
xaccLogEnable();
|
|
|
|
/* check for session errors, put up appropriate dialog */
|
|
SHOW_LOCK_ERR_MSG (newsess);
|
|
|
|
/* check for i/o error, put up appropriate error message */
|
|
io_error = xaccGetFileIOError();
|
|
SHOW_IO_ERR_MSG(io_error);
|
|
|
|
/* Umm, came up empty-handed, i.e. the file was not found. */
|
|
/* This is almost certainly not what the user wanted. */
|
|
if (!newgrp && !io_error)
|
|
{
|
|
sprintf (buf, FILE_NOT_FOUND_MSG, newfile);
|
|
errorBox ( buf);
|
|
uh_oh = 1;
|
|
}
|
|
|
|
/* going down -- abandon ship */
|
|
if (uh_oh)
|
|
{
|
|
xaccSessionEnd (newsess);
|
|
xaccSessionDestroy (newsess);
|
|
|
|
/* well, no matter what, I think its 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 user continues, then bad things will happen ...
|
|
*/
|
|
if (NULL == topgroup)
|
|
{
|
|
topgroup = xaccMallocAccountGroup();
|
|
}
|
|
free (newfile);
|
|
return;
|
|
}
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
xaccSessionEnd (current_session);
|
|
xaccSessionDestroy (current_session);
|
|
current_session = newsess;
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* clean up old stuff, and then we're outta here. */
|
|
xaccLogDisable();
|
|
xaccLogSetBaseName (newfile);
|
|
/* destroy open windows first, before destroying the group itself */
|
|
xaccGroupWindowDestroy (oldgrp);
|
|
xaccFreeAccountGroup (oldgrp);
|
|
topgroup = newgrp;
|
|
xaccLogEnable();
|
|
|
|
free (newfile);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileOpen (void)
|
|
{
|
|
char * newfile;
|
|
|
|
gncFileQuerySave ();
|
|
newfile = fileBox( OPEN_STR, "*.xac");
|
|
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 thier very first account.
|
|
* Don't leave thier ass in a sling, give them what they need.
|
|
*/
|
|
if (NULL == topgroup)
|
|
{
|
|
topgroup = xaccMallocAccountGroup();
|
|
}
|
|
}
|
|
|
|
void
|
|
gncFileOpenFile (const char * newfile)
|
|
{
|
|
if (!newfile) return;
|
|
gncFileQuerySave ();
|
|
gncPostFileOpen (newfile);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileQIFImport (void)
|
|
{
|
|
char * newfile;
|
|
char buf[BUFSIZE];
|
|
int io_error, uh_oh = 0;
|
|
AccountGroup *newgrp;
|
|
|
|
newfile = fileBox( OPEN_STR, "*.qif");
|
|
if (!newfile) return;
|
|
|
|
/* load the accounts from the file the user specified */
|
|
newgrp = xaccReadQIFAccountGroup (newfile);
|
|
|
|
/* check for i/o error, put up appropriate error message */
|
|
io_error = xaccGetQIFIOError();
|
|
SHOW_IO_ERR_MSG(io_error);
|
|
|
|
if (uh_oh) return;
|
|
|
|
if( NULL == topgroup ) {
|
|
/* no topgroup exists */
|
|
topgroup = xaccMallocAccountGroup();
|
|
}
|
|
|
|
/* since quicken will not export all accounts
|
|
* into one file, we must merge them in one by one */
|
|
xaccConcatGroups (topgroup, newgrp);
|
|
xaccMergeAccounts (topgroup);
|
|
xaccConsolidateGrpTransactions (topgroup);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
static int been_here_before = 0;
|
|
|
|
void
|
|
gncFileSave (void)
|
|
{
|
|
AccountGroup *newgrp = NULL;
|
|
char * newfile;
|
|
char buf[BUFSIZE];
|
|
int io_error, norr, uh_oh = 0;
|
|
/* hack alert -- Somehow make sure all in-progress edits get committed! */
|
|
|
|
/* if no session exists, then we don't have a filename/path
|
|
* to save to. Get one now. */
|
|
if ((NULL == current_session) ||
|
|
(NULL == xaccSessionGetGroup (current_session)))
|
|
{
|
|
gncFileSaveAs();
|
|
return;
|
|
}
|
|
|
|
/* use the current session to save to file */
|
|
xaccSessionSave (current_session);
|
|
|
|
/* in theory, no error should have occured, but just in case,
|
|
* we're gonna check and handle ... */
|
|
norr = xaccSessionGetError (current_session);
|
|
if (norr)
|
|
{
|
|
if (been_here_before) return;
|
|
been_here_before = 1;
|
|
gncFileSaveAs(); /* been_here prevents infinite recuirsion */
|
|
been_here_before = 0;
|
|
return;
|
|
}
|
|
|
|
/* check for i/o error, put up appropriate error message */
|
|
io_error = xaccGetFileIOError();
|
|
newfile = xaccSessionGetFilePath(current_session);
|
|
SHOW_IO_ERR_MSG(io_error);
|
|
|
|
/* going down -- abandon ship */
|
|
if (uh_oh) return;
|
|
|
|
xaccAccountGroupMarkSaved (topgroup);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileSaveAs (void)
|
|
{
|
|
Session *newsess;
|
|
AccountGroup *oldgrp;
|
|
char * newfile;
|
|
AccountGroup *newgrp;
|
|
char * oldfile;
|
|
char buf[BUFSIZE];
|
|
int io_error, uh_oh = 0;
|
|
|
|
newfile = fileBox( SAVE_STR, "*.xac");
|
|
if (!newfile) return;
|
|
|
|
/* check to see if the user did something silly,
|
|
* like specifying the same file as the current file ...
|
|
* if so, then just do that, instead of the below,
|
|
* which assumes a tuly new name was given.
|
|
*/
|
|
newfile = xaccResolveFilePath (newfile);
|
|
assert (newfile); /* deep doodoo if resolve failed */
|
|
oldfile = xaccSessionGetFilePath (current_session);
|
|
if (oldfile && !strcmp (oldfile, newfile))
|
|
{
|
|
free (newfile);
|
|
gncFileSave ();
|
|
return;
|
|
}
|
|
|
|
/* -------------- BEGIN CORE SESSION CODE ------------- */
|
|
/* -- this code identical in FileOpen and FileSaveAs -- */
|
|
oldgrp = xaccSessionGetGroup (current_session);
|
|
/* if session not yet started ... */
|
|
if (!oldgrp) oldgrp = topgroup;
|
|
|
|
/* create a new session ... */
|
|
newsess = xaccMallocSession ();
|
|
|
|
/* disable logging while we move over to the new set of accounts to
|
|
* edit; the mass deletetion of accounts and transactions during
|
|
* switchover is not something we want to keep in a journal. */
|
|
xaccLogDisable();
|
|
newgrp = xaccSessionBeginFile (newsess, newfile);
|
|
xaccLogEnable();
|
|
|
|
/* check for session errors (e.g. file locked by another user) */
|
|
SHOW_LOCK_ERR_MSG (newsess);
|
|
|
|
/* check for i/o error, put up appropriate error message */
|
|
io_error = xaccGetFileIOError();
|
|
SHOW_IO_ERR_MSG(io_error);
|
|
|
|
/* going down -- abandon ship */
|
|
if (uh_oh)
|
|
{
|
|
xaccSessionEnd (newsess);
|
|
xaccSessionDestroy (newsess);
|
|
|
|
/* well, no matter what, I think its 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 user continues, then bad things will happen ...
|
|
*/
|
|
if (NULL == topgroup)
|
|
{
|
|
topgroup = xaccMallocAccountGroup();
|
|
}
|
|
free (newfile);
|
|
return;
|
|
}
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
xaccSessionEnd (current_session);
|
|
xaccSessionDestroy (current_session);
|
|
current_session = newsess;
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* oops ... file already exists ... ask user what to do... */
|
|
if (newgrp)
|
|
{
|
|
char *tmpmsg;
|
|
tmpmsg = alloca (strlen (FMB_EEXIST_MSG) + strlen (newfile));
|
|
sprintf (tmpmsg, FMB_EEXIST_MSG, newfile);
|
|
/* if user says cancel, we should break out */
|
|
if (! verifyBox ( tmpmsg)) return;
|
|
|
|
/* Whoa-ok. Blow away the previous file.
|
|
* Do not disable logging ... we want to capture the
|
|
* old file in the log, just in case the user later
|
|
* decides it was all a big mistake. */
|
|
xaccSessionSetGroup (newsess, NULL);
|
|
/* xaccLogDisable(); no don't disable, keep logging on */
|
|
xaccFreeAccountGroup (newgrp);
|
|
}
|
|
|
|
/* OK, save the data to the file ... */
|
|
xaccLogSetBaseName (newfile);
|
|
xaccSessionSetGroup (newsess, oldgrp);
|
|
gncFileSave ();
|
|
free (newfile);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
gncFileQuit (void)
|
|
{
|
|
AccountGroup *grp;
|
|
|
|
grp = xaccSessionGetGroup (current_session);
|
|
/* if session not yet started ... */
|
|
if (!grp) grp = topgroup;
|
|
|
|
/* disable logging; the mass deletetion of accounts and transactions
|
|
* during shutdown is not something we want to keep in a journal. */
|
|
xaccLogDisable();
|
|
|
|
xaccSessionEnd (current_session);
|
|
xaccSessionDestroy (current_session);
|
|
current_session = NULL;
|
|
// xaccGroupWindowDestroy (grp);
|
|
xaccFreeAccountGroup (grp);
|
|
topgroup = NULL;
|
|
|
|
}
|
|
|
|
/********************* END OF FILE **********************************/
|