From 0832c0d1669a3504b7ec21d583aecc79f84e8506 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sun, 6 May 2012 22:41:35 +0200 Subject: [PATCH] Process address book updates incrementally - No longer invalidates selection model, thus retains selection on address book changes - Fixes selection of new address when added --- src/qt/addressbookpage.cpp | 29 +++++++----- src/qt/addressbookpage.h | 5 +++ src/qt/addresstablemodel.cpp | 86 ++++++++++++++++++++++++++++++------ src/qt/addresstablemodel.h | 7 ++- src/qt/walletmodel.cpp | 13 +++--- src/qt/walletmodel.h | 2 +- src/wallet.cpp | 4 +- src/wallet.h | 2 +- 8 files changed, 113 insertions(+), 35 deletions(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d314e62b..c2079875 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -132,6 +132,10 @@ void AddressBookPage::setModel(AddressTableModel *model) connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged())); + // Select row for newly created address + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(selectNewAddress(QModelIndex,int,int))); + if(mode == ForSending) { // Auto-select first row when in sending mode @@ -193,20 +197,11 @@ void AddressBookPage::on_newAddressButton_clicked() EditAddressDialog dlg( tab == SendingTab ? EditAddressDialog::NewSendingAddress : - EditAddressDialog::NewReceivingAddress); + EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if(dlg.exec()) { - // Select row for newly created address - QString address = dlg.getAddress(); - QModelIndexList lst = proxyModel->match(proxyModel->index(0, - AddressTableModel::Address, QModelIndex()), - Qt::EditRole, address, 1, Qt::MatchExactly); - if(!lst.isEmpty()) - { - ui->tableView->setFocus(); - ui->tableView->selectRow(lst.at(0).row()); - } + newAddressToSelect = dlg.getAddress(); } } @@ -338,3 +333,15 @@ void AddressBookPage::contextualMenu(const QPoint &point) contextMenu->exec(QCursor::pos()); } } + +void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int end) +{ + QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); + if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) + { + // Select row of newly created address, once + ui->tableView->setFocus(); + ui->tableView->selectRow(idx.row()); + newAddressToSelect.clear(); + } +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b2cf2db9..b2e91c7c 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -13,6 +13,7 @@ class QTableView; class QItemSelection; class QSortFilterProxyModel; class QMenu; +class QModelIndex; QT_END_NAMESPACE /** Widget that shows a list of sending or receiving addresses. @@ -51,6 +52,7 @@ private: QSortFilterProxyModel *proxyModel; QMenu *contextMenu; QAction *deleteAction; + QString newAddressToSelect; private slots: void on_deleteButton_clicked(); @@ -67,6 +69,9 @@ private slots: void onCopyLabelAction(); /** Edit currently selected address entry */ void onEditAction(); + + /** New entry/entries were added to address table */ + void selectNewAddress(const QModelIndex &parent, int begin, int end); }; #endif // ADDRESSBOOKDIALOG_H diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 4af4c3ac..75ea2c12 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -26,20 +26,36 @@ struct AddressTableEntry type(type), label(label), address(address) {} }; +struct AddressTableEntryLessThan +{ + bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const + { + return a.address < b.address; + } + bool operator()(const AddressTableEntry &a, const QString &b) const + { + return a.address < b; + } + bool operator()(const QString &a, const AddressTableEntry &b) const + { + return a < b.address; + } +}; + // Private implementation class AddressTablePriv { public: CWallet *wallet; QList cachedAddressTable; + AddressTableModel *parent; - AddressTablePriv(CWallet *wallet): - wallet(wallet) {} + AddressTablePriv(CWallet *wallet, AddressTableModel *parent): + wallet(wallet), parent(parent) {} void refreshAddressTable() { cachedAddressTable.clear(); - { LOCK(wallet->cs_wallet); BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook) @@ -54,6 +70,53 @@ public: } } + void updateEntry(const QString &address, const QString &label, bool isMine, int status) + { + // Find address / label in model + QList::iterator lower = qLowerBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + QList::iterator upper = qUpperBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + int lowerIndex = (lower - cachedAddressTable.begin()); + int upperIndex = (upper - cachedAddressTable.begin()); + bool inModel = (lower != upper); + AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; + + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); + break; + } + parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); + cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address)); + parent->endInsertRows(); + break; + case CT_UPDATED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); + break; + } + lower->type = newEntryType; + lower->label = label; + parent->emitDataChanged(lowerIndex); + break; + case CT_DELETED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); + break; + } + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedAddressTable.erase(lower, upper); + parent->endRemoveRows(); + break; + } + } + int size() { return cachedAddressTable.size(); @@ -76,7 +139,7 @@ AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) : QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) { columns << tr("Label") << tr("Address"); - priv = new AddressTablePriv(wallet); + priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } @@ -158,7 +221,6 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu { case Label: wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString()); - rec->label = value.toString(); break; case Address: // Refuse to set invalid address, set error status and return false @@ -177,12 +239,9 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu // Add new entry with new address wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString()); } - - rec->address = value.toString(); } break; } - emit dataChanged(index, index); return true; } @@ -232,13 +291,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & pa } } -void AddressTableModel::updateEntry(const QString &address, const QString &label, int status) +void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status) { // Update address book model from Bitcoin core - // TODO: use address, label, status to update only the specified entry (like in WalletModel) - beginResetModel(); - priv->refreshAddressTable(); - endResetModel(); + priv->updateEntry(address, label, isMine, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) @@ -342,3 +398,7 @@ int AddressTableModel::lookupAddress(const QString &address) const } } +void AddressTableModel::emitDataChanged(int idx) +{ + emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); +} diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index bd73c6d1..42974e3e 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -74,13 +74,18 @@ private: QStringList columns; EditStatus editStatus; + /** Notify listeners that data changed. */ + void emitDataChanged(int index); + signals: void defaultAddressChanged(const QString &address); public slots: /* Update address list from core. */ - void updateEntry(const QString &address, const QString &label, int status); + void updateEntry(const QString &address, const QString &label, bool isMine, int status); + + friend class AddressTablePriv; }; #endif // ADDRESSTABLEMODEL_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 1a9700ef..b89c3dba 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -75,10 +75,10 @@ void WalletModel::updateTransaction(const QString &hash, int status) cachedNumTransactions = newNumTransactions; } -void WalletModel::updateAddressBook(const QString &address, const QString &label, int status) +void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) { if(addressTableModel) - addressTableModel->updateEntry(address, label, status); + addressTableModel->updateEntry(address, label, isMine, status); } bool WalletModel::validateAddress(const QString &address) @@ -268,12 +268,13 @@ static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStor QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } -static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const std::string &address, const std::string &label, ChangeType status) +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status) { - OutputDebugStringF("NotifyAddressBookChanged %s %s status=%i\n", address.c_str(), label.c_str(), status); + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", address.c_str(), label.c_str(), isMine, status); QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(address)), Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), Q_ARG(int, status)); } @@ -289,7 +290,7 @@ void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); } @@ -297,7 +298,7 @@ void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index c413ed24..8b615ffe 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -145,7 +145,7 @@ public slots: /* New transaction, or transaction changed status */ void updateTransaction(const QString &hash, int status); /* New, updated or removed address book entry */ - void updateAddressBook(const QString &address, const QString &label, int status); + void updateAddressBook(const QString &address, const QString &label, bool isMine, int status); }; diff --git a/src/wallet.cpp b/src/wallet.cpp index 92ad8c0f..d67cdf4b 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1289,7 +1289,7 @@ bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& s { std::map::iterator mi = mapAddressBook.find(address); mapAddressBook[address] = strName; - NotifyAddressBookChanged(this, address.ToString(), strName, (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); + NotifyAddressBookChanged(this, address.ToString(), strName, HaveKey(address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); @@ -1298,7 +1298,7 @@ bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& s bool CWallet::DelAddressBookName(const CBitcoinAddress& address) { mapAddressBook.erase(address); - NotifyAddressBookChanged(this, address.ToString(), "", CT_DELETED); + NotifyAddressBookChanged(this, address.ToString(), "", HaveKey(address), CT_DELETED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).EraseName(address.ToString()); diff --git a/src/wallet.h b/src/wallet.h index b3b2e4f4..55deffdd 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -266,7 +266,7 @@ public: /** Address book entry changed. * @note called with lock cs_wallet held. */ - boost::signals2::signal NotifyAddressBookChanged; + boost::signals2::signal NotifyAddressBookChanged; /** Wallet transaction added, removed or updated. * @note called with lock cs_wallet held.