Bitcoin ABC 0.30.5
P2P Digital Currency
addresstablemodel.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2016 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
7#include <cashaddrenc.h>
8#include <key_io.h>
9#include <qt/guiutil.h>
10#include <qt/walletmodel.h>
11#include <wallet/wallet.h>
12
13#include <algorithm>
14#include <variant>
15
16#include <QDebug>
17#include <QFont>
18
19const QString AddressTableModel::Send = "S";
20const QString AddressTableModel::Receive = "R";
21
23 enum Type {
26 /* QSortFilterProxyModel will filter these out */
27 Hidden
28 };
29
31 QString label;
32 QString address;
33
35 AddressTableEntry(Type _type, const QString &_label,
36 const QString &_address)
37 : type(_type), label(_label), address(_address) {}
38};
39
42 const AddressTableEntry &b) const {
43 return a.address < b.address;
44 }
45 bool operator()(const AddressTableEntry &a, const QString &b) const {
46 return a.address < b;
47 }
48 bool operator()(const QString &a, const AddressTableEntry &b) const {
49 return a < b.address;
50 }
51};
52
53/* Determine address type from address purpose */
55translateTransactionType(const QString &strPurpose, bool isMine) {
57 // "refund" addresses aren't shown, and change addresses aren't returned by
58 // getAddresses at all.
59 if (strPurpose == "send") {
60 addressType = AddressTableEntry::Sending;
61 } else if (strPurpose == "receive") {
62 addressType = AddressTableEntry::Receiving;
63 } else if (strPurpose == "unknown" || strPurpose == "") {
64 // if purpose not set, guess
65 addressType = (isMine ? AddressTableEntry::Receiving
67 }
68 return addressType;
69}
70
71// Private implementation
73public:
74 QList<AddressTableEntry> cachedAddressTable;
76
77 explicit AddressTablePriv(AddressTableModel *_parent) : parent(_parent) {}
78
80 cachedAddressTable.clear();
81 for (const auto &address : wallet.getAddresses()) {
83 QString::fromStdString(address.purpose), address.is_mine);
85 addressType, QString::fromStdString(address.name),
86 QString::fromStdString(EncodeCashAddr(
87 address.dest, parent->walletModel->getChainParams()))));
88 }
89 // std::lower_bound() and std::upper_bound() require our
90 // cachedAddressTable list to be sorted in asc order Even though the map
91 // is already sorted this re-sorting step is needed because the
92 // originating map is sorted by binary address, not by base58() address.
93 std::sort(cachedAddressTable.begin(), cachedAddressTable.end(),
95 }
96
97 void updateEntry(const QString &address, const QString &label, bool isMine,
98 const QString &purpose, int status) {
99 // Find address / label in model
100 QList<AddressTableEntry>::iterator lower = std::lower_bound(
101 cachedAddressTable.begin(), cachedAddressTable.end(), address,
103 QList<AddressTableEntry>::iterator upper = std::upper_bound(
104 cachedAddressTable.begin(), cachedAddressTable.end(), address,
106 int lowerIndex = (lower - cachedAddressTable.begin());
107 int upperIndex = (upper - cachedAddressTable.begin());
108 bool inModel = (lower != upper);
109 AddressTableEntry::Type newEntryType =
110 translateTransactionType(purpose, isMine);
111
112 switch (status) {
113 case CT_NEW:
114 if (inModel) {
115 qWarning() << "AddressTablePriv::updateEntry: Warning: Got "
116 "CT_NEW, but entry is already in model";
117 break;
118 }
119 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
120 cachedAddressTable.insert(
121 lowerIndex,
122 AddressTableEntry(newEntryType, label, address));
123 parent->endInsertRows();
124 break;
125 case CT_UPDATED:
126 if (!inModel) {
127 qWarning() << "AddressTablePriv::updateEntry: Warning: Got "
128 "CT_UPDATED, but entry is not in model";
129 break;
130 }
131 lower->type = newEntryType;
132 lower->label = label;
133 parent->emitDataChanged(lowerIndex);
134 break;
135 case CT_DELETED:
136 if (!inModel) {
137 qWarning() << "AddressTablePriv::updateEntry: Warning: Got "
138 "CT_DELETED, but entry is not in model";
139 break;
140 }
141 parent->beginRemoveRows(QModelIndex(), lowerIndex,
142 upperIndex - 1);
143 cachedAddressTable.erase(lower, upper);
144 parent->endRemoveRows();
145 break;
146 }
147 }
148
149 int size() { return cachedAddressTable.size(); }
150
152 if (idx >= 0 && idx < cachedAddressTable.size()) {
153 return &cachedAddressTable[idx];
154 } else {
155 return nullptr;
156 }
157 }
158};
159
161 : QAbstractTableModel(parent), walletModel(parent) {
162 columns << tr("Label") << tr("Address");
163 priv = new AddressTablePriv(this);
164 priv->refreshAddressTable(parent->wallet());
165}
166
168 delete priv;
169}
170
171int AddressTableModel::rowCount(const QModelIndex &parent) const {
172 Q_UNUSED(parent);
173 return priv->size();
174}
175
176int AddressTableModel::columnCount(const QModelIndex &parent) const {
177 Q_UNUSED(parent);
178 return columns.length();
179}
180
181QVariant AddressTableModel::data(const QModelIndex &index, int role) const {
182 if (!index.isValid()) {
183 return QVariant();
184 }
185
186 AddressTableEntry *rec =
187 static_cast<AddressTableEntry *>(index.internalPointer());
188
189 if (role == Qt::DisplayRole || role == Qt::EditRole) {
190 switch (index.column()) {
191 case Label:
192 if (rec->label.isEmpty() && role == Qt::DisplayRole) {
193 return tr("(no label)");
194 } else {
195 return rec->label;
196 }
197 case Address:
198 return rec->address;
199 }
200 } else if (role == Qt::FontRole) {
201 QFont font;
202 if (index.column() == Address) {
204 }
205 return font;
206 } else if (role == TypeRole) {
207 switch (rec->type) {
209 return Send;
211 return Receive;
212 default:
213 break;
214 }
215 }
216 return QVariant();
217}
218
219bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
220 int role) {
221 if (!index.isValid()) {
222 return false;
223 }
224 AddressTableEntry *rec =
225 static_cast<AddressTableEntry *>(index.internalPointer());
226 std::string strPurpose =
227 (rec->type == AddressTableEntry::Sending ? "send" : "receive");
228 editStatus = OK;
229
230 if (role == Qt::EditRole) {
232 rec->address.toStdString(), walletModel->getChainParams());
233 if (index.column() == Label) {
234 // Do nothing, if old label == new label
235 if (rec->label == value.toString()) {
237 return false;
238 }
240 curAddress, value.toString().toStdString(), strPurpose);
241 } else if (index.column() == Address) {
243 value.toString().toStdString(), walletModel->getChainParams());
244 // Refuse to set invalid address, set error status and return false
245 if (std::get_if<CNoDestination>(&newAddress)) {
247 return false;
248 }
249 // Do nothing, if old address == new address
250 else if (newAddress == curAddress) {
252 return false;
253 }
254 // Check for duplicate addresses to prevent accidental deletion of
255 // addresses, if you try to paste an existing address over another
256 // address (with a different label)
258 newAddress, /* name= */ nullptr, /* is_mine= */ nullptr,
259 /* purpose= */ nullptr)) {
261 return false;
262 }
263 // Double-check that we're not overwriting a receiving address
264 else if (rec->type == AddressTableEntry::Sending) {
265 // Remove old entry
266 walletModel->wallet().delAddressBook(curAddress);
267 // Add new entry with new address
269 newAddress, value.toString().toStdString(), strPurpose);
270 }
271 }
272 return true;
273 }
274 return false;
275}
276
277QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation,
278 int role) const {
279 if (orientation == Qt::Horizontal) {
280 if (role == Qt::DisplayRole && section < columns.size()) {
281 return columns[section];
282 }
283 }
284 return QVariant();
285}
286
287Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const {
288 if (!index.isValid()) {
289 return Qt::NoItemFlags;
290 }
291 AddressTableEntry *rec =
292 static_cast<AddressTableEntry *>(index.internalPointer());
293
294 Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
295 // Can edit address and label for sending addresses, and only label for
296 // receiving addresses.
297 if (rec->type == AddressTableEntry::Sending ||
299 index.column() == Label)) {
300 retval |= Qt::ItemIsEditable;
301 }
302 return retval;
303}
304
305QModelIndex AddressTableModel::index(int row, int column,
306 const QModelIndex &parent) const {
307 Q_UNUSED(parent);
309 if (data) {
310 return createIndex(row, column, priv->index(row));
311 } else {
312 return QModelIndex();
313 }
314}
315
316void AddressTableModel::updateEntry(const QString &address,
317 const QString &label, bool isMine,
318 const QString &purpose, int status) {
319 // Update address book model from Bitcoin core
320 priv->updateEntry(address, label, isMine, purpose, status);
321}
322
323QString AddressTableModel::addRow(const QString &type, const QString &label,
324 const QString &address,
325 const OutputType address_type) {
326 std::string strLabel = label.toStdString();
327 std::string strAddress = address.toStdString();
328
329 editStatus = OK;
330
331 if (type == Send) {
332 if (!walletModel->validateAddress(address)) {
334 return QString();
335 }
336 // Check for duplicate addresses
339 /* name= */ nullptr, /* is_mine= */ nullptr,
340 /* purpose= */ nullptr)) {
342 return QString();
343 }
344 // Add entry
347 strLabel, "send");
348 } else if (type == Receive) {
349 // Generate a new address to associate with given label
350 CTxDestination dest;
351 if (!walletModel->wallet().getNewDestination(address_type, strLabel,
352 dest)) {
354 if (!ctx.isValid()) {
355 // Unlock wallet failed or was cancelled
357 return QString();
358 }
359 if (!walletModel->wallet().getNewDestination(address_type, strLabel,
360 dest)) {
362 return QString();
363 }
364 }
365 strAddress = EncodeCashAddr(dest, walletModel->getChainParams());
366 } else {
367 return QString();
368 }
369 return QString::fromStdString(strAddress);
370}
371
373 const QModelIndex &parent) {
374 Q_UNUSED(parent);
375 AddressTableEntry *rec = priv->index(row);
376 if (count != 1 || !rec || rec->type == AddressTableEntry::Receiving) {
377 // Can only remove one row at a time, and cannot remove rows not in
378 // model.
379 // Also refuse to remove receiving addresses.
380 return false;
381 }
383 rec->address.toStdString(), walletModel->getChainParams()));
384 return true;
385}
386
387QString AddressTableModel::labelForAddress(const QString &address) const {
388 std::string name;
389 if (getAddressData(address, &name, /* purpose= */ nullptr)) {
390 return QString::fromStdString(name);
391 }
392 return QString();
393}
394
395QString AddressTableModel::purposeForAddress(const QString &address) const {
396 std::string purpose;
397 if (getAddressData(address, /* name= */ nullptr, &purpose)) {
398 return QString::fromStdString(purpose);
399 }
400 return QString();
401}
402
403bool AddressTableModel::getAddressData(const QString &address,
404 std::string *name,
405 std::string *purpose) const {
406 CTxDestination destination =
407 DecodeDestination(address.toStdString(), walletModel->getChainParams());
408 return walletModel->wallet().getAddress(destination, name,
409 /* is_mine= */ nullptr, purpose);
410}
411
412int AddressTableModel::lookupAddress(const QString &address) const {
413 QModelIndexList lst = match(index(0, Address, QModelIndex()), Qt::EditRole,
414 address, 1, Qt::MatchExactly);
415 if (lst.isEmpty()) {
416 return -1;
417 } else {
418 return lst.at(0).row();
419 }
420}
421
424};
425
427 Q_EMIT dataChanged(index(idx, 0, QModelIndex()),
428 index(idx, columns.length() - 1, QModelIndex()));
429}
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
secp256k1_context * ctx
std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams &params)
Definition: cashaddrenc.cpp:90
Qt model of the address book in the core.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
@ TypeRole
Type of address (Send or Receive)
int lookupAddress(const QString &address) const
AddressTablePriv * priv
OutputType GetDefaultAddressType() const
@ WALLET_UNLOCK_FAILURE
Wallet could not be unlocked to create new receiving address.
@ NO_CHANGES
No changes were made during edit operation.
@ INVALID_ADDRESS
Unparseable address.
@ KEY_GENERATION_FAILURE
Generating a new public key for a receiving address failed.
@ OK
Everything ok.
@ DUPLICATE_ADDRESS
Address already in address book.
void emitDataChanged(int index)
Notify listeners that data changed.
@ Address
Bitcoin address.
@ Label
User specified label.
QVariant data(const QModelIndex &index, int role) const override
QModelIndex index(int row, int column, const QModelIndex &parent) const override
int columnCount(const QModelIndex &parent) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
static const QString Send
Specifies send address.
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
friend class AddressTablePriv
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
static const QString Receive
Specifies receive address.
int rowCount(const QModelIndex &parent) const override
WalletModel *const walletModel
AddressTableModel(WalletModel *parent=nullptr)
QString purposeForAddress(const QString &address) const
Look up purpose for address in address book, if not found return empty string.
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
bool getAddressData(const QString &address, std::string *name, std::string *purpose) const
Look up address book data given an address string.
QList< AddressTableEntry > cachedAddressTable
AddressTablePriv(AddressTableModel *_parent)
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
AddressTableEntry * index(int idx)
void refreshAddressTable(interfaces::Wallet &wallet)
AddressTableModel * parent
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:47
bool validateAddress(const QString &address)
const CChainParams & getChainParams() const
interfaces::Wallet & wallet() const
Definition: walletmodel.h:150
UnlockContext requestUnlock()
Interface for accessing a wallet.
Definition: wallet.h:59
virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination &dest)=0
virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose)=0
Add or update address.
virtual OutputType getDefaultAddressType()=0
virtual bool delAddressBook(const CTxDestination &dest)=0
virtual bool getAddress(const CTxDestination &dest, std::string *name, isminetype *is_mine, std::string *purpose)=0
Look up address in wallet, return whether exists.
CTxDestination DecodeDestination(const std::string &addr, const CChainParams &params)
Definition: key_io.cpp:174
QFont fixedPitchFont()
Definition: guiutil.cpp:87
OutputType
Definition: outputtype.h:16
const char * name
Definition: rest.cpp:47
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:85
Type type
QString label
AddressTableEntry()
AddressTableEntry(Type _type, const QString &_label, const QString &_address)
QString address
Type
@ Hidden
@ Sending
@ Receiving
bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
bool operator()(const QString &a, const AddressTableEntry &b) const
bool operator()(const AddressTableEntry &a, const QString &b) const
static int count
Definition: tests.c:31
@ CT_UPDATED
Definition: ui_change_type.h:9
@ CT_DELETED
Definition: ui_change_type.h:9
@ CT_NEW
Definition: ui_change_type.h:9