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