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