Process address book updates incrementally

- No longer invalidates selection model, thus retains selection on address book changes
- Fixes selection of new address when added
This commit is contained in:
Wladimir J. van der Laan 2012-05-06 22:41:35 +02:00
parent ab1b288fa7
commit 0832c0d166
8 changed files with 113 additions and 35 deletions

View file

@ -132,6 +132,10 @@ void AddressBookPage::setModel(AddressTableModel *model)
connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(selectionChanged())); 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) if(mode == ForSending)
{ {
// Auto-select first row when in sending mode // Auto-select first row when in sending mode
@ -193,20 +197,11 @@ void AddressBookPage::on_newAddressButton_clicked()
EditAddressDialog dlg( EditAddressDialog dlg(
tab == SendingTab ? tab == SendingTab ?
EditAddressDialog::NewSendingAddress : EditAddressDialog::NewSendingAddress :
EditAddressDialog::NewReceivingAddress); EditAddressDialog::NewReceivingAddress, this);
dlg.setModel(model); dlg.setModel(model);
if(dlg.exec()) if(dlg.exec())
{ {
// Select row for newly created address newAddressToSelect = dlg.getAddress();
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());
}
} }
} }
@ -338,3 +333,15 @@ void AddressBookPage::contextualMenu(const QPoint &point)
contextMenu->exec(QCursor::pos()); 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();
}
}

View file

@ -13,6 +13,7 @@ class QTableView;
class QItemSelection; class QItemSelection;
class QSortFilterProxyModel; class QSortFilterProxyModel;
class QMenu; class QMenu;
class QModelIndex;
QT_END_NAMESPACE QT_END_NAMESPACE
/** Widget that shows a list of sending or receiving addresses. /** Widget that shows a list of sending or receiving addresses.
@ -51,6 +52,7 @@ private:
QSortFilterProxyModel *proxyModel; QSortFilterProxyModel *proxyModel;
QMenu *contextMenu; QMenu *contextMenu;
QAction *deleteAction; QAction *deleteAction;
QString newAddressToSelect;
private slots: private slots:
void on_deleteButton_clicked(); void on_deleteButton_clicked();
@ -67,6 +69,9 @@ private slots:
void onCopyLabelAction(); void onCopyLabelAction();
/** Edit currently selected address entry */ /** Edit currently selected address entry */
void onEditAction(); void onEditAction();
/** New entry/entries were added to address table */
void selectNewAddress(const QModelIndex &parent, int begin, int end);
}; };
#endif // ADDRESSBOOKDIALOG_H #endif // ADDRESSBOOKDIALOG_H

View file

@ -26,20 +26,36 @@ struct AddressTableEntry
type(type), label(label), address(address) {} 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 // Private implementation
class AddressTablePriv class AddressTablePriv
{ {
public: public:
CWallet *wallet; CWallet *wallet;
QList<AddressTableEntry> cachedAddressTable; QList<AddressTableEntry> cachedAddressTable;
AddressTableModel *parent;
AddressTablePriv(CWallet *wallet): AddressTablePriv(CWallet *wallet, AddressTableModel *parent):
wallet(wallet) {} wallet(wallet), parent(parent) {}
void refreshAddressTable() void refreshAddressTable()
{ {
cachedAddressTable.clear(); cachedAddressTable.clear();
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook) 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<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() int size()
{ {
return cachedAddressTable.size(); return cachedAddressTable.size();
@ -76,7 +139,7 @@ AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) :
QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0)
{ {
columns << tr("Label") << tr("Address"); columns << tr("Label") << tr("Address");
priv = new AddressTablePriv(wallet); priv = new AddressTablePriv(wallet, this);
priv->refreshAddressTable(); priv->refreshAddressTable();
} }
@ -158,7 +221,6 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu
{ {
case Label: case Label:
wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString()); wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString());
rec->label = value.toString();
break; break;
case Address: case Address:
// Refuse to set invalid address, set error status and return false // 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 // Add new entry with new address
wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString()); wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString());
} }
rec->address = value.toString();
} }
break; break;
} }
emit dataChanged(index, index);
return true; 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 // Update address book model from Bitcoin core
// TODO: use address, label, status to update only the specified entry (like in WalletModel) priv->updateEntry(address, label, isMine, status);
beginResetModel();
priv->refreshAddressTable();
endResetModel();
} }
QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) 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()));
}

View file

@ -74,13 +74,18 @@ private:
QStringList columns; QStringList columns;
EditStatus editStatus; EditStatus editStatus;
/** Notify listeners that data changed. */
void emitDataChanged(int index);
signals: signals:
void defaultAddressChanged(const QString &address); void defaultAddressChanged(const QString &address);
public slots: public slots:
/* Update address list from core. /* 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 #endif // ADDRESSTABLEMODEL_H

View file

@ -75,10 +75,10 @@ void WalletModel::updateTransaction(const QString &hash, int status)
cachedNumTransactions = newNumTransactions; 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) if(addressTableModel)
addressTableModel->updateEntry(address, label, status); addressTableModel->updateEntry(address, label, isMine, status);
} }
bool WalletModel::validateAddress(const QString &address) bool WalletModel::validateAddress(const QString &address)
@ -268,12 +268,13 @@ static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStor
QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); 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, QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(address)), Q_ARG(QString, QString::fromStdString(address)),
Q_ARG(QString, QString::fromStdString(label)), Q_ARG(QString, QString::fromStdString(label)),
Q_ARG(bool, isMine),
Q_ARG(int, status)); Q_ARG(int, status));
} }
@ -289,7 +290,7 @@ void WalletModel::subscribeToCoreSignals()
{ {
// Connect signals to wallet // Connect signals to wallet
wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); 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)); wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
} }
@ -297,7 +298,7 @@ void WalletModel::unsubscribeFromCoreSignals()
{ {
// Disconnect signals from wallet // Disconnect signals from wallet
wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); 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)); wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
} }

View file

@ -145,7 +145,7 @@ public slots:
/* New transaction, or transaction changed status */ /* New transaction, or transaction changed status */
void updateTransaction(const QString &hash, int status); void updateTransaction(const QString &hash, int status);
/* New, updated or removed address book entry */ /* 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);
}; };

View file

@ -1289,7 +1289,7 @@ bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& s
{ {
std::map<CBitcoinAddress, std::string>::iterator mi = mapAddressBook.find(address); std::map<CBitcoinAddress, std::string>::iterator mi = mapAddressBook.find(address);
mapAddressBook[address] = strName; 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) if (!fFileBacked)
return false; return false;
return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); 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) bool CWallet::DelAddressBookName(const CBitcoinAddress& address)
{ {
mapAddressBook.erase(address); mapAddressBook.erase(address);
NotifyAddressBookChanged(this, address.ToString(), "", CT_DELETED); NotifyAddressBookChanged(this, address.ToString(), "", HaveKey(address), CT_DELETED);
if (!fFileBacked) if (!fFileBacked)
return false; return false;
return CWalletDB(strWalletFile).EraseName(address.ToString()); return CWalletDB(strWalletFile).EraseName(address.ToString());

View file

@ -266,7 +266,7 @@ public:
/** Address book entry changed. /** Address book entry changed.
* @note called with lock cs_wallet held. * @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. /** Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held. * @note called with lock cs_wallet held.