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