Enable editing of the "other" account of a transaction.

Register Account and our other types in the QMetaType system
so that QVariant can hold it and pass it between the model,
view, and delegate. Implement AccountSelectionDelegate that
presents a QComboBox editor widget for account selection.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18939 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2010-03-20 22:33:59 +00:00
parent ad0ddae5e6
commit 14f810a0ca
17 changed files with 304 additions and 21 deletions

View File

@ -33,9 +33,11 @@ extern "C"
#include "gnc/GncInstance.hpp"
#include "gnc/Commodity.hpp"
#include "gnc/Numeric.hpp"
#include <QString>
#include <QList>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QMetaType>
namespace gnc
{
@ -58,6 +60,7 @@ public:
: base_class(ptr)
{ }
QString getName() const { return QString::fromUtf8(xaccAccountGetName(get())); }
QString getFullName() const { return gchar_to_QString(gnc_account_get_full_name (get())); }
QString getCode() const { return QString::fromUtf8(xaccAccountGetCode(get())); }
QString getDescription() const { return QString::fromUtf8(xaccAccountGetDescription(get())); }
Commodity getCommodity() const { return xaccAccountGetCommodity(get()); }
@ -109,4 +112,6 @@ public:
} // END namespace gnc
Q_DECLARE_METATYPE(gnc::Account)
#endif

View File

@ -186,4 +186,32 @@ QModelIndex AccountListModel::index(int row, int column,
}
// ////////////////////////////////////////////////////////////
int AccountListNamesModel::columnCount(const QModelIndex& parent) const
{
return 1;
}
QVariant AccountListNamesModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
Account account(static_cast< ::Account*>(index.internalPointer()));
switch (index.column())
{
case 0:
switch (role)
{
case Qt::DisplayRole:
return account.getFullName();
default:
return QVariant();
}
default:
return QVariant();
}
}
} // END namespace gnc

View File

@ -71,11 +71,11 @@ class AccountListModel : public AccountTreeModel
{
Q_OBJECT
public:
typedef AccountTreeModel base_class;
AccountListModel(Account rootaccount, QObject *parent = 0)
: AccountTreeModel(rootaccount, parent)
: base_class(rootaccount, parent)
, m_list(Account::fromGList(rootaccount.get_descendants()))
{
}
{}
int rowCount(const QModelIndex& parent = QModelIndex()) const { return m_list.size(); }
@ -84,10 +84,28 @@ public:
QModelIndex parent(const QModelIndex &index) const { return QModelIndex(); }
int indexOf(AccountQList::value_type value) const { return m_list.indexOf(value); }
const AccountQList::value_type at(int i) const { return m_list.at(i); }
private:
AccountQList m_list;
};
/** Specialization of the account list model that only shows the
* "Account Full Name" in one single column.
*/
class AccountListNamesModel : public AccountListModel
{
Q_OBJECT
public:
typedef AccountListModel base_class;
AccountListNamesModel(Account rootaccount, QObject *parent = 0)
: base_class(rootaccount, parent)
{}
int columnCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role) const;
};
} // END namespace gnc

View File

