Bitcoin Core  24.99.0
P2P Digital Currency
addressbookpage.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2021 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>
10 #include <qt/forms/ui_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  model(nullptr),
68  mode(_mode),
69  tab(_tab)
70 {
71  ui->setupUi(this);
72 
73  if (!platformStyle->getImagesOnButtons()) {
74  ui->newAddress->setIcon(QIcon());
75  ui->copyAddress->setIcon(QIcon());
76  ui->deleteAddress->setIcon(QIcon());
77  ui->exportButton->setIcon(QIcon());
78  } else {
79  ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add"));
80  ui->copyAddress->setIcon(platformStyle->SingleColorIcon(":/icons/editcopy"));
81  ui->deleteAddress->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
82  ui->exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
83  }
84 
85  switch(mode)
86  {
87  case ForSelection:
88  switch(tab)
89  {
90  case SendingTab: setWindowTitle(tr("Choose the address to send coins to")); break;
91  case ReceivingTab: setWindowTitle(tr("Choose the address to receive coins with")); break;
92  }
93  connect(ui->tableView, &QTableView::doubleClicked, this, &QDialog::accept);
94  ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
95  ui->tableView->setFocus();
96  ui->closeButton->setText(tr("C&hoose"));
97  ui->exportButton->hide();
98  break;
99  case ForEditing:
100  switch(tab)
101  {
102  case SendingTab: setWindowTitle(tr("Sending addresses")); break;
103  case ReceivingTab: setWindowTitle(tr("Receiving addresses")); break;
104  }
105  break;
106  }
107  switch(tab)
108  {
109  case SendingTab:
110  ui->labelExplanation->setText(tr("These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins."));
111  ui->deleteAddress->setVisible(true);
112  ui->newAddress->setVisible(true);
113  break;
114  case ReceivingTab:
115  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'."));
116  ui->deleteAddress->setVisible(false);
117  ui->newAddress->setVisible(false);
118  break;
119  }
120 
121  // Build context menu
122  contextMenu = new QMenu(this);
123  contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked);
124  contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction);
125  contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction);
126 
127  if (tab == SendingTab) {
128  contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
129  }
130 
131  connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu);
132  connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept);
133 
135 }
136 
138 {
139  delete ui;
140 }
141 
143 {
144  this->model = _model;
145  if(!_model)
146  return;
147 
150  proxyModel->setSourceModel(_model);
151 
152  connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterWildcard);
153 
154  ui->tableView->setModel(proxyModel);
155  ui->tableView->sortByColumn(0, Qt::AscendingOrder);
156 
157  // Set column widths
158  ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch);
159  ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents);
160 
161  connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
163 
164  // Select row for newly created address
165  connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress);
166 
168 }
169 
171 {
173 }
174 
176 {
178 }
179 
181 {
182  if(!model)
183  return;
184 
185  if(!ui->tableView->selectionModel())
186  return;
187  QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows();
188  if(indexes.isEmpty())
189  return;
190 
191  auto dlg = new EditAddressDialog(
192  tab == SendingTab ?
195  dlg->setModel(model);
196  QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0));
197  dlg->loadRow(origIndex.row());
199 }
200 
202 {
203  if(!model)
204  return;
205 
206  if (tab == ReceivingTab) {
207  return;
208  }
209 
211  dlg.setModel(model);
212  if(dlg.exec())
213  {
215  }
216 }
217 
219 {
220  QTableView *table = ui->tableView;
221  if(!table->selectionModel())
222  return;
223 
224  QModelIndexList indexes = table->selectionModel()->selectedRows();
225  if(!indexes.isEmpty())
226  {
227  table->model()->removeRow(indexes.at(0).row());
228  }
229 }
230 
232 {
233  // Set button states based on selected tab and selection
234  QTableView *table = ui->tableView;
235  if(!table->selectionModel())
236  return;
237 
238  if(table->selectionModel()->hasSelection())
239  {
240  switch(tab)
241  {
242  case SendingTab:
243  // In sending tab, allow deletion of selection
244  ui->deleteAddress->setEnabled(true);
245  ui->deleteAddress->setVisible(true);
246  break;
247  case ReceivingTab:
248  // Deleting receiving addresses, however, is not allowed
249  ui->deleteAddress->setEnabled(false);
250  ui->deleteAddress->setVisible(false);
251  break;
252  }
253  ui->copyAddress->setEnabled(true);
254  }
255  else
256  {
257  ui->deleteAddress->setEnabled(false);
258  ui->copyAddress->setEnabled(false);
259  }
260 }
261 
262 void AddressBookPage::done(int retval)
263 {
264  QTableView *table = ui->tableView;
265  if(!table->selectionModel() || !table->model())
266  return;
267 
268  // Figure out which address was selected, and return it
269  QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
270 
271  for (const QModelIndex& index : indexes) {
272  QVariant address = table->model()->data(index);
273  returnValue = address.toString();
274  }
275 
276  if(returnValue.isEmpty())
277  {
278  // If no address entry selected, return rejected
279  retval = Rejected;
280  }
281 
282  QDialog::done(retval);
283 }
284 
286 {
287  // CSV is currently the only supported format
288  QString filename = GUIUtil::getSaveFileName(this,
289  tr("Export Address List"), QString(),
290  /*: Expanded name of the CSV file format.
291  See: https://en.wikipedia.org/wiki/Comma-separated_values. */
292  tr("Comma separated file") + QLatin1String(" (*.csv)"), nullptr);
293 
294  if (filename.isNull())
295  return;
296 
297  CSVModelWriter writer(filename);
298 
299  // name, column, role
300  writer.setModel(proxyModel);
301  writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole);
302  writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole);
303 
304  if(!writer.write()) {
305  QMessageBox::critical(this, tr("Exporting Failed"),
306  /*: An error message. %1 is a stand-in argument for the name
307  of the file we attempted to save to. */
308  tr("There was an error trying to save the address list to %1. Please try again.").arg(filename));
309  }
310 }
311 
312 void AddressBookPage::contextualMenu(const QPoint &point)
313 {
314  QModelIndex index = ui->tableView->indexAt(point);
315  if(index.isValid())
316  {
317  contextMenu->exec(QCursor::pos());
318  }
319 }
320 
321 void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/)
322 {
323  QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent));
324  if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect))
325  {
326  // Select row of newly created address, once
327  ui->tableView->setFocus();
328  ui->tableView->selectRow(idx.row());
329  newAddressToSelect.clear();
330  }
331 }
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 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.
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:988
void handleCloseWindowShortcut(QWidget *w)
Definition: guiutil.cpp:415
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:255
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:304
constexpr auto dialog_flags
Definition: guiutil.h:60