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:
parent
ab1b288fa7
commit
0832c0d166
8 changed files with 113 additions and 35 deletions
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue