changeset 2641:8c808b8dcf02 draft

Process address book updates incrementally - No longer invalidates selection model, thus retains selection on address book changes - Fixes selection of new address when added
author Wladimir J. van der Laan <laanwj@gmail.com>
date Sun, 06 May 2012 22:41:35 +0200
parents 60b19c042950
children 8bc3131c2449
files src/qt/addressbookpage.cpp src/qt/addressbookpage.h src/qt/addresstablemodel.cpp src/qt/addresstablemodel.h src/qt/walletmodel.cpp src/qt/walletmodel.h src/wallet.cpp src/wallet.h
diffstat 8 files changed, 113 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/src/qt/addressbookpage.cpp
+++ b/src/qt/addressbookpage.cpp
@@ -132,6 +132,10 @@
     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 @@
     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 @@
         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();
+    }
+}
--- a/src/qt/addressbookpage.h
+++ b/src/qt/addressbookpage.h
@@ -13,6 +13,7 @@
 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 @@
     QSortFilterProxyModel *proxyModel;
     QMenu *contextMenu;
     QAction *deleteAction;
+    QString newAddressToSelect;
 
 private slots:
     void on_deleteButton_clicked();
@@ -67,6 +69,9 @@
     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
--- a/src/qt/addresstablemodel.cpp
+++ b/src/qt/addresstablemodel.cpp
@@ -26,20 +26,36 @@
         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<AddressTableEntry> 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 @@
         }
     }
 
+    void updateEntry(const QString &address, const QString &label, bool isMine, int status)
+    {
+        // Find address / label in model
+        QList<AddressTableEntry>::iterator lower = qLowerBound(
+            cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
+        QList<AddressTableEntry>::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 @@
     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 @@
         {
         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 @@
                     // 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 @@
     }
 }
 
-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 @@
     }
 }
 
+void AddressTableModel::emitDataChanged(int idx)
+{
+    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
+}
--- a/src/qt/addresstablemodel.h
+++ b/src/qt/addresstablemodel.h
@@ -74,13 +74,18 @@
     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
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -75,10 +75,10 @@
     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 @@
     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 @@
 {
     // 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 @@
 {
     // 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));
 }
 
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -145,7 +145,7 @@
     /* 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);
 };
 
 
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -1289,7 +1289,7 @@
 {
     std::map<CBitcoinAddress, std::string>::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::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());
--- a/src/wallet.h
+++ b/src/wallet.h
@@ -266,7 +266,7 @@
     /** Address book entry changed.
      * @note called with lock cs_wallet held.
      */
-    boost::signals2::signal<void (CWallet *wallet, const std::string &address, const std::string &label, ChangeType status)> NotifyAddressBookChanged;
+    boost::signals2::signal<void (CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged;
 
     /** Wallet transaction added, removed or updated.
      * @note called with lock cs_wallet held.