Cutecash: Fix file loading, saving, and save-as. Works fine now.

Also, use the new GDate setter/getter for the transaction time.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18926 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2010-03-17 19:24:01 +00:00
parent 59be2dc692
commit 4e3be7b89c
11 changed files with 808 additions and 350 deletions

View File

@ -126,9 +126,6 @@ IF (UNIX)
")
ENDIF (UNIX)
# Let cmake copy the created file only on changes.
#CONFIGURE_FILE (${CONFIG_H} ${CMAKE_CURRENT_BINARY_DIR}/config.h COPYONLY)
# ############################################################
# The subdirectories

View File

@ -35,15 +35,13 @@ SET (libgnc_core_utils_SOURCES
SET_SOURCE_FILES_PROPERTIES (${libgnc_core_utils_SOURCES} PROPERTIES OBJECT_DEPENDS ${CONFIG_H})
# Workaround to create a very simple gncla-dir.h file
FILE (WRITE ${CMAKE_CURRENT_BINARY_DIR}/gncla-dir.h.tmp "
FILE (WRITE ${CMAKE_CURRENT_BINARY_DIR}/gncla-dir.h "
#define PREFIX \"${CMAKE_INSTALL_PREFIX}\"
#define DATADIR \"${CMAKE_INSTALL_PREFIX}/share\"
#define SYSCONFDIR \"${CMAKE_INSTALL_PREFIX}/etc\"
#define LIBDIR \"${CMAKE_INSTALL_PREFIX}/lib\"
#define LOCALE_DATADIRNAME \"share\"
")
# Let cmake copy the created file only on changes.
CONFIGURE_FILE (${CMAKE_CURRENT_BINARY_DIR}/gncla-dir.h.tmp ${CMAKE_CURRENT_BINARY_DIR}/gncla-dir.h COPYONLY)
SET (libgnc_core_utils_HEADERS
binreloc.h

View File

@ -21,6 +21,7 @@ SET (gnc_SOURCES
SplitListModel.cpp
main.cpp
mainwindow.cpp
mainwindow-file.cpp
)
SET (gnc_QOBJECT_HEADERS

View File

@ -175,7 +175,7 @@ QUndoCommand* setTransactionDate(Transaction& t, const QDate& newValue)
{
return new Cmd<Transaction, QDate>(QObject::tr("Edit Transaction Date"),
t, &Transaction::setDatePosted,
t.getDatePosted().date(), newValue);
t.getDatePosted(), newValue);
}

View File

@ -63,6 +63,13 @@ inline QDateTime toQDateTime(const ::Timespec& timespec)
result.setTimeSpec(Qt::UTC);
return result;
}
inline QDate toQDate(const ::GDate& d)
{
if (g_date_valid(&d))
return QDate(g_date_year(&d), g_date_month(&d), g_date_day(&d));
else
return QDate();
}
inline ::Timespec toTimespec(const QDateTime& qdt)
{
::Timespec result;

View File

@ -122,7 +122,7 @@ QVariant SplitListModel::data(const QModelIndex& index, int role) const
{
case Qt::DisplayRole:
case Qt::EditRole:
return trans.getDatePosted().date();
return trans.getDatePosted();
default:
return QVariant();
}
@ -307,7 +307,7 @@ bool SplitListModel::setData(const QModelIndex &index, const QVariant &value, in
Transaction trans = split.getParent();
Split other = split.getOtherSplit();
Q_ASSERT(other);
Commodity originCommodity =split.getAccount().getCommodity();
Commodity originCommodity = split.getAccount().getCommodity();
Commodity transCommodity = trans.getCurrency();
Commodity otherCommodity = other.getAccount().getCommodity();
if (originCommodity != transCommodity

View File

@ -87,10 +87,9 @@ public:
bool isBalanced() const { return xaccTransIsBalanced(get()); }
Numeric getAccountConvRate(const Account& acc) const { return xaccTransGetAccountConvRate(get(), acc.get()); }
void setDatePosted(const QDate& t) { xaccTransSetDatePostedSecs(get(), QDateTime(t, QTime(12,0,0)).toTime_t()); }
void setDatePosted(const QDateTime& t) { xaccTransSetDatePostedSecs(get(), t.toTime_t()); }
void setDatePosted(const QDate& d) { xaccTransSetDate(get(), d.day(), d.month(), d.year()); }
void setDateEntered(const QDateTime& t) { xaccTransSetDateEnteredSecs(get(), t.toTime_t()); }
QDateTime getDatePosted() const { return toQDateTime(xaccTransRetDatePostedTS(get())); }
QDate getDatePosted() const { return toQDate(xaccTransGetDatePostedGDate(get())); }
QDateTime getDateEntered() const { return toQDateTime(xaccTransRetDateEnteredTS(get())); }
};

774
src/gnc/mainwindow-file.cpp Normal file
View File

@ -0,0 +1,774 @@
/*
* mainwindow-file.cpp
* Copyright (C) 2010 Christian Stimming
*
* 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, contact:
*
* Free Software Foundation Voice: +1-617-542-5942
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
* Boston, MA 02110-1301, USA gnu@gnu.org
*/
#include <QtCore/QSettings>
#include <QtGui/QCloseEvent>
#include <QtGui/QFileDialog>
#include <QtGui/QMessageBox>
#include <QtGui/QProgressBar>
#include <QtGui/QPushButton>
#include <QtGui/QToolBar>
#include <QtGui/QUndoStack>
#include <QDebug>
#include "config.h"
#include "mainwindow.hpp"
#include "ui_mainwindow.h"
// gnucash includes
#include <glib/gi18n.h>
extern "C"
{
#include "qof.h"
#include "engine/gnc-hooks.h"
#include "core-utils/gnc-uri-utils.h"
#include "engine/Account.h"
#include "engine/TransLog.h"
}
#include "gnc/Account.hpp"
#include "gnc/AccountItemModel.hpp"
#include "gnc/Book.hpp"
#include "gnc/Numeric.hpp"
#include "gnc/Split.hpp"
#include "gnc/SplitListModel.hpp"
#include "gnc/RecentFileMenu.hpp"
#include "gnc/Cmd.hpp"
namespace gnc
{
inline QString errorToString(QofBackendError err)
{
return errorToStringPair(err).first;
}
inline QString errorToDescription(QofBackendError err)
{
return errorToStringPair(err).second;
}
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_GUI;
static const char* PROPERTY_TAB_PREVIOUSPOS = "tab_previouspos";
static gint save_in_progress = 0;
// ////////////////////////////////////////////////////////////
bool MainWindow::show_session_error (QWidget *parent,
::QofBackendError io_error,
const QString& filename,
GNCFileDialogType type)
{
bool should_abort = true;
QString fmt;
switch (io_error)
{
case ERR_BACKEND_NO_ERR:
should_abort = FALSE;
break;
case ERR_BACKEND_NO_HANDLER:
QMessageBox::critical(parent, tr("Error"),
tr("No suitable backend was found for %1.").arg(filename));
break;
case ERR_BACKEND_NO_BACKEND:
QMessageBox::critical(parent, tr("Error"),
tr("The URL %1 is not supported by this version of GnuCash.").arg(filename));
break;
case ERR_BACKEND_BAD_URL:
QMessageBox::critical(parent, tr("Error"),
tr("Can't parse the URL %1.").arg(filename));
break;
case ERR_BACKEND_CANT_CONNECT:
QMessageBox::critical(parent, tr("Error"),
tr("Can't connect to %1. "
"The host, username or password were incorrect.").arg(filename));
break;
case ERR_BACKEND_CONN_LOST:
QMessageBox::critical(parent, tr("Error"),
tr("Can't connect to %1. "
"Connection was lost, unable to send data.").arg(filename));
break;
case ERR_BACKEND_TOO_NEW:
QMessageBox::critical(parent, tr("Error"),
tr("This file/URL appears to be from a newer version "
"of GnuCash. You must upgrade your version of GnuCash "
"to work with this data."));
break;
case ERR_BACKEND_NO_SUCH_DB:
if (QMessageBox::question(parent, tr("Create New File?"),
tr("The database %1 doesn't seem to exist. "
"Do you want to create it?").arg(filename),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
should_abort = false;
}
break;
case ERR_BACKEND_LOCKED:
switch (type)
{
case GNC_FILE_DIALOG_OPEN:
default:
fmt = tr("GnuCash could not obtain the lock for %1. "
"That database may be in use by another user, "
"in which case you should not open the database. "
"Do you want to proceed with opening the database?");
break;
case GNC_FILE_DIALOG_IMPORT:
fmt = tr("GnuCash could not obtain the lock for %1. "
"That database may be in use by another user, "
"in which case you should not import the database. "
"Do you want to proceed with importing the database?");
break;
case GNC_FILE_DIALOG_SAVE:
fmt = tr("GnuCash could not obtain the lock for %1. "
"That database may be in use by another user, "
"in which case you should not save the database. "
"Do you want to proceed with saving the database?");
break;
case GNC_FILE_DIALOG_EXPORT:
fmt = tr("GnuCash could not obtain the lock for %1. "
"That database may be in use by another user, "
"in which case you should not export the database. "
"Do you want to proceed with exporting the database?");
break;
}
if (QMessageBox::question(parent, tr("Create New File?"),
tr("The database %1 doesn't seem to exist. "
"Do you want to create it?").arg(filename),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
== QMessageBox::Yes)
{
should_abort = false;
}
break;
case ERR_BACKEND_READONLY:
QMessageBox::critical(parent, tr("Error"),
tr("GnuCash could not write to %1. "
"That database may be on a read-only file system, "
"or you may not have write permission for the directory.").arg(filename));
break;
case ERR_BACKEND_DATA_CORRUPT:
QMessageBox::critical(parent, tr("Error"),
tr("The file/URL %1 "
"does not contain GnuCash data or the data is corrupt.").arg(filename));
break;
case ERR_BACKEND_SERVER_ERR:
QMessageBox::critical(parent, tr("Error"),
tr("The server at URL %1 "
"experienced an error or encountered bad or corrupt data.").arg(filename));
break;
case ERR_BACKEND_PERM:
QMessageBox::critical(parent, tr("Error"),
tr("You do not have permission to access %1.").arg(filename));
break;
case ERR_BACKEND_MISC:
QMessageBox::critical(parent, tr("Error"),
tr("An error occurred while processing %1.").arg(filename));
break;
case ERR_FILEIO_FILE_BAD_READ:
if (QMessageBox::question(parent, tr("Continue?"),
tr("There was an error reading the file. "
"Do you want to continue?"),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
should_abort = false;
}
break;
case ERR_FILEIO_PARSE_ERROR:
QMessageBox::critical(parent, tr("Error"),
tr("There was an error parsing the file %1.").arg(filename));
break;
case ERR_FILEIO_FILE_EMPTY:
QMessageBox::critical(parent, tr("Error"),
tr("The file %1 is empty.").arg(filename));
break;
case ERR_FILEIO_FILE_NOT_FOUND:
if (type == GNC_FILE_DIALOG_SAVE)
{
should_abort = false;
}
else
{
QMessageBox::critical(parent, tr("Error"),
tr("The file %1 could not be found.").arg(filename));
}
break;
case ERR_FILEIO_FILE_TOO_OLD:
if (QMessageBox::question(parent, tr("Continue?"),
tr("This file is from an older version of GnuCash. "
"Do you want to continue?"),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
should_abort = false;
}
break;
case ERR_FILEIO_UNKNOWN_FILE_TYPE:
QMessageBox::critical(parent, tr("Error"),
tr("The file type of file %1 is unknown.").arg(filename));
break;
case ERR_FILEIO_BACKUP_ERROR:
QMessageBox::critical(parent, tr("Error"),
tr("Could not make a backup of the file %1").arg(filename));
break;
case ERR_FILEIO_WRITE_ERROR:
QMessageBox::critical(parent, tr("Error"),
tr("Could not write to file %1. Check that you have "
"permission to write to this file and that "
"there is sufficient space to create it.").arg(filename));
break;
case ERR_FILEIO_FILE_EACCES:
QMessageBox::critical(parent, tr("Error"),
tr("No read permission to read from file %1.").arg(filename));
break;
case ERR_SQL_DB_TOO_OLD:
if (QMessageBox::question(parent, tr("Upgrade Database?"),
tr("This database is from an older version of GnuCash. "
"Do you want to want to upgrade the database "
"to the current version?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
== QMessageBox::Yes)
{
should_abort = false;
}
break;
case ERR_SQL_DB_BUSY:
QMessageBox::critical(parent, tr("Error"),
tr("The SQL database is in use by other users, "
"and the upgrade cannot be performed until they logoff. "
"If there are currently no other users, consult the "
"documentation to learn how to clear out dangling login "
"sessions."));
break;
default:
PERR("FIXME: Unhandled error %d", io_error);
QMessageBox::critical(parent, tr("Error"),
tr("An unknown I/O error (%1) occurred.").arg(int(io_error)));
break;
}
return should_abort;
}
// ////////////////////////////////////////////////////////////
static void
gnc_book_opened (Session& sess)
{
gnc_hook_run(HOOK_BOOK_OPENED, sess.get());
}
namespace
{
/** This is a workaround function object so that we can obtain a
* QofPercentageFunc without extra boost::bind usage; obviously due to
* the static member variable it will not work if multiple instances
* are in use simultaneously */
class progress_functor
{
public:
progress_functor(QProgressBar *progressBar)
{
m_progressBar = progressBar;
}
~progress_functor()
{
m_progressBar = NULL;
}
static void static_func(const char *message, double percent)
{
assert(m_progressBar);
m_progressBar->setValue(int(percent));
// Give the Qt event loop some time
qApp->processEvents();
}
private:
static QProgressBar *m_progressBar;
};
QProgressBar *progress_functor::m_progressBar = NULL;
} // END namespace anonymous
void MainWindow::loadFile(const QString &fileName)
{
if (fileName.isEmpty())
return;
// copied from gnc_post_file_open, gnome-utils/gnc-file.c
QString newfile_qstring =
gchar_to_QString(gnc_uri_normalize_uri(fileName.toUtf8(), TRUE));
if (newfile_qstring.isEmpty())
{
show_session_error (this, ERR_FILEIO_FILE_NOT_FOUND, fileName,
GNC_FILE_DIALOG_OPEN);
return;
}
QByteArray newfile = newfile_qstring.toUtf8();
/* disable events while moving over to the new set of accounts;
* the mass deletion of accounts and transactions during
* switchover would otherwise cause excessive redraws. */
qof_event_suspend ();
// Change the mouse to a busy cusor
QApplication::setOverrideCursor(Qt::WaitCursor);
/* -------------- BEGIN CORE SESSION CODE ------------- */
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
m_session.call_close_hooks();
gnc_hook_run(HOOK_BOOK_CLOSED, m_session.get());
qof_session_destroy(m_session.get());
m_session.reset();
/* load the accounts from the users datafile */
/* but first, check to make sure we've got a session going. */
QofSession *new_session = qof_session_new ();
bool we_are_in_error = false;
qof_session_begin (new_session, newfile, FALSE, FALSE);
QofBackendError io_err = qof_session_get_error (new_session);
/* if file appears to be locked, ask the user ... */
if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
{
QString fmt1 = tr("GnuCash could not obtain the lock for %1.").arg(fileName);
QString fmt2 =
((ERR_BACKEND_LOCKED == io_err)
? tr("That database may be in use by another user, "
"in which case you should not open the database. "
"What would you like to do?")
: tr("That database may be on a read-only file system, "
"or you may not have write permission for the directory. "
"If you proceed you may not be able to save any changes. "
"What would you like to do?"));
QMessageBox msgBox;
QPushButton *openAnyway = msgBox.addButton(tr("_Open Anyway"), QMessageBox::ActionRole);
QPushButton *createNewFile = msgBox.addButton(tr("_Create New File"), QMessageBox::ActionRole);
QPushButton *close = msgBox.addButton(QMessageBox::Close);
msgBox.exec();
if (msgBox.clickedButton() == openAnyway)
{
/* user told us to ignore locks. So ignore them. */
qof_session_begin (new_session, newfile, TRUE, FALSE);
}
else if (msgBox.clickedButton() == createNewFile)
{
/* Can't use the given file, so just create a new
* database so that the user will get a window that
* they can click "Exit" on.
*/
newFile();
return;
}
else
{
qApp->quit();
return;
}
}
if (ERR_QSF_OPEN_NOT_MERGE == io_err)
{
we_are_in_error = true;
}
/* if the database doesn't exist, ask the user ... */
else if ((ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
if (false == show_session_error (this, io_err, newfile, GNC_FILE_DIALOG_OPEN))
{
/* user told us to create a new database. Do it. */
qof_session_begin (new_session, newfile, FALSE, TRUE);
}
}
/* Check for errors again, since above may have cleared the lock.
* If its still locked, still, doesn't exist, still too old, then
* don't bother with the message, just die. */
io_err = qof_session_get_error (new_session);
if ((ERR_BACKEND_LOCKED == io_err) ||
(ERR_BACKEND_READONLY == io_err) ||
(ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
we_are_in_error = true;
}
else
{
we_are_in_error = show_session_error (this, io_err, newfile, GNC_FILE_DIALOG_OPEN);
}
if (!we_are_in_error)
{
gchar *logpath = NULL;
if ( gnc_uri_is_file_uri ( newfile ) )
logpath = gnc_uri_get_path(newfile);
PINFO ("logpath=%s", logpath ? logpath : "(null)");
xaccLogSetBaseName (logpath);
g_free ( logpath );
xaccLogDisable();
{
// Set up a progress bar in the statusBar()
QProgressBar progressBar;
progressBar.setMinimum(0);
progressBar.setMaximum(100);
statusBar()->showMessage(tr("Loading user data..."));
statusBar()->addPermanentWidget(&progressBar);
progressBar.show();
// This local progress_functor is a workaround on how to
// pass the suitable function pointer to session_load -
// not very nice because of its static member, but it does
// the trick for now.
progress_functor functor(&progressBar);
// Do the loading.
qof_session_load (new_session, &progress_functor::static_func);
// Remove the progress bar again from the status bar.
statusBar()->removeWidget(&progressBar);
}
xaccLogEnable();
/* check for i/o error, put up appropriate error dialog */
io_err = qof_session_get_error (new_session);
we_are_in_error = show_session_error(this, io_err, newfile, GNC_FILE_DIALOG_OPEN);
::Account* new_root_account = gnc_book_get_root_account (qof_session_get_book (new_session));
if (we_are_in_error) new_root_account = NULL;
/* Umm, came up empty-handed, but no error:
* The backend forgot to set an error. So make one up. */
if (!we_are_in_error && !new_root_account)
{
we_are_in_error = show_session_error (this, ERR_BACKEND_MISC, newfile,
GNC_FILE_DIALOG_OPEN);
}
}
QApplication::restoreOverrideCursor();
/* going down -- abandon ship */
if (we_are_in_error)
{
xaccLogDisable();
qof_session_destroy (new_session);
xaccLogEnable();
/* well, no matter what, I think it's a good idea to have a root
* account 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 root
* account, because if the user continues, then bad things will
* happen. */
new_session = qof_session_new ();
m_session.reset(new_session);
qof_event_resume ();
return;
}
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
m_session.reset(new_session);
qof_event_resume ();
/* Call this after re-enabling events. */
gnc_book_opened (m_session);
// ////////////////////////////////////////////////////////////
// Some display about this file
Account root (m_session.get_book().get_root_account());
if (root)
{
m_accountListModel = new AccountListModel(root, this);
ui->tableView->setModel(m_accountListModel);
m_accountTreeModel = new AccountTreeModel(root, this);
ui->treeView->setModel(m_accountTreeModel);
ui->treeViewTab->setProperty(PROPERTY_TAB_PREVIOUSPOS, ui->tabWidget->currentIndex());
ui->tabWidget->setCurrentWidget(ui->treeViewTab);
}
else
{
//ui->labelMain->setText(tr("No root account"));
}
// ////////////////////////////////////////////////////////////
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 5000);
}
// ////////////////////////////////////////////////////////////
bool MainWindow::saveFileAs(const QString &fileName)
{
if (gnc_uri_is_file_uri(fileName.toUtf8()))
{
QFile file(gchar_to_QString(gnc_uri_get_path(fileName.toUtf8())));
if (!file.open(QFile::WriteOnly))
{
QMessageBox::warning(this, tr("Application"),
tr("Cannot write file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return false;
}
file.close();
}
/* Check to see if the user specified the same file as the current
* file. If so, then just do a simple save, instead of a full save as */
/* FIXME Check if it is ok to have a password in the uri here */
QString newfile_qstring =
gchar_to_QString(gnc_uri_normalize_uri ( fileName.toUtf8(), TRUE ));
if (newfile_qstring.isEmpty())
{
show_session_error (this, ERR_FILEIO_FILE_NOT_FOUND, fileName,
GNC_FILE_DIALOG_SAVE);
return false;
}
QByteArray newfile = newfile_qstring.toUtf8();
QofSession *session = m_session.get();
if (m_session.get_url() == fileName)
{
return saveFile();
}
/* Make sure all of the data from the old file is loaded */
qof_session_ensure_all_data_loaded(session);
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
save_in_progress++;
QofSession *new_session = qof_session_new ();
qof_session_begin (new_session, newfile, FALSE, FALSE);
QofBackendError io_err = qof_session_get_error (new_session);
/* if file appears to be locked, ask the user ... */
if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
{
if (false == show_session_error (this, io_err, newfile, GNC_FILE_DIALOG_SAVE))
{
/* user told us to ignore locks. So ignore them. */
qof_session_begin (new_session, newfile, TRUE, FALSE);
}
}
/* if the database doesn't exist, ask the user ... */
else if ((ERR_FILEIO_FILE_NOT_FOUND == io_err) ||
(ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
if (false == show_session_error (this, io_err, newfile, GNC_FILE_DIALOG_SAVE))
{
/* user told us to create a new database. Do it. */
qof_session_begin (new_session, 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 = qof_session_get_error (new_session);
if (ERR_BACKEND_NO_ERR != io_err)
{
show_session_error (this, io_err, newfile, GNC_FILE_DIALOG_SAVE);
xaccLogDisable();
qof_session_destroy (new_session);
xaccLogEnable();
save_in_progress--;
return false;
}
/* oops ... file already exists ... ask user what to do... */
if (qof_session_save_may_clobber_data (new_session))
{
if (QMessageBox::question(this, tr("File Exists"),
tr("The file %1 already exists. "
"Are you sure you want to overwrite it?").arg(fileName),
QMessageBox::Yes | QMessageBox::No)
!= QMessageBox::Yes)
{
/* if user says cancel, we should break out */
xaccLogDisable();
qof_session_destroy (new_session);
xaccLogEnable();
save_in_progress--;
return false;
}
/* Whoa-ok. Blow away the previous file. */
}
/* XXX Would logging make sense for databases as well (mysql/postgres) ?
* Currently the logpath is relative to the data file path.
* Databases don't have a file path, so no logging will be
* done for them in the current setup.
*/
gchar *logpath = NULL;
if ( gnc_uri_is_file_uri ( newfile ) )
logpath = gnc_uri_get_path(newfile);
PINFO ("logpath=%s", logpath ? logpath : "(null)");
xaccLogSetBaseName (logpath);
g_free ( logpath );
/* Prevent race condition between swapping the contents of the two
* sessions, and actually installing the new session as the current
* one. Any event callbacks that occur in this interval will have
* problems if they check for the current book. */
qof_event_suspend();
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
qof_session_swap_data (session, new_session);
qof_session_destroy(m_session.get());
m_session.reset();
session = NULL;
/* XXX At this point, we should really mark the data in the new session
* as being 'dirty', since we haven't saved it at all under the new
* session. But I'm lazy...
*/
m_session.reset(new_session);
qof_event_resume();
/* --------------- END CORE SESSION CODE -------------- */
bool r = saveFile();
save_in_progress--;
setCurrentFile(fileName);
return r;
}
static bool been_here_before = false;
bool MainWindow::saveFile()
{
/* hack alert -- Somehow make sure all in-progress edits get committed! */
/* If we don't have a filename/path to save to get one. */
if (m_session.get_url().isEmpty())
return on_actionSave_as_triggered();
/* use the current session to save to file */
save_in_progress++;
QApplication::setOverrideCursor(Qt::WaitCursor);
{
// Set up a progress bar in the statusBar()
QProgressBar progressBar;
progressBar.setMinimum(0);
progressBar.setMaximum(100);
statusBar()->showMessage(tr("Saving user data..."));
statusBar()->addPermanentWidget(&progressBar);
progressBar.show();
// A local progress_functor to pass the suitable function
// pointer to session_load
progress_functor functor(&progressBar);
// The actual saving
qof_session_save (m_session.get(), &progress_functor::static_func);
// Remove the progress bar again from the status bar.
statusBar()->removeWidget(&progressBar);
}
// And saving is finished
QApplication::restoreOverrideCursor();
save_in_progress--;
/* Make sure everything's OK - disk could be full, file could have
become read-only etc. */
QofBackendError io_err = m_session.get_error ();
if (ERR_BACKEND_NO_ERR != io_err)
{
show_session_error (this, io_err, m_session.get_url(), GNC_FILE_DIALOG_SAVE);
if (been_here_before)
return true;
been_here_before = true;
on_actionSave_as_triggered();
/* been_here prevents infinite recursion */
been_here_before = false;
return true;
}
xaccReopenLog();
gnc_hook_run(HOOK_BOOK_SAVED, m_session.get());
statusBar()->showMessage(tr("File saved"), 5000);
return true;
}
} // END namespace gnc

View File

@ -136,7 +136,7 @@ bool MainWindow::on_actionSave_triggered()
}
else
{
return saveFile(m_currentFilename);
return saveFile();
}
}
@ -147,7 +147,7 @@ bool MainWindow::on_actionSave_as_triggered()
if (fileName.isEmpty())
return false;
return saveFile(fileName);
return saveFileAs(fileName);
}
// Auto-connected to ui->actionAbout's signal triggered()
@ -469,12 +469,6 @@ void MainWindow::closeEvent(QCloseEvent *event)
}
}
static void
gnc_book_opened (Session& sess)
{
gnc_hook_run(HOOK_BOOK_OPENED, sess.get());
}
void MainWindow::newFile()
{
if (maybeSave())
@ -500,334 +494,10 @@ void MainWindow::newFile()
gnc_hook_run(HOOK_NEW_BOOK, NULL);
/* Call this after re-enabling events. */
gnc_book_opened (m_session);
gnc_hook_run(HOOK_BOOK_OPENED, m_session.get());
setCurrentFile("");
}
}
namespace
{
/** This is a workaround function object so that we can obtain a
* QofPercentageFunc without extra boost::bind usage; obviously due to
* the static member variable it will not work if multiple instances
* are in use simultaneously */
class progress_functor
{
public:
progress_functor(QProgressBar *progressBar)
{
m_progressBar = progressBar;
}
~progress_functor()
{
m_progressBar = NULL;
}
static void static_func(const char *message, double percent)
{
assert(m_progressBar);
m_progressBar->setValue(int(percent));
// Give the Qt event loop some time
qApp->processEvents();
}
private:
static QProgressBar *m_progressBar;
};
QProgressBar *progress_functor::m_progressBar = NULL;
} // END namespace anonymous
void MainWindow::loadFile(const QString &fileName)
{
if (fileName.isEmpty())
return;
// copied from gnc_post_file_open, gnome-utils/gnc-file.c
/* disable events while moving over to the new set of accounts;
* the mass deletion of accounts and transactions during
* switchover would otherwise cause excessive redraws. */
qof_event_suspend ();
/* Change the mouse to a busy cursor */
//gnc_set_busy_cursor (NULL, TRUE);
/* -------------- BEGIN CORE SESSION CODE ------------- */
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
m_session.call_close_hooks();
gnc_hook_run(HOOK_BOOK_CLOSED, m_session.get());
qof_session_destroy(m_session.get());
m_session.reset();
/* load the accounts from the users datafile */
/* but first, check to make sure we've got a session going. */
QofSession *new_session = qof_session_new ();
bool we_are_in_error = false;
QByteArray newfile = fileName.toUtf8();
qof_session_begin (new_session, newfile, FALSE, FALSE);
QofBackendError io_err = qof_session_get_error (new_session);
/* if file appears to be locked, ask the user ... */
if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
{
QString fmt1 = tr("GnuCash could not obtain the lock for %1.").arg(fileName);
QString fmt2 =
((ERR_BACKEND_LOCKED == io_err)
? tr("That database may be in use by another user, "
"in which case you should not open the database. "
"What would you like to do? Open anyway? FIXME")
: tr("That database may be on a read-only file system, "
"or you may not have write permission for the directory. "
"If you proceed you may not be able to save any changes. "
"What would you like to do? Open anyway? FIXME"));
if (QMessageBox::question(this, fmt1, fmt2,
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
/* user told us to ignore locks. So ignore them. */
qof_session_begin (new_session, newfile, TRUE, FALSE);
}
else
{
/* Can't use the given file, so just create a new
* database so that the user will get a window that
* they can click "Exit" on.
*/
newFile();
}
}
/* if the database doesn't exist, ask the user ... */
else if ((ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
if (QMessageBox::question(this, tr("Create New File?"),
tr("The file %1 does not exist. Do you want to create it?").arg(fileName),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
/* user told us to create a new database. Do it. */
qof_session_begin (new_session, newfile, FALSE, TRUE);
}
}
QApplication::setOverrideCursor(Qt::WaitCursor);
/* Check for errors again, since above may have cleared the lock.
* If its still locked, still, doesn't exist, still too old, then
* don't bother with the message, just die. */
io_err = qof_session_get_error (new_session);
if ((ERR_BACKEND_LOCKED == io_err) ||
(ERR_BACKEND_READONLY == io_err) ||
(ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
we_are_in_error = true;
}
else
{
if (io_err != ERR_BACKEND_NO_ERR)
{
we_are_in_error = !(QMessageBox::question(this, tr("Open anyway?"),
tr("The file %1 has some errors: %2: %3. Open anyway?")
.arg(fileName)
.arg(errorToString(io_err))
.arg(errorToDescription(io_err)),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok);
}
}
if (!we_are_in_error)
{
char * logpath = gnc_uri_get_path(newfile);
PINFO ("logpath=%s", logpath ? logpath : "(null)");
xaccLogSetBaseName (logpath);
xaccLogDisable();
{
// Set up a progress bar in the statusBar()
QProgressBar progressBar;
progressBar.setMinimum(0);
progressBar.setMaximum(100);
statusBar()->showMessage(tr("Loading user data..."));
statusBar()->addPermanentWidget(&progressBar);
progressBar.show();
// This local progress_functor is a workaround on how to
// pass the suitable function pointer to session_load -
// not very nice because of its static member, but it does
// the trick for now.
progress_functor functor(&progressBar);
// Do the loading.
qof_session_load (new_session, &progress_functor::static_func);
// Remove the progress bar again from the status bar. No
// explicit delete necessary because it is just a local
// variable.
statusBar()->removeWidget(&progressBar);
}
xaccLogEnable();
/* check for i/o error, put up appropriate error dialog */
io_err = qof_session_get_error (new_session);
if (io_err != ERR_BACKEND_NO_ERR)
{
we_are_in_error = !(QMessageBox::question(this, tr("Error on Open"),
tr("There was an error opening the file %1: %2: %3. Continue? FIXME")
.arg(fileName)
.arg(errorToString(io_err))
.arg(errorToDescription(io_err)),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok);
}
}
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
if (m_session.get())
{
qof_session_destroy(m_session.get());
m_session.reset();
}
m_session.reset(new_session);
qof_event_resume ();
/* Call this after re-enabling events. */
gnc_book_opened (m_session);
// ////////////////////////////////////////////////////////////
// Some display about this file
Account root (m_session.get_book().get_root_account());
if (root)
{
m_accountListModel = new AccountListModel(root, this);
ui->tableView->setModel(m_accountListModel);
m_accountTreeModel = new AccountTreeModel(root, this);
ui->treeView->setModel(m_accountTreeModel);
ui->treeViewTab->setProperty(PROPERTY_TAB_PREVIOUSPOS, ui->tabWidget->currentIndex());
ui->tabWidget->setCurrentWidget(ui->treeViewTab);
}
else
{
//ui->labelMain->setText(tr("No root account"));
}
// ////////////////////////////////////////////////////////////
QApplication::restoreOverrideCursor();
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 5000);
}
bool MainWindow::saveFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::WriteOnly))
{
QMessageBox::warning(this, tr("Application"),
tr("Cannot write file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return false;
}
file.close();
QApplication::setOverrideCursor(Qt::WaitCursor);
QofSession *session = m_session.get();
/* Make sure all of the data from the old file is loaded */
qof_session_ensure_all_data_loaded(session);
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
QByteArray newfile = fileName.toUtf8();
xaccLogSetBaseName(newfile); //FIXME: This is premature.
QofSession *new_session = qof_session_new ();
qof_session_begin (new_session, newfile, FALSE, FALSE);
QofBackendError io_err = qof_session_get_error (new_session);
/* if file appears to be locked, ask the user ... */
if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
{
if (QMessageBox::question(this, tr("Ignore Lock?"),
tr("The file %1 is locked. Should we ignore the lock?").arg(fileName),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
/* user told us to ignore locks. So ignore them. */
qof_session_begin (new_session, newfile, TRUE, FALSE);
}
}
/* if the database doesn't exist, ask the user ... */
else if ((ERR_FILEIO_FILE_NOT_FOUND == io_err) ||
(ERR_BACKEND_NO_SUCH_DB == io_err) ||
(ERR_SQL_DB_TOO_OLD == io_err))
{
if (QMessageBox::question(this, tr("Create New File?"),
tr("The file %1 does not exist. Should it be created?").arg(fileName),
QMessageBox::Ok | QMessageBox::Cancel)
== QMessageBox::Ok)
{
/* user told us to create a new database. Do it. */
qof_session_begin (new_session, 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 = qof_session_get_error (new_session);
if (ERR_BACKEND_NO_ERR != io_err)
{
QMessageBox::critical(this, tr("Still in Error"),
tr("The file %1 still cannot be written: %2: %3. FIXME")
.arg(fileName)
.arg(errorToString(io_err))
.arg(errorToDescription(io_err)));
xaccLogDisable();
qof_session_destroy (new_session);
xaccLogEnable();
return false;
}
/* Prevent race condition between swapping the contents of the two
* sessions, and actually installing the new session as the current
* one. Any event callbacks that occur in this interval will have
* problems if they check for the current book. */
qof_event_suspend();
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
qof_session_swap_data (session, new_session);
qof_session_destroy(m_session.get());
m_session.reset();
session = NULL;
/* XXX At this point, we should really mark the data in the new session
* as being 'dirty', since we haven't saved it at all under the new
* session. But I'm lazy...
*/
m_session.reset(new_session);
qof_event_resume();
/* --------------- END CORE SESSION CODE -------------- */
QApplication::restoreOverrideCursor();
setCurrentFile(fileName);
statusBar()->showMessage(tr("File saved"), 2000);
return true;
}
} // END namespace gnc

View File

@ -87,14 +87,28 @@ private:
void readSettings();
void writeSettings();
bool maybeSave();
void loadFile(const QString &fileName);
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
void viewOrHideTab(bool checkedView, QWidget *widget);
void reallyRemoveTab(int index);
void updateWindowTitle();
void loadFile(const QString &fileName);
bool saveFile();
bool saveFileAs(const QString &fileName);
typedef enum
{
GNC_FILE_DIALOG_OPEN,
GNC_FILE_DIALOG_IMPORT,
GNC_FILE_DIALOG_SAVE,
GNC_FILE_DIALOG_EXPORT
} GNCFileDialogType;
static bool show_session_error (QWidget *parent,
::QofBackendError io_error,
const QString& newfile,
GNCFileDialogType type);
Ui::MainWindow *ui;
QString m_currentFilename;

View File

@ -10,11 +10,9 @@ INCLUDE_DIRECTORIES (${CMAKE_SOURCE_DIR}/lib/libc) # for strptime.h
# Workaround to create a very simple gncla-dir.h file
FILE (WRITE ${CMAKE_CURRENT_BINARY_DIR}/qofla-dir.h.tmp "
FILE (WRITE ${CMAKE_CURRENT_BINARY_DIR}/qofla-dir.h "
#define QOF_LIB_DIR \"${CMAKE_INSTALL_PREFIX}/lib/gnucash\"
")
# Let cmake copy the created file only on changes.
CONFIGURE_FILE (${CMAKE_CURRENT_BINARY_DIR}/qofla-dir.h.tmp ${CMAKE_CURRENT_BINARY_DIR}/qofla-dir.h COPYONLY)
INCLUDE_DIRECTORIES (${CMAKE_CURRENT_BINARY_DIR}) # for qofla-dir.h