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