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