Bitcoin Core  27.99.0
P2P Digital Currency
addressbookpage.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2022 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/addressbookpage.h>
6 #include <qt/forms/ui_addressbookpage.h>
7 
8 #include <qt/addresstablemodel.h>
9 #include <qt/csvmodelwriter.h>
10 #include <qt/editaddressdialog.h>
11 #include <qt/guiutil.h>
12 #include <qt/platformstyle.h>
13 
14 #include <QIcon>
15 #include <QMenu>
16 #include <QMessageBox>
17 #include <QSortFilterProxyModel>
18 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
19 #include <QRegularExpression>
20 #else
21 #include <QRegExp>
22 #endif
23 
24 class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel
25 {
26  const QString m_type;
27 
28 public:
29  AddressBookSortFilterProxyModel(const QString& type, QObject* parent)
30  : QSortFilterProxyModel(parent)
31  , m_type(type)
32  {
33  setDynamicSortFilter(true);
34  setFilterCaseSensitivity(Qt::CaseInsensitive);
35  setSortCaseSensitivity(Qt::CaseInsensitive);
36  }
37 
38 protected:
39  bool filterAcceptsRow(int row, const QModelIndex& parent) const override
40  {
41  auto model = sourceModel();
42  auto label = model->index(row, AddressTableModel::Label, parent);
43 
44  if (model->data(label, AddressTableModel::TypeRole).toString() != m_type) {
45  return false;
46  }
47 
48  auto address = model->index(row, AddressTableModel::Address, parent);
49 
50 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
51  const auto pattern = filterRegularExpression();
52 #else
53  const auto pattern = filterRegExp();
54 #endif
55  return (model->data(address).toString().contains(pattern) ||
56  model->data(label).toString().contains(pattern));
57  }
58 };
59 
60 AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) :
61  QDialog(parent, GUIUtil::dialog_flags),
62  ui(new Ui::AddressBookPage),
63  mode(_mode),
64  tab(_tab)
65 {
66  ui->setupUi(this);
67 
68  if (!platformStyle->getImagesOnButtons()) {
69  ui->newAddress->setIcon(QIcon());
70  ui->copyAddress->setIcon(QIcon());
71  ui->deleteAddress->setIcon(QIcon());
72  ui->exportButton->setIcon(QIcon());
73  } else {
74  ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add"));
75  ui->copyAddress->setIcon(platformStyle->SingleColorIcon(":/icons/editcopy"));
76  ui->deleteAddress->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
77  ui->exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
78  }
79 
80  if (mode == ForSelection) {
81  switch(tab)
82  {
83  case SendingTab: setWindowTitle(tr("Choose the address to send coins to")); break;
84  case ReceivingTab: setWindowTitle(tr("Choose the address to receive coins with")); break;
85  }
86  connect(ui->tableView, &QTableView::doubleClicked, this, &QDialog::accept);
87  ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
88  ui->tableView->setFocus();
89  ui->closeButton->setText(tr("C&hoose"));
90  ui->exportButton->hide();
91  }
92  switch(tab)
93  {
94  case SendingTab:
95  ui->labelExplanation->setText(tr("These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins."));
96  ui->deleteAddress->setVisible(true);
97  ui->newAddress->setVisible(true);
98  break;
99  case ReceivingTab:
100  ui->labelExplanation->setText(tr("These are your Bitcoin addresses for receiving payments. Use the 'Create new receiving address' button in the receive tab to create new addresses.\nSigning is only possible with addresses of the type 'legacy'."));
101  ui->deleteAddress->setVisible(false);
102  ui->newAddress->setVisible(false);
103  break;
104  }
105 
106  // Build context menu
107  contextMenu = new QMenu(this);
108  contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked);
109  contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction);
110  contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction);
111 
112  if (tab == SendingTab) {
113  contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
114  }
115 
116  connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu);
117  connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept);
118 
120 }
121 
123 {
124  delete ui;
125 }
126 
128 {
129  this->model = _model;
130  if(!_model)
131  return;
132 
135  proxyModel->setSourceModel(_model);
136 
137  connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterWildcard);
138 
139  ui->tableView->setModel(proxyModel);
140  ui->tableView->sortByColumn(0, Qt::AscendingOrder);
141 
142  // Set column widths
143  ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch);
144  ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents);
145 
146  connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
148 
149  // Select row for newly created address
150  connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress);
151 
154 }
155 
157 {
159 }
160 
162 {
164 }
165 
167 {
168  if(!model)
169  return;
170 
171  if(!ui->tableView->selectionModel())
172  return;
173  QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows();
174  if(indexes.isEmpty())
175  return;
176 
177  auto dlg = new EditAddressDialog(
178  tab == SendingTab ?
181  dlg->setModel(model);
182  QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0));
183  dlg->loadRow(origIndex.row());
185 }
186 
188 {
189  if(!model)
190  return;
191 
192  if (tab == ReceivingTab) {
193  return;
194  }
195 
197  dlg.setModel(model);
198  if(dlg.exec())
199  {
201  }
202 }
203 
205 {
206  QTableView *table = ui->tableView;
207  if(!table->selectionModel())
208  return;
209 
210  QModelIndexList indexes = table->selectionModel()->selectedRows();
211  if(!indexes.isEmpty())
212  {
213  table->model()->removeRow(indexes.at(0).row());
214  }
215 }
216 
218 {
219  // Set button states based on selected tab and selection
220  QTableView *table = ui->tableView;
221  if(!table->selectionModel())
222  return;
223 
224  if(table->selectionModel()->hasSelection())
225  {
226  switch(tab)
227  {
228  case SendingTab:
229  // In sending tab, allow deletion of selection
230  ui->deleteAddress->setEnabled(true);
231  ui->deleteAddress->setVisible(true);
232  break;
233  case ReceivingTab:
234  // Deleting receiving addresses, however, is not allowed
235  ui->deleteAddress->setEnabled(false);
236  ui->deleteAddress->setVisible(false);
237  break;
238  }
239  ui->copyAddress->setEnabled(true);
240  }
241  else
242  {
243  ui->deleteAddress->setEnabled(false);
244  ui->copyAddress->setEnabled(false);
245  }
246 }
247 
248 void AddressBookPage::done(int retval)
249 {
250  QTableView *table = ui->tableView;
251  if(!table->selectionModel() || !table->model())
252  return;
253 
254  // Figure out which address was selected, and return it
255  QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
256 
257  for (const QModelIndex& index : indexes) {
258  QVariant address = table->model()->data(index);
259  returnValue = address.toString();
260  }
261 
262  if(returnValue.isEmpty())
263  {
264  // If no address entry selected, return rejected
265  retval = Rejected;
266  }
267 
268  QDialog::done(retval);
269 }
270 
272 {
273  // CSV is currently the only supported format
274  QString filename = GUIUtil::getSaveFileName(this,
275  tr("Export Address List"), QString(),
276  /*: Expanded name of the CSV file format.
277  See: https://en.wikipedia.org/wiki/Comma-separated_values. */
278  tr("Comma separated file") + QLatin1String(" (*.csv)"), nullptr);
279 
280  if (filename.isNull())
281  return;
282 
283  CSVModelWriter writer(filename);
284 
285  // name, column, role
286  writer.setModel(proxyModel);
287  writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole);
288  writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole);
289 
290  if(!writer.write()) {
291  QMessageBox::critical(this, tr("Exporting Failed"),
292  /*: An error message. %1 is a stand-in argument for the name
293  of the file we attempted to save to. */
294  tr("There was an error trying to save the address list to %1. Please try again.").arg(filename));
295  }
296 }
297 
298 void AddressBookPage::contextualMenu(const QPoint &point)
299 {
300  QModelIndex index = ui->tableView->indexAt(point);
301  if(index.isValid())
302  {
303  contextMenu->exec(QCursor::pos());
304  }
305 }
306 
307 void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/)
308 {
309  QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent));
310  if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect))
311  {
312  // Select row of newly created address, once
313  ui->tableView->setFocus();
314  ui->tableView->selectRow(idx.row());
315  newAddressToSelect.clear();
316  }
317 }
318 
320 {
321  const QString walletName = this->model->GetWalletDisplayName();
322 
323  if (mode == ForEditing) {
324  switch(tab)
325  {
326  case SendingTab: setWindowTitle(tr("Sending addresses - %1").arg(walletName)); break;
327  case ReceivingTab: setWindowTitle(tr("Receiving addresses - %1").arg(walletName)); break;
328  }
329  }
330 }
Widget that shows a list of sending or receiving addresses.
Ui::AddressBookPage * ui
void onEditAction()
Edit currently selected address entry (no button)
@ ForEditing
Open address book for editing.
@ ForSelection
Open address book to pick address.
void setModel(AddressTableModel *model)
void onCopyLabelAction()
Copy label of currently selected address entry to clipboard (no button)
QString newAddressToSelect
void done(int retval) override
AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent=nullptr)
void on_copyAddress_clicked()
Copy address of currently selected address entry to clipboard.
void on_exportButton_clicked()
Export button clicked.
void on_deleteAddress_clicked()
Delete currently selected address entry.
void contextualMenu(const QPoint &point)
Spawn contextual menu (right mouse menu) for address book entry.
AddressTableModel * model
void updateWindowsTitleWithWalletName()
void selectionChanged()
Set button states based on selected tab and selection.
void selectNewAddress(const QModelIndex &parent, int begin, int)
New entry/entries were added to address table.
void on_newAddress_clicked()
Create a new address for receiving coins and / or add a new address book entry.
AddressBookSortFilterProxyModel * proxyModel
bool filterAcceptsRow(int row, const QModelIndex &parent) const override
AddressBookSortFilterProxyModel(const QString &type, QObject *parent)
Qt model of the address book in the core.
@ TypeRole
Type of address (Send or Receive)
@ Address
Bitcoin address.
@ Label
User specified label.
QModelIndex index(int row, int column, const QModelIndex &parent) const override
static const QString Send
Specifies send address.
QString GetWalletDisplayName() const
static const QString Receive
Specifies receive address.
Export a Qt table model to a CSV file.
bool write()
Perform export of the model to CSV.
void setModel(const QAbstractItemModel *model)
void addColumn(const QString &title, int column, int role=Qt::EditRole)
Dialog for editing an address and associated information.
void setModel(AddressTableModel *model)
QString getAddress() const
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getImagesOnButtons() const
Definition: platformstyle.h:21
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:60
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:1005
void handleCloseWindowShortcut(QWidget *w)
Definition: guiutil.cpp:427
void copyEntryData(const QAbstractItemView *view, int column, int role)
Copy a field of the currently selected entry of a view to the clipboard.
Definition: guiutil.cpp:267
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition: guiutil.cpp:316
constexpr auto dialog_flags
Definition: guiutil.h:60