@ -0,0 +1,105 @@
/*
* AccountSelectionDelegate.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 "AccountSelectionDelegate.hpp"
#include "gnc/AccountItemModel.hpp"
#include "gnc/Book.hpp"
#include "gnc/Split.hpp"
#include <QDebug>
#include <QComboBox>
namespace gnc
{
QString AccountSelectionDelegate::displayText(const QVariant& value, const QLocale& locale) const
{
if (value.canConvert<Account>())
{
Account acc = value.value<Account>();
return acc.getFullName();
}
else
{
return base_class::displayText(value, locale);
}
}
QWidget *AccountSelectionDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
QComboBox* comboBox = new QComboBox(parent);
Split split(static_cast< ::Split*>(index.internalPointer()));
if (split)
{
Book book = split.getBook();
Q_ASSERT(book);
Account rootaccount = book.get_root_account();
Q_ASSERT(rootaccount);
AccountListModel* model = new AccountListNamesModel(rootaccount, comboBox);
comboBox->setModel(model);
}
return comboBox;
}
void AccountSelectionDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
Q_ASSERT(index.isValid());
QVariant value = index.model()->data(index, Qt::EditRole);
if (value.canConvert<Account>())
{
Account acc = value.value<Account>();
Q_ASSERT(acc);
const AccountListModel* amodel = dynamic_cast<const AccountListModel*>(comboBox->model());
Q_ASSERT(amodel);
comboBox->setCurrentIndex(amodel->indexOf(acc.get()));
}
else
{
qDebug() << "huh? why no Account?";
}
}
void AccountSelectionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
int currentIndex = comboBox->currentIndex();
if (currentIndex == -1)
return;
const AccountListModel* amodel = dynamic_cast<const AccountListModel*>(comboBox->model());
Q_ASSERT(amodel);
Account acc(amodel->at(currentIndex));
Q_ASSERT(acc);
model->setData(index, QVariant::fromValue(acc), Qt::EditRole);
}
} // END namespace gnc

View File

@ -0,0 +1,55 @@
/*
* AccountSelectionDelegate.hpp
* 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
*/
#ifndef GNC_ACCOUNTSELECTIONDELEGATE_HPP
#define GNC_ACCOUNTSELECTIONDELEGATE_HPP
#include "gnc/Account.hpp"
#include <QtGui/QStyledItemDelegate>
#include <QDebug>
namespace gnc
{
class AccountSelectionDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
typedef QStyledItemDelegate base_class;
AccountSelectionDelegate(QObject* parent = 0)
: base_class(parent)
{}
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
virtual QString displayText(const QVariant& value, const QLocale& locale) const;
virtual void setEditorData(QWidget *editor, const QModelIndex &index) const;
virtual void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
};
} // END namespace gnc
#endif

View File

