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