addressbookpage.cpp raw
1 // Copyright (c) 2011-present 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 #include <QRegularExpression>
19
20 class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel
21 {
22 const QString m_type;
23
24 public:
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
34 protected:
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
52 AddressBookPage::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
111 GUIUtil::handleCloseWindowShortcut(this);
112 }
113
114 AddressBookPage::~AddressBookPage()
115 {
116 delete ui;
117 }
118
119 void AddressBookPage::setModel(AddressTableModel *_model)
120 {
121 this->model = _model;
122 if(!_model)
123 return;
124
125 auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send;
126 proxyModel = new AddressBookSortFilterProxyModel(type, this);
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,
139 this, &AddressBookPage::selectionChanged);
140
141 // Select row for newly created address
142 connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress);
143
144 selectionChanged();
145 this->updateWindowsTitleWithWalletName();
146 }
147
148 void AddressBookPage::on_copyAddress_clicked()
149 {
150 GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address);
151 }
152
153 void AddressBookPage::onCopyLabelAction()
154 {
155 GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label);
156 }
157
158 void AddressBookPage::onEditAction()
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 ?
171 EditAddressDialog::EditSendingAddress :
172 EditAddressDialog::EditReceivingAddress, this);
173 dlg->setModel(model);
174 QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0));
175 dlg->loadRow(origIndex.row());
176 GUIUtil::ShowModalDialogAsynchronously(dlg);
177 }
178
179 void AddressBookPage::on_newAddress_clicked()
180 {
181 if(!model)
182 return;
183
184 if (tab == ReceivingTab) {
185 return;
186 }
187
188 EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this);
189 dlg.setModel(model);
190 if(dlg.exec())
191 {
192 newAddressToSelect = dlg.getAddress();
193 }
194 }
195
196 void AddressBookPage::on_deleteAddress_clicked()
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
209 void AddressBookPage::selectionChanged()
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
240 void 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
263 void AddressBookPage::on_exportButton_clicked()
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
290 void 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
299 void 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
311 void AddressBookPage::updateWindowsTitleWithWalletName()
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 }
323