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)),
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();
}
}

View file

@ -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

View file

@ -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<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 @@ 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()
{
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()));
}

View file

@ -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

View file

@ -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));
}

View file

@ -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);
};

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);
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());

View file

@ -266,7 +266,7 @@ public:
/** 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.