Enable editing of the "Description" column in the split list view - WITH UNDO!

The Qt Undo framework is almost like magic. We just have to create a
command object instead of directly manipulating the value, and suddenly
the undo/redo just works. This is fun!

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18889 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2010-03-10 21:40:58 +00:00
parent 0a2d901f71
commit 81ec044f82
9 changed files with 174 additions and 34 deletions

View File

@ -12,6 +12,7 @@ LINK_DIRECTORIES (${GLIB2_LIBRARY_DIRS}
SET (gnc_SOURCES
AccountItemModel.cpp
Book.cpp
Cmd.cpp
Numeric.cpp
RecentFileMenu.cpp
Session.cpp
@ -30,6 +31,7 @@ SET (gnc_QOBJECT_HEADERS
SET (gnc_HEADERS ${gnc_QOBJECT_HEADERS}
Account.hpp
Book.hpp
Cmd.hpp
Session.hpp
Split.hpp
Transaction.hpp

91
src/gnc/Cmd.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
* Cmd.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
*/
#include "Cmd.hpp"
#include "gnc/Split.hpp"
#include "gnc/Account.hpp"
#include "gnc/Transaction.hpp"
#include <QObject>
namespace gnc
{
// Explicit instantiations to check for compiler errors
template class Cmd<Account, QString>;
namespace cmd
{
QUndoCommand* setSplitMemo(Split& t, const QString& newValue)
{
return new Cmd<Split, QString>(QObject::tr("Edit Split Memo"),
t, &Split::setMemo,
&Split::getMemo, newValue);
}
QUndoCommand* setSplitAction(Split& t, const QString& newValue)
{
return new Cmd<Split, QString>(QObject::tr("Edit Split Action"),
t, &Split::setAction,
&Split::getAction, newValue);
}
QUndoCommand* setSplitReconcile(Split& t, char newValue)
{
return new Cmd<Split, char>(QObject::tr("Edit Split Reconcile"),
t, &Split::setReconcile,
&Split::getReconcile, newValue);
}
QUndoCommand* setTransactionNum(Transaction& t, const QString& newValue)
{
return new Cmd<Transaction, QString>(QObject::tr("Edit Transaction Number"),
t, &Transaction::setNum,
&Transaction::getNum, newValue);
}
QUndoCommand* setTransactionDescription(Transaction& t, const QString& newValue)
{
return new Cmd<Transaction, QString>(QObject::tr("Edit Transaction Description"),
t, &Transaction::setDescription,
&Transaction::getDescription, newValue);
}
QUndoCommand* setTransactionNotes(Transaction& t, const QString& newValue)
{
return new Cmd<Transaction, QString>(QObject::tr("Edit Transaction Notes"),
t, &Transaction::setNotes,
&Transaction::getNotes, newValue);
}
QUndoCommand* setTransactionDate(Transaction& t, const QDateTime& newValue)
{
return new Cmd<Transaction, QDateTime>(QObject::tr("Edit Transaction Date"),
t, &Transaction::setDatePosted,
&Transaction::getDatePosted, newValue);
}
} // END namespace cmd
} // END namespace gnc

View File

@ -24,11 +24,16 @@
#define GNC_CMD_HPP
#include <QUndoCommand>
#include <gnc/Split.hpp>
#include <gnc/WeakPointer.hpp>
#include <gnc/Numeric.hpp>
namespace gnc
{
class Split;
class Account;
class Transaction;
template<class TargetT, class ValueT>
class Cmd : public QUndoCommand
{
@ -38,26 +43,27 @@ public:
typedef void (TargetT::*setter_func)(const value_type&);
typedef value_type (TargetT::*getter_func)() const;
Cmd(const QString& text,
TargetT& target, setter_func setter,
const value_type& previousValue,
const value_type& newValue,
QUndoCommand *parent = 0)
: QUndoCommand(text, parent)
, m_target(target)
, m_setter(setter)
, m_previousValue(previousValue)
, m_newValue(newValue)
{
}
// Cmd(const QString& text,
// TargetT& target, setter_func setter,
// const value_type& previousValue,
// const value_type& newValue,
// QUndoCommand *parent = 0)
// : QUndoCommand(text, parent)
// , m_target(target)
// , m_setter(setter)
// , m_previousValue(previousValue)
// , m_newValue(newValue)
// {
// }
Cmd(const QString& text,
TargetT& target, setter_func setter,
WeakPointer<typename TargetT::element_type>& targetPtr,
setter_func setter,
getter_func getter,
const value_type& newValue,
QUndoCommand *parent = 0)
: QUndoCommand(text, parent)
, m_target(target)
, m_target(targetPtr.get())
, m_setter(setter)
, m_previousValue((m_target.*getter)())
, m_newValue(newValue)
@ -77,7 +83,7 @@ public:
protected:
TargetT& m_target;
TargetT m_target;
setter_func m_setter;
value_type m_previousValue;
value_type m_newValue;
@ -86,12 +92,18 @@ protected:
namespace cmd
{
QUndoCommand* setSplitMemo(Split& split, const QString& newValue)
{
return new Cmd<Split, QString>(QWidget::tr("Edit Split Memo"),
split, &Split::setMemo,
&Split::getMemo, newValue);
}
// This is the collection of command objects which are already
// provided for the different data types and their simple
// members. Just create one of those, add it to a QUndoStack, and
// magically the values will change with undo/redo back and
// forth. Spooky, IMHO.
QUndoCommand* setSplitMemo(Split& split, const QString& newValue);
QUndoCommand* setSplitAction(Split& t, const QString& newValue);
QUndoCommand* setSplitReconcile(Split& t, char newValue);
QUndoCommand* setTransactionNum(Transaction& t, const QString& newValue);
QUndoCommand* setTransactionDescription(Transaction& t, const QString& newValue);
QUndoCommand* setTransactionNotes(Transaction& t, const QString& newValue);
QUndoCommand* setTransactionDate(Transaction& t, const QDateTime& newValue);
} // END namespace cmd

View File

@ -80,7 +80,7 @@ void RecentFileMenu::updateMenu()
const QString& qs = m_fileNames.at(i);
QAction *act = m_actionRecentFile[i];
act->setVisible(true);
act->setText(tr("&%1 %2").arg(i+1).arg(QFileInfo(qs).fileName()));
act->setText(tr("&%1 %2").arg(i + 1).arg(QFileInfo(qs).fileName()));
act->setStatusTip(qs);
act->setData(qs);
}

View File

@ -68,7 +68,7 @@ public:
void setAction(const QString& v) { xaccSplitSetAction(get(), v.toUtf8()); }
char getReconcile() const { return xaccSplitGetReconcile(get()); }
void setReconcile(char v) { xaccSplitSetReconcile(get(), v); }
void setReconcile(const char& v) { xaccSplitSetReconcile(get(), v); }
Split getOtherSplit() const { return xaccSplitGetOtherSplit(get()); }

View File

@ -22,14 +22,17 @@
#include "SplitListModel.hpp"
#include "gnc/Transaction.hpp"
#include "gnc/Cmd.hpp"
#include <QDebug>
#include <QUndoStack>
namespace gnc
{
SplitListModel::SplitListModel(const SplitQList splits, QObject *parent)
SplitListModel::SplitListModel(const SplitQList splits, QUndoStack* undoStack, QObject *parent)
: QAbstractItemModel(parent)
, m_list(splits)
, m_undoStack(undoStack)
{
}
@ -100,8 +103,16 @@ Qt::ItemFlags SplitListModel::flags(const QModelIndex &index) const
if (!index.isValid())
return 0;
Qt::ItemFlags result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
switch (index.column())
{
case 2:
// Allow write access as well
return result | Qt::ItemIsEditable;
default:
// Ensure read-only access only
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return result;
}
}
QVariant SplitListModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -133,5 +144,28 @@ QVariant SplitListModel::headerData(int section, Qt::Orientation orientation, in
return QString("%1").arg(1 + section);
}
bool SplitListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole)
{
Split split(static_cast< ::Split*>(index.internalPointer()));
Transaction trans(split.getParent());
switch (index.column())
{
case 2:
// We allow to edit column 2. "Editing" is done by
// creating a Cmd-object and adding it to the undo
// stack. That's in fact all that was needed to create an
// *undoable* edit of this data cell. Seems almost spooky,
// doesn't it?
m_undoStack->push(cmd::setTransactionDescription(trans, value.toString()));
emit dataChanged(index, index);
return true;
default:
return false;
}
}
return false;
}
} // END namespace gnc

View File

@ -26,6 +26,7 @@
#include "gnc/Split.hpp"
#include <QAbstractItemModel>
class QUndoStack;
namespace gnc
{
@ -36,7 +37,7 @@ class SplitListModel : public QAbstractItemModel
{
Q_OBJECT
public:
SplitListModel(const SplitQList splits, QObject *parent = 0);
SplitListModel(const SplitQList splits, QUndoStack* undoStack, QObject *parent = 0);
QModelIndex parent(const QModelIndex &index) const { return QModelIndex(); }
int rowCount(const QModelIndex& parent = QModelIndex()) const { return m_list.size(); }
@ -47,9 +48,12 @@ public:
QVariant data(const QModelIndex& index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
protected:
SplitQList m_list;
QUndoStack* m_undoStack;
};
} // END namespace gnc

View File

@ -61,8 +61,8 @@ public:
int countSplits() const { return xaccTransCountSplits(get()); }
void setDatePosted(const QDateTime& t);
void setDateEntered(const QDateTime& t);
void setDatePosted(const QDateTime& t) { xaccTransSetDatePostedSecs(get(), t.toTime_t()); }
void setDateEntered(const QDateTime& t) { xaccTransSetDateEnteredSecs(get(), t.toTime_t()); }
QDateTime getDatePosted() const { return toQDateTime(xaccTransRetDatePostedTS(get())); }
QDateTime getDateEntered() const { return toQDateTime(xaccTransRetDateEnteredTS(get())); }

View File

@ -57,9 +57,6 @@ extern "C"
namespace gnc
{
// Explicit instantiations to check for compiler errors
template class Cmd<Account, QString>;
inline QString errorToString(QofBackendError err)
{
return errorToStringPair(err).first;
@ -420,7 +417,7 @@ void MainWindow::accountItemActivated(const QModelIndex & index)
// We create a new model for this list of splits and also a view
// widget for this list.
QTableView *tableView = new QTableView(ui->tabWidget); // FIXME: Is this parent correct?
SplitListModel *smodel = new SplitListModel(Split::fromGList(slist), tableView);
SplitListModel *smodel = new SplitListModel(Split::fromGList(slist), m_undoStack, tableView);
tableView->setModel(smodel);
tableView->setAlternatingRowColors(true);