From 14f810a0ca8ce116bf9a447122c9b396953ba9b9 Mon Sep 17 00:00:00 2001 From: Christian Stimming Date: Sat, 20 Mar 2010 22:33:59 +0000 Subject: [PATCH] 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 --- src/gnc/Account.hpp | 9 ++- src/gnc/AccountItemModel.cpp | 28 +++++++ src/gnc/AccountItemModel.hpp | 24 +++++- src/gnc/AccountSelectionDelegate.cpp | 105 +++++++++++++++++++++++++++ src/gnc/AccountSelectionDelegate.hpp | 55 ++++++++++++++ src/gnc/CMakeLists.txt | 2 + src/gnc/Cmd.cpp | 24 ++++++ src/gnc/Cmd.hpp | 2 + src/gnc/Commodity.hpp | 5 +- src/gnc/Numeric.hpp | 7 +- src/gnc/Split.cpp | 2 +- src/gnc/Split.hpp | 10 ++- src/gnc/SplitListModel.cpp | 23 +++++- src/gnc/SplitListModel.hpp | 2 + src/gnc/SplitListView.cpp | 8 ++ src/gnc/Transaction.hpp | 9 ++- src/gnc/mainwindow-file.cpp | 10 +-- 17 files changed, 304 insertions(+), 21 deletions(-) create mode 100644 src/gnc/AccountSelectionDelegate.cpp create mode 100644 src/gnc/AccountSelectionDelegate.hpp diff --git a/src/gnc/Account.hpp b/src/gnc/Account.hpp index d8a280eaee..0ac958c28e 100644 --- a/src/gnc/Account.hpp +++ b/src/gnc/Account.hpp @@ -33,9 +33,11 @@ extern "C" #include "gnc/GncInstance.hpp" #include "gnc/Commodity.hpp" +#include "gnc/Numeric.hpp" -#include -#include +#include +#include +#include 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 diff --git a/src/gnc/AccountItemModel.cpp b/src/gnc/AccountItemModel.cpp index 2418d81055..aa0ac1e18e 100644 --- a/src/gnc/AccountItemModel.cpp +++ b/src/gnc/AccountItemModel.cpp @@ -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 diff --git a/src/gnc/AccountItemModel.hpp b/src/gnc/AccountItemModel.hpp index bedc831635..8ee5e063b1 100644 --- a/src/gnc/AccountItemModel.hpp +++ b/src/gnc/AccountItemModel.hpp @@ -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 diff --git a/src/gnc/AccountSelectionDelegate.cpp b/src/gnc/AccountSelectionDelegate.cpp new file mode 100644 index 0000000000..8d0f7903ef --- /dev/null +++ b/src/gnc/AccountSelectionDelegate.cpp @@ -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 +#include + +namespace gnc +{ + +QString AccountSelectionDelegate::displayText(const QVariant& value, const QLocale& locale) const +{ + if (value.canConvert()) + { + Account acc = value.value(); + 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(editor); + + Q_ASSERT(index.isValid()); + + QVariant value = index.model()->data(index, Qt::EditRole); + if (value.canConvert()) + { + Account acc = value.value(); + Q_ASSERT(acc); + const AccountListModel* amodel = dynamic_cast(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(editor); + int currentIndex = comboBox->currentIndex(); + if (currentIndex == -1) + return; + const AccountListModel* amodel = dynamic_cast(comboBox->model()); + Q_ASSERT(amodel); + Account acc(amodel->at(currentIndex)); + Q_ASSERT(acc); + model->setData(index, QVariant::fromValue(acc), Qt::EditRole); +} + + +} // END namespace gnc diff --git a/src/gnc/AccountSelectionDelegate.hpp b/src/gnc/AccountSelectionDelegate.hpp new file mode 100644 index 0000000000..b4f7bef285 --- /dev/null +++ b/src/gnc/AccountSelectionDelegate.hpp @@ -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 +#include + +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 diff --git a/src/gnc/CMakeLists.txt b/src/gnc/CMakeLists.txt index bf9ef22c19..3eb27d47bb 100644 --- a/src/gnc/CMakeLists.txt +++ b/src/gnc/CMakeLists.txt @@ -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 diff --git a/src/gnc/Cmd.cpp b/src/gnc/Cmd.cpp index 00b4456221..da728c94d3 100644 --- a/src/gnc/Cmd.cpp +++ b/src/gnc/Cmd.cpp @@ -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(QObject::tr("Edit Split Account"), + t, tmp, + t.getAccount(), newValue); +} + QUndoCommand* setSplitAmount(Split& t, const Numeric& newValue) { return new Cmd(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(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(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(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(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 diff --git a/src/gnc/Cmd.hpp b/src/gnc/Cmd.hpp index 4b2c5a9e67..0aa03da6f7 100644 --- a/src/gnc/Cmd.hpp +++ b/src/gnc/Cmd.hpp @@ -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); diff --git a/src/gnc/Commodity.hpp b/src/gnc/Commodity.hpp index 63bc41d7df..0125797c56 100644 --- a/src/gnc/Commodity.hpp +++ b/src/gnc/Commodity.hpp @@ -32,7 +32,8 @@ extern "C" } #include "gnc/GncInstance.hpp" -#include +#include +#include /** 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 diff --git a/src/gnc/Numeric.hpp b/src/gnc/Numeric.hpp index d8a2df2c54..c2eedaf0b8 100644 --- a/src/gnc/Numeric.hpp +++ b/src/gnc/Numeric.hpp @@ -32,8 +32,9 @@ extern "C" #include "app-utils/gnc-ui-util.h" } -#include -#include +#include +#include +#include namespace gnc { @@ -197,4 +198,6 @@ inline bool operator!=(const Numeric& a, const Numeric& b) } // END namespace gnc +Q_DECLARE_METATYPE(gnc::Numeric) + #endif diff --git a/src/gnc/Split.cpp b/src/gnc/Split.cpp index 77a005838b..26494af042 100644 --- a/src/gnc/Split.cpp +++ b/src/gnc/Split.cpp @@ -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); } diff --git a/src/gnc/Split.hpp b/src/gnc/Split.hpp index a778f6c939..2f264fceab 100644 --- a/src/gnc/Split.hpp +++ b/src/gnc/Split.hpp @@ -31,8 +31,9 @@ extern "C" #include "engine/Split.h" } -#include -#include +#include +#include +#include #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 diff --git a/src/gnc/SplitListModel.cpp b/src/gnc/SplitListModel.cpp index dce8b06445..892c034bd8 100644 --- a/src/gnc/SplitListModel.cpp +++ b/src/gnc/SplitListModel.cpp @@ -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()) + { + if (trans.countSplits() == 2) + { + Split other = split.getOtherSplit(); + cmd = cmd::setSplitAccount(other, value.value()); + } + 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()); diff --git a/src/gnc/SplitListModel.hpp b/src/gnc/SplitListModel.hpp index d946454456..77baffc8d6 100644 --- a/src/gnc/SplitListModel.hpp +++ b/src/gnc/SplitListModel.hpp @@ -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; diff --git a/src/gnc/SplitListView.cpp b/src/gnc/SplitListView.cpp index 4db0747661..8c2087fa1b 100644 --- a/src/gnc/SplitListView.cpp +++ b/src/gnc/SplitListView.cpp @@ -24,6 +24,7 @@ #include "gnc/Account.hpp" #include "gnc/SplitListModel.hpp" +#include "gnc/AccountSelectionDelegate.hpp" #include #include @@ -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)); diff --git a/src/gnc/Transaction.hpp b/src/gnc/Transaction.hpp index ad9c5ddf60..bb212610ee 100644 --- a/src/gnc/Transaction.hpp +++ b/src/gnc/Transaction.hpp @@ -37,8 +37,9 @@ extern "C" #include "gnc/Numeric.hpp" #include "gnc/GncInstance.hpp" -#include -#include +#include +#include +#include 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 diff --git a/src/gnc/mainwindow-file.cpp b/src/gnc/mainwindow-file.cpp index 026c82922c..a79690bf1a 100644 --- a/src/gnc/mainwindow-file.cpp +++ b/src/gnc/mainwindow-file.cpp @@ -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)