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