[tests] Add -blocknotify functional test
[bitcoinplatinum.git] / src / qt / addresstablemodel.cpp
blob0eb7ec4306ba8346c24dec7b8f81f3b6c06bad32
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.
5 #include "addresstablemodel.h"
7 #include "guiutil.h"
8 #include "walletmodel.h"
10 #include "base58.h"
11 #include "wallet/wallet.h"
14 #include <QFont>
15 #include <QDebug>
17 const QString AddressTableModel::Send = "S";
18 const QString AddressTableModel::Receive = "R";
20 struct AddressTableEntry
22 enum Type {
23 Sending,
24 Receiving,
25 Hidden /* QSortFilterProxyModel will filter these out */
28 Type type;
29 QString label;
30 QString address;
32 AddressTableEntry() {}
33 AddressTableEntry(Type _type, const QString &_label, const QString &_address):
34 type(_type), label(_label), address(_address) {}
37 struct AddressTableEntryLessThan
39 bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
41 return a.address < b.address;
43 bool operator()(const AddressTableEntry &a, const QString &b) const
45 return a.address < b;
47 bool operator()(const QString &a, const AddressTableEntry &b) const
49 return a < b.address;
53 /* Determine address type from address purpose */
54 static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
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;
67 // Private implementation
68 class AddressTablePriv
70 public:
71 CWallet *wallet;
72 QList<AddressTableEntry> cachedAddressTable;
73 AddressTableModel *parent;
75 AddressTablePriv(CWallet *_wallet, AddressTableModel *_parent):
76 wallet(_wallet), parent(_parent) {}
78 void refreshAddressTable()
80 cachedAddressTable.clear();
82 LOCK(wallet->cs_wallet);
83 for (const std::pair<CTxDestination, CAddressBookData>& item : wallet->mapAddressBook)
85 const CTxDestination& address = item.first;
86 bool fMine = IsMine(*wallet, address);
87 AddressTableEntry::Type addressType = translateTransactionType(
88 QString::fromStdString(item.second.purpose), fMine);
89 const std::string& strName = item.second.name;
90 cachedAddressTable.append(AddressTableEntry(addressType,
91 QString::fromStdString(strName),
92 QString::fromStdString(EncodeDestination(address))));
95 // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
96 // Even though the map is already sorted this re-sorting step is needed because the originating map
97 // is sorted by binary address, not by base58() address.
98 qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
101 void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
103 // Find address / label in model
104 QList<AddressTableEntry>::iterator lower = qLowerBound(
105 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
106 QList<AddressTableEntry>::iterator upper = qUpperBound(
107 cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
108 int lowerIndex = (lower - cachedAddressTable.begin());
109 int upperIndex = (upper - cachedAddressTable.begin());
110 bool inModel = (lower != upper);
111 AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);
113 switch(status)
115 case CT_NEW:
116 if(inModel)
118 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
119 break;
121 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
122 cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
123 parent->endInsertRows();
124 break;
125 case CT_UPDATED:
126 if(!inModel)
128 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
129 break;
131 lower->type = newEntryType;
132 lower->label = label;
133 parent->emitDataChanged(lowerIndex);
134 break;
135 case CT_DELETED:
136 if(!inModel)
138 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
139 break;
141 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
142 cachedAddressTable.erase(lower, upper);
143 parent->endRemoveRows();
144 break;
148 int size()
150 return cachedAddressTable.size();
153 AddressTableEntry *index(int idx)
155 if(idx >= 0 && idx < cachedAddressTable.size())
157 return &cachedAddressTable[idx];
159 else
161 return 0;
166 AddressTableModel::AddressTableModel(CWallet *_wallet, WalletModel *parent) :
167 QAbstractTableModel(parent),walletModel(parent),wallet(_wallet),priv(0)
169 columns << tr("Label") << tr("Address");
170 priv = new AddressTablePriv(wallet, this);
171 priv->refreshAddressTable();
174 AddressTableModel::~AddressTableModel()
176 delete priv;
179 int AddressTableModel::rowCount(const QModelIndex &parent) const
181 Q_UNUSED(parent);
182 return priv->size();
185 int AddressTableModel::columnCount(const QModelIndex &parent) const
187 Q_UNUSED(parent);
188 return columns.length();
191 QVariant AddressTableModel::data(const QModelIndex &index, int role) const
193 if(!index.isValid())
194 return QVariant();
196 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
198 if(role == Qt::DisplayRole || role == Qt::EditRole)
200 switch(index.column())
202 case Label:
203 if(rec->label.isEmpty() && role == Qt::DisplayRole)
205 return tr("(no label)");
207 else
209 return rec->label;
211 case Address:
212 return rec->address;
215 else if (role == Qt::FontRole)
217 QFont font;
218 if(index.column() == Address)
220 font = GUIUtil::fixedPitchFont();
222 return font;
224 else if (role == TypeRole)
226 switch(rec->type)
228 case AddressTableEntry::Sending:
229 return Send;
230 case AddressTableEntry::Receiving:
231 return Receive;
232 default: break;
235 return QVariant();
238 bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
240 if(!index.isValid())
241 return false;
242 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
243 std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
244 editStatus = OK;
246 if(role == Qt::EditRole)
248 LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */
249 CTxDestination curAddress = DecodeDestination(rec->address.toStdString());
250 if(index.column() == Label)
252 // Do nothing, if old label == new label
253 if(rec->label == value.toString())
255 editStatus = NO_CHANGES;
256 return false;
258 wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose);
259 } else if(index.column() == Address) {
260 CTxDestination newAddress = DecodeDestination(value.toString().toStdString());
261 // Refuse to set invalid address, set error status and return false
262 if(boost::get<CNoDestination>(&newAddress))
264 editStatus = INVALID_ADDRESS;
265 return false;
267 // Do nothing, if old address == new address
268 else if(newAddress == curAddress)
270 editStatus = NO_CHANGES;
271 return false;
273 // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
274 // to paste an existing address over another address (with a different label)
275 else if(wallet->mapAddressBook.count(newAddress))
277 editStatus = DUPLICATE_ADDRESS;
278 return false;
280 // Double-check that we're not overwriting a receiving address
281 else if(rec->type == AddressTableEntry::Sending)
283 // Remove old entry
284 wallet->DelAddressBook(curAddress);
285 // Add new entry with new address
286 wallet->SetAddressBook(newAddress, rec->label.toStdString(), strPurpose);
289 return true;
291 return false;
294 QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
296 if(orientation == Qt::Horizontal)
298 if(role == Qt::DisplayRole && section < columns.size())
300 return columns[section];
303 return QVariant();
306 Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
308 if(!index.isValid())
309 return 0;
310 AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
312 Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
313 // Can edit address and label for sending addresses,
314 // and only label for receiving addresses.
315 if(rec->type == AddressTableEntry::Sending ||
316 (rec->type == AddressTableEntry::Receiving && index.column()==Label))
318 retval |= Qt::ItemIsEditable;
320 return retval;
323 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
325 Q_UNUSED(parent);
326 AddressTableEntry *data = priv->index(row);
327 if(data)
329 return createIndex(row, column, priv->index(row));
331 else
333 return QModelIndex();
337 void AddressTableModel::updateEntry(const QString &address,
338 const QString &label, bool isMine, const QString &purpose, int status)
340 // Update address book model from Bitcoin core
341 priv->updateEntry(address, label, isMine, purpose, status);
344 QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
346 std::string strLabel = label.toStdString();
347 std::string strAddress = address.toStdString();
349 editStatus = OK;
351 if(type == Send)
353 if(!walletModel->validateAddress(address))
355 editStatus = INVALID_ADDRESS;
356 return QString();
358 // Check for duplicate addresses
360 LOCK(wallet->cs_wallet);
361 if(wallet->mapAddressBook.count(DecodeDestination(strAddress)))
363 editStatus = DUPLICATE_ADDRESS;
364 return QString();
368 else if(type == Receive)
370 // Generate a new address to associate with given label
371 CPubKey newKey;
372 if(!wallet->GetKeyFromPool(newKey))
374 WalletModel::UnlockContext ctx(walletModel->requestUnlock());
375 if(!ctx.isValid())
377 // Unlock wallet failed or was cancelled
378 editStatus = WALLET_UNLOCK_FAILURE;
379 return QString();
381 if(!wallet->GetKeyFromPool(newKey))
383 editStatus = KEY_GENERATION_FAILURE;
384 return QString();
387 strAddress = EncodeDestination(newKey.GetID());
389 else
391 return QString();
394 // Add entry
396 LOCK(wallet->cs_wallet);
397 wallet->SetAddressBook(DecodeDestination(strAddress), strLabel,
398 (type == Send ? "send" : "receive"));
400 return QString::fromStdString(strAddress);
403 bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
405 Q_UNUSED(parent);
406 AddressTableEntry *rec = priv->index(row);
407 if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
409 // Can only remove one row at a time, and cannot remove rows not in model.
410 // Also refuse to remove receiving addresses.
411 return false;
414 LOCK(wallet->cs_wallet);
415 wallet->DelAddressBook(DecodeDestination(rec->address.toStdString()));
417 return true;
420 /* Look up label for address in address book, if not found return empty string.
422 QString AddressTableModel::labelForAddress(const QString &address) const
425 LOCK(wallet->cs_wallet);
426 CTxDestination destination = DecodeDestination(address.toStdString());
427 std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(destination);
428 if (mi != wallet->mapAddressBook.end())
430 return QString::fromStdString(mi->second.name);
433 return QString();
436 int AddressTableModel::lookupAddress(const QString &address) const
438 QModelIndexList lst = match(index(0, Address, QModelIndex()),
439 Qt::EditRole, address, 1, Qt::MatchExactly);
440 if(lst.isEmpty())
442 return -1;
444 else
446 return lst.at(0).row();
450 void AddressTableModel::emitDataChanged(int idx)
452 Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));