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