@ -11,6 +11,7 @@ LINK_DIRECTORIES (${GLIB2_LIBRARY_DIRS}
SET (gnc_SOURCES
AccountItemModel.cpp
AccountSelectionDelegate.cpp
Book.cpp
Cmd.cpp
Numeric.cpp
@ -28,6 +29,7 @@ SET (gnc_SOURCES
SET (gnc_QOBJECT_HEADERS
AccountItemModel.hpp
AccountSelectionDelegate.hpp
RecentFileMenu.hpp
SplitListModel.hpp
SplitListView.hpp

View File

@ -53,6 +53,8 @@ QUndoCommand* setSplitAction(Split& t, const QString& newValue)
QUndoCommand* setSplitReconcile(Split& t, char newValue)
{
if (newValue == t.getReconcile())
return NULL;
// Special third argument: The setter function takes a value
// directly, instead of a const-reference, so the template type
// must be given explicitly.
@ -61,6 +63,16 @@ QUndoCommand* setSplitReconcile(Split& t, char newValue)
t.getReconcile(), newValue);
}
QUndoCommand* setSplitAccount(Split& t, Account newValue)
{
// Temporary function pointer "tmp" to resolve the ambiguous
// overload "setAccount()".
void (Split::*tmp)(Account) = &Split::setAccount;
return new Cmd<Split, Account, void (Split::*)(Account)>(QObject::tr("Edit Split Account"),
t, tmp,
t.getAccount(), newValue);
}
QUndoCommand* setSplitAmount(Split& t, const Numeric& newValue)
{
return new Cmd<Split, Numeric>(QObject::tr("Edit Split Amount"),
@ -143,6 +155,8 @@ QUndoCommand* setSplitValueAndAmount(Split& t, const Numeric& newValue)
QUndoCommand* setTransactionNum(Transaction& t, const QString& newValue)
{
if (newValue == t.getNum())
return NULL;
return new Cmd<Transaction, QString>(QObject::tr("Edit Transaction Number"),
t, &Transaction::setNum,
t.getNum(), newValue);
@ -150,6 +164,8 @@ QUndoCommand* setTransactionNum(Transaction& t, const QString& newValue)
QUndoCommand* setTransactionDescription(Transaction& t, const QString& newValue)
{
if (newValue == t.getDescription())
return NULL;
return new Cmd<Transaction, QString>(QObject::tr("Edit Transaction Description"),
t, &Transaction::setDescription,
t.getDescription(), newValue);
@ -164,6 +180,8 @@ QUndoCommand* setTransactionNotes(Transaction& t, const QString& newValue)
QUndoCommand* setTransactionDate(Transaction& t, const QDate& newValue)
{
if (newValue == t.getDatePosted())
return NULL;
return new Cmd<Transaction, QDate>(QObject::tr("Edit Transaction Date"),
t, &Transaction::setDatePosted,
t.getDatePosted(), newValue);
@ -291,6 +309,12 @@ protected:
};
QUndoCommand* setSplitAccount(TmpSplit& t, Account newValue)
{
return new CmdRef<TmpSplit, ::Account*, void(TmpSplit::*)(::Account*)>(QObject::tr("Edit Split Account"),
t, &TmpSplit::setAccount,
t.getAccount(), newValue.get());
}
QUndoCommand* setSplitReconcile(TmpSplit& t, char newValue)
{
// Special third argument: The setter function takes a value

View File

@ -160,6 +160,8 @@ namespace cmd
// forth. Spooky, IMHO.
// QUndoCommand* setSplitMemo(Split& split, const QString& newValue);
// QUndoCommand* setSplitAction(Split& t, const QString& newValue);
QUndoCommand* setSplitAccount(Split& t, Account newValue);
QUndoCommand* setSplitAccount(TmpSplit& t, Account newValue);
QUndoCommand* setSplitReconcile(Split& t, char newValue);
QUndoCommand* setSplitReconcile(TmpSplit& t, char newValue);
// QUndoCommand* setSplitAmount(Split& t, const Numeric& newValue);

View File

@ -32,7 +32,8 @@ extern "C"
}
#include "gnc/GncInstance.hpp"
#include <QString>
#include <QtCore/QMetaType>
#include <QtCore/QString>
/** Wrapper around a gnucash gnc_commodity pointer */
namespace gnc
@ -70,4 +71,6 @@ inline bool operator!=(const Commodity& a, const Commodity& b)
} // END namespace gnc
Q_DECLARE_METATYPE(gnc::Commodity)
#endif

View File

@ -32,8 +32,9 @@ extern "C"
#include "app-utils/gnc-ui-util.h"
}
#include <QString>
#include <QDateTime>
#include <QtCore/QDateTime>
#include <QtCore/QMetaType>
#include <QtCore/QString>
namespace gnc
{
@ -197,4 +198,6 @@ inline bool operator!=(const Numeric& a, const Numeric& b)
} // END namespace gnc
Q_DECLARE_METATYPE(gnc::Numeric)
#endif

View File

@ -30,7 +30,7 @@ namespace gnc
{
Account Split::getAccount() const { return xaccSplitGetAccount(get()); }
void Split::setAccount(Account& acc) { xaccSplitSetAccount(get(), acc.get()); }
void Split::setAccount(Account acc) { xaccSplitSetAccount(get(), acc.get()); }
void Split::setAccount(::Account* acc) { xaccSplitSetAccount(get(), acc); }

View File

@ -31,8 +31,9 @@ extern "C"
#include "engine/Split.h"
}
#include <QString>
#include <QList>
#include <QtCore/QList>
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include "gnc/GncInstance.hpp"
#include "gnc/Numeric.hpp"
@ -63,7 +64,7 @@ public:
{ }
Account getAccount() const;
void setAccount(Account& acc);
void setAccount(Account acc);
void setAccount(::Account* acc);
Transaction getParent() const;
@ -153,4 +154,7 @@ private:
} // END namespace gnc
Q_DECLARE_METATYPE(gnc::Split)
Q_DECLARE_METATYPE(gnc::TmpSplit)
#endif

View File

@ -72,6 +72,7 @@ void SplitListModel::recreateTmpTrans()
{
m_tmpTransaction.clear();
m_tmpTransaction.push_back(TmpSplit(m_account.get()));
// m_tmpTransaction.push_back(TmpSplit(NULL));
m_tmpTransaction.setCommodity(m_account.getCommodity());
m_tmpTransaction.setDatePosted(QDate::currentDate());
}
@ -145,9 +146,9 @@ Qt::ItemFlags SplitListModel::flags(const QModelIndex &index) const
case COLUMN_RECONCILE:
case COLUMN_INCREASE:
case COLUMN_DECREASE:
case COLUMN_ACCOUNT:
// Allow write access as well
return result | Qt::ItemIsEditable;
case COLUMN_ACCOUNT:
case COLUMN_BALANCE:
default:
// Ensure read-only access only
@ -290,7 +291,11 @@ QVariant SplitListModel::data(const QModelIndex& index, int role) const
switch (role)
{
case Qt::DisplayRole:
return split.getCorrAccountFullName();
case Qt::EditRole:
if (trans.countSplits() == 2)
return QVariant::fromValue(split.getOtherSplit().getAccount());
else
return split.getCorrAccountFullName();
default:
return QVariant();
}
@ -485,6 +490,7 @@ bool SplitListModel::setData(const QModelIndex &index, const QVariant &value, in
Split split(static_cast< ::Split*>(index.internalPointer()));
Transaction trans(split.getParent());
QVariant y(trans);
// "Editing" is done by creating a Cmd-object and adding it to
// the undo stack. That's in fact all that was needed to
@ -507,6 +513,19 @@ bool SplitListModel::setData(const QModelIndex &index, const QVariant &value, in
case COLUMN_DESC:
cmd = cmd::setTransactionDescription(trans, value.toString());
break;
case COLUMN_ACCOUNT:
if (value.canConvert<Account>())
{
if (trans.countSplits() == 2)
{
Split other = split.getOtherSplit();
cmd = cmd::setSplitAccount(other, value.value<Account>());
}
else
QMessageBox::warning(NULL, tr("Unimplemented"),
tr("Sorry, but editing a transaction with more than two splits (here: %1) is not yet implemented.").arg(split.getParent().countSplits()));
}
break;
case COLUMN_RECONCILE:
{
QString str(value.toString());

View File

@ -64,6 +64,8 @@ public:
SplitListModel(const Account& acc, QUndoStack* undoStack, QObject *parent = 0);
~SplitListModel();
Account getAccount() const { return m_account; }
QModelIndex parent(const QModelIndex &index) const { return QModelIndex(); }
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;

View File

@ -24,6 +24,7 @@
#include "gnc/Account.hpp"
#include "gnc/SplitListModel.hpp"
#include "gnc/AccountSelectionDelegate.hpp"
#include <QtGui/QAbstractItemDelegate>
#include <QUndoStack>
@ -35,13 +36,20 @@ namespace gnc
SplitListView::SplitListView(Account account, QUndoStack* undoStack, QWidget* parent)
: base_class(parent)
{
// Create a model that is used in this view
SplitListModel *smodel = new SplitListModel(account, undoStack, this);
setModel(smodel);
connect(this, SIGNAL(editorClosed(const QModelIndex&,QAbstractItemDelegate::EndEditHint)),
smodel, SLOT(editorClosed(const QModelIndex&,QAbstractItemDelegate::EndEditHint)));
// Create a separate delegate only for the Account colum
QAbstractItemDelegate *accountDelegate = new AccountSelectionDelegate(this);
setItemDelegateForColumn(SplitListModel::COLUMN_ACCOUNT, accountDelegate);
// Appearance of this view
setAlternatingRowColors(true);
// Move the focus to the latest line
scrollToBottom();
if (model()->rowCount() > 0)
setCurrentIndex(model()->index(model()->rowCount() - 1, 0));

View File

@ -37,8 +37,9 @@ extern "C"
#include "gnc/Numeric.hpp"
#include "gnc/GncInstance.hpp"
#include <QString>
#include <QList>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QMetaType>
namespace gnc
{
@ -98,6 +99,7 @@ public:
static element_type* newInstance(const Book& b);
};
class TmpTransaction
{
public:
@ -142,4 +144,7 @@ private:
} // END namespace gnc
Q_DECLARE_METATYPE(gnc::Transaction)
Q_DECLARE_METATYPE(gnc::TmpTransaction)
#endif

View File

@ -387,7 +387,7 @@ void MainWindow::loadFile(const QString &fileName)
/* 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 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, "
@ -398,10 +398,10 @@ void MainWindow::loadFile(const QString &fileName)
"If you proceed you may not be able to save any changes. "
"What would you like to do?"));
QMessageBox msgBox(this);
msgBox.setWindowTitle(fmt1);
msgBox.setText(fmt2);
QPushButton *openAnyway = msgBox.addButton(tr("_Open Anyway"), QMessageBox::ActionRole);
QPushButton *createNewFile = msgBox.addButton(tr("_Create New File"), QMessageBox::ActionRole);
msgBox.setWindowTitle(tr("Could not obtain file lock"));
msgBox.setText(fmt1 + fmt2);
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)