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