Line data Source code
1 : // Copyright (c) 2011-2020 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 :
5 : #include <qt/addresstablemodel.h>
6 :
7 : #include <qt/guiutil.h>
8 : #include <qt/walletmodel.h>
9 :
10 : #include <key_io.h>
11 : #include <wallet/wallet.h>
12 :
13 : #include <algorithm>
14 : #include <typeinfo>
15 :
16 : #include <QFont>
17 : #include <QDebug>
18 :
19 1 : const QString AddressTableModel::Send = "S";
20 1 : const QString AddressTableModel::Receive = "R";
21 :
22 0 : struct AddressTableEntry
23 : {
24 : enum Type {
25 : Sending,
26 : Receiving,
27 : Hidden /* QSortFilterProxyModel will filter these out */
28 : };
29 :
30 : Type type;
31 : QString label;
32 : QString address;
33 :
34 : AddressTableEntry() {}
35 0 : AddressTableEntry(Type _type, const QString &_label, const QString &_address):
36 0 : type(_type), label(_label), address(_address) {}
37 : };
38 :
39 : struct AddressTableEntryLessThan
40 : {
41 0 : bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
42 : {
43 0 : return a.address < b.address;
44 : }
45 0 : bool operator()(const AddressTableEntry &a, const QString &b) const
46 : {
47 0 : return a.address < b;
48 : }
49 0 : bool operator()(const QString &a, const AddressTableEntry &b) const
50 : {
51 0 : return a < b.address;
52 : }
53 : };
54 :
55 : /* Determine address type from address purpose */
56 0 : static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
57 : {
58 : AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
59 : // "refund" addresses aren't shown, and change addresses aren't returned by getAddresses at all.
60 0 : if (strPurpose == "send")
61 0 : addressType = AddressTableEntry::Sending;
62 0 : else if (strPurpose == "receive")
63 0 : addressType = AddressTableEntry::Receiving;
64 0 : else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
65 0 : addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
66 0 : return addressType;
67 : }
68 :
69 : // Private implementation
70 0 : class AddressTablePriv
71 : {
72 : public:
73 : QList<AddressTableEntry> cachedAddressTable;
74 : AddressTableModel *parent;
75 :
76 0 : explicit AddressTablePriv(AddressTableModel *_parent):
77 0 : parent(_parent) {}
78 :
79 0 : void refreshAddressTable(interfaces::Wallet& wallet, bool pk_hash_only = false)
80 : {
81 0 : cachedAddressTable.clear();
82 : {
83 0 : for (const auto& address : wallet.getAddresses())
84 : {
85 0 : if (pk_hash_only && address.dest.type() != typeid(PKHash))
86 0 : continue;
87 0 : AddressTableEntry::Type addressType = translateTransactionType(
88 0 : QString::fromStdString(address.purpose), address.is_mine);
89 0 : cachedAddressTable.append(AddressTableEntry(addressType,
90 0 : QString::fromStdString(address.name),
91 0 : QString::fromStdString(EncodeDestination(address.dest))));
92 0 : }
93 : }
94 : // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order
95 : // Even though the map is already sorted this re-sorting step is needed because the originating map
96 : // is sorted by binary address, not by base58() address.
97 0 : std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
98 0 : }
99 :
100 0 : void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
101 : {
102 : // Find address / label in model
103 0 : QList<AddressTableEntry>::iterator lower = std::lower_bound(
104 0 : cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
105 0 : QList<AddressTableEntry>::iterator upper = std::upper_bound(
106 0 : cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
107 0 : int lowerIndex = (lower - cachedAddressTable.begin());
108 0 : int upperIndex = (upper - cachedAddressTable.begin());
109 0 : bool inModel = (lower != upper);
110 0 : AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);
111 :
112 0 : switch(status)
113 : {
114 : case CT_NEW:
115 0 : if(inModel)
116 : {
117 0 : qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
118 0 : break;
119 : }
120 0 : parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
121 0 : cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
122 0 : parent->endInsertRows();
123 0 : break;
124 : case CT_UPDATED:
125 0 : if(!inModel)
126 : {
127 0 : qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
128 0 : break;
129 : }
130 0 : lower->type = newEntryType;
131 0 : lower->label = label;
132 0 : parent->emitDataChanged(lowerIndex);
133 0 : break;
134 : case CT_DELETED:
135 0 : if(!inModel)
136 : {
137 0 : qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
138 0 : break;
139 : }
140 0 : parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
141 0 : cachedAddressTable.erase(lower, upper);
142 0 : parent->endRemoveRows();
143 0 : break;
144 : }
145 0 : }
146 :
147 0 : int size()
148 : {
149 0 : return cachedAddressTable.size();
150 : }
151 :
152 0 : AddressTableEntry *index(int idx)
153 : {
154 0 : if(idx >= 0 && idx < cachedAddressTable.size())
155 : {
156 0 : return &cachedAddressTable[idx];
157 : }
158 : else
159 : {
160 0 : return nullptr;
161 : }
162 0 : }
163 : };
164 :
165 0 : AddressTableModel::AddressTableModel(WalletModel *parent, bool pk_hash_only) :
166 0 : QAbstractTableModel(parent), walletModel(parent)
167 0 : {
168 0 : columns << tr("Label") << tr("Address");
169 0 : priv = new AddressTablePriv(this);
170 0 : priv->refreshAddressTable(parent->wallet(), pk_hash_only);
171 0 : }
172 :
173 0 : AddressTableModel::~AddressTableModel()
174 0 : {
175 0 : delete priv;
176 0 : }
177 :
178 0 : int AddressTableModel::rowCount(const QModelIndex &parent) const
179 : {
180 : Q_UNUSED(parent);
181 0 : return priv->size();
182 : }
183 :
184 0 : int AddressTableModel::columnCount(const QModelIndex &parent) const
185 : {
186 : Q_UNUSED(parent);
187 0 : return columns.length();
188 : }
189 :
190 0 : QVariant AddressTableModel::data(const QModelIndex &index, int role) const
191 : {
192 0 : if(!index.isValid())
193 0 : return QVariant();
194 :
195 0 : AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
196 :
197 0 : if(role == Qt::DisplayRole || role == Qt::EditRole)
198 : {
199 0 : switch(index.column())
200 : {
201 : case Label:
202 0 : if(rec->label.isEmpty() && role == Qt::DisplayRole)
203 : {
204 0 : return tr("(no label)");
205 : }
206 : else
207 : {
208 0 : return rec->label;
209 : }
210 : case Address:
211 0 : return rec->address;
212 : }
213 : }
214 0 : else if (role == Qt::FontRole)
215 : {
216 0 : QFont font;
217 0 : if(index.column() == Address)
218 : {
219 0 : font = GUIUtil::fixedPitchFont();
220 0 : }
221 0 : return font;
222 0 : }
223 0 : else if (role == TypeRole)
224 : {
225 0 : switch(rec->type)
226 : {
227 : case AddressTableEntry::Sending:
228 0 : return Send;
229 : case AddressTableEntry::Receiving:
230 0 : return Receive;
231 : default: break;
232 : }
233 : }
234 0 : return QVariant();
235 0 : }
236 :
237 0 : bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
238 : {
239 0 : if(!index.isValid())
240 0 : return false;
241 0 : AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
242 0 : std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
243 0 : editStatus = OK;
244 :
245 0 : if(role == Qt::EditRole)
246 : {
247 0 : CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
248 0 : if(index.column() == Label)
249 : {
250 : // Do nothing, if old label == new label
251 0 : if(rec->label == value.toString())
252 : {
253 0 : editStatus = NO_CHANGES;
254 0 : return false;
255 : }
256 0 : walletModel->wallet().setAddressBook(curAddress, value.toString().toStdString(), strPurpose);
257 0 : } else if(index.column() == Address) {
258 0 : CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
259 : // Refuse to set invalid address, set error status and return false
260 0 : if(boost::get<CNoDestination>(&newAddress))
261 : {
262 0 : editStatus = INVALID_ADDRESS;
263 0 : return false;
264 : }
265 : // Do nothing, if old address == new address
266 0 : else if(newAddress == curAddress)
267 : {
268 0 : editStatus = NO_CHANGES;
269 0 : return false;
270 : }
271 : // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
272 : // to paste an existing address over another address (with a different label)
273 0 : if (walletModel->wallet().getAddress(
274 : newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
275 : {
276 0 : editStatus = DUPLICATE_ADDRESS;
277 0 : return false;
278 : }
279 : // Double-check that we're not overwriting a receiving address
280 0 : else if(rec->type == AddressTableEntry::Sending)
281 : {
282 : // Remove old entry
283 0 : walletModel->wallet().delAddressBook(curAddress);
284 : // Add new entry with new address
285 0 : walletModel->wallet().setAddressBook(newAddress, value.toString().toStdString(), strPurpose);
286 0 : }
287 0 : }
288 0 : return true;
289 0 : }
290 0 : return false;
291 0 : }
292 :
293 0 : QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
294 : {
295 0 : if(orientation == Qt::Horizontal)
296 : {
297 0 : if(role == Qt::DisplayRole && section < columns.size())
298 : {
299 0 : return columns[section];
300 : }
301 : }
302 0 : return QVariant();
303 0 : }
304 :
305 0 : Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
306 : {
307 0 : if (!index.isValid()) return Qt::NoItemFlags;
308 :
309 0 : AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
310 :
311 0 : Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
312 : // Can edit address and label for sending addresses,
313 : // and only label for receiving addresses.
314 0 : if(rec->type == AddressTableEntry::Sending ||
315 0 : (rec->type == AddressTableEntry::Receiving && index.column()==Label))
316 : {
317 0 : retval |= Qt::ItemIsEditable;
318 0 : }
319 0 : return retval;
320 0 : }
321 :
322 0 : QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
323 : {
324 : Q_UNUSED(parent);
325 0 : AddressTableEntry *data = priv->index(row);
326 0 : if(data)
327 : {
328 0 : return createIndex(row, column, priv->index(row));
329 : }
330 : else
331 : {
332 0 : return QModelIndex();
333 : }
334 0 : }
335 :
336 0 : void AddressTableModel::updateEntry(const QString &address,
337 : const QString &label, bool isMine, const QString &purpose, int status)
338 : {
339 : // Update address book model from Bitcoin core
340 0 : priv->updateEntry(address, label, isMine, purpose, status);
341 0 : }
342 :
343 0 : QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
344 : {
345 0 : std::string strLabel = label.toStdString();
346 0 : std::string strAddress = address.toStdString();
347 :
348 0 : editStatus = OK;
349 :
350 0 : if(type == Send)
351 : {
352 0 : if(!walletModel->validateAddress(address))
353 : {
354 0 : editStatus = INVALID_ADDRESS;
355 0 : return QString();
356 : }
357 : // Check for duplicate addresses
358 : {
359 0 : if (walletModel->wallet().getAddress(
360 0 : DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
361 : {
362 0 : editStatus = DUPLICATE_ADDRESS;
363 0 : return QString();
364 : }
365 : }
366 :
367 : // Add entry
368 0 : walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send");
369 0 : }
370 0 : else if(type == Receive)
371 : {
372 : // Generate a new address to associate with given label
373 0 : CTxDestination dest;
374 0 : if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
375 : {
376 0 : WalletModel::UnlockContext ctx(walletModel->requestUnlock());
377 0 : if(!ctx.isValid())
378 : {
379 : // Unlock wallet failed or was cancelled
380 0 : editStatus = WALLET_UNLOCK_FAILURE;
381 0 : return QString();
382 : }
383 0 : if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest))
384 : {
385 0 : editStatus = KEY_GENERATION_FAILURE;
386 0 : return QString();
387 : }
388 0 : }
389 0 : strAddress = EncodeDestination(dest);
390 0 : }
391 : else
392 : {
393 0 : return QString();
394 : }
395 0 : return QString::fromStdString(strAddress);
396 0 : }
397 :
398 0 : bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
399 : {
400 : Q_UNUSED(parent);
401 0 : AddressTableEntry *rec = priv->index(row);
402 0 : if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
403 : {
404 : // Can only remove one row at a time, and cannot remove rows not in model.
405 : // Also refuse to remove receiving addresses.
406 0 : return false;
407 : }
408 0 : walletModel->wallet().delAddressBook(DecodeDestination(rec->address.toStdString()));
409 0 : return true;
410 0 : }
411 :
412 0 : QString AddressTableModel::labelForAddress(const QString &address) const
413 : {
414 0 : std::string name;
415 0 : if (getAddressData(address, &name, /* purpose= */ nullptr)) {
416 0 : return QString::fromStdString(name);
417 : }
418 0 : return QString();
419 0 : }
420 :
421 0 : QString AddressTableModel::purposeForAddress(const QString &address) const
422 : {
423 0 : std::string purpose;
424 0 : if (getAddressData(address, /* name= */ nullptr, &purpose)) {
425 0 : return QString::fromStdString(purpose);
426 : }
427 0 : return QString();
428 0 : }
429 :
430 0 : bool AddressTableModel::getAddressData(const QString &address,
431 : std::string* name,
432 : std::string* purpose) const {
433 0 : CTxDestination destination = DecodeDestination(address.toStdString());
434 0 : return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
435 0 : }
436 :
437 0 : int AddressTableModel::lookupAddress(const QString &address) const
438 : {
439 0 : QModelIndexList lst = match(index(0, Address, QModelIndex()),
440 0 : Qt::EditRole, address, 1, Qt::MatchExactly);
441 0 : if(lst.isEmpty())
442 : {
443 0 : return -1;
444 : }
445 : else
446 : {
447 0 : return lst.at(0).row();
448 : }
449 0 : }
450 :
451 0 : OutputType AddressTableModel::GetDefaultAddressType() const { return walletModel->wallet().getDefaultAddressType(); };
452 :
453 0 : void AddressTableModel::emitDataChanged(int idx)
454 : {
455 0 : Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
456 0 : }
|