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