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