1 // Copyright (c) 2011-2020 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 <wallet/wallet.h>
6 
7 #include <qt/receivecoinsdialog.h>
8 #include <qt/forms/ui_receivecoinsdialog.h>
9 
10 #include <qt/addresstablemodel.h>
11 #include <qt/guiutil.h>
12 #include <qt/optionsmodel.h>
13 #include <qt/platformstyle.h>
14 #include <qt/receiverequestdialog.h>
15 #include <qt/recentrequeststablemodel.h>
16 #include <qt/walletmodel.h>
17 
18 #include <QAction>
19 #include <QCursor>
20 #include <QMessageBox>
21 #include <QScrollBar>
22 #include <QSettings>
23 #include <QTextDocument>
24 
ReceiveCoinsDialog(const PlatformStyle * _platformStyle,QWidget * parent)25 ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
26     QDialog(parent, GUIUtil::dialog_flags),
27     ui(new Ui::ReceiveCoinsDialog),
28     model(nullptr),
29     platformStyle(_platformStyle)
30 {
31     ui->setupUi(this);
32 
33     if (!_platformStyle->getImagesOnButtons()) {
34         ui->clearButton->setIcon(QIcon());
35         ui->receiveButton->setIcon(QIcon());
36         ui->showRequestButton->setIcon(QIcon());
37         ui->removeRequestButton->setIcon(QIcon());
38     } else {
39         ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
40         ui->receiveButton->setIcon(_platformStyle->SingleColorIcon(":/icons/receiving_addresses"));
41         ui->showRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/edit"));
42         ui->removeRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
43     }
44 
45     // context menu
46     contextMenu = new QMenu(this);
47     contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI);
48     contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress);
49     copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel);
50     copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage);
51     copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount);
52     connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu);
53 
54     connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear);
55 
56     QTableView* tableView = ui->recentRequestsView;
57     tableView->verticalHeader()->hide();
58     tableView->setAlternatingRowColors(true);
59     tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
60     tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
61 
62     QSettings settings;
63     if (!tableView->horizontalHeader()->restoreState(settings.value("RecentRequestsViewHeaderState").toByteArray())) {
64         tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH);
65         tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH);
66         tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
67         tableView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH);
68         tableView->horizontalHeader()->setStretchLastSection(true);
69     }
70 }
71 
setModel(WalletModel * _model)72 void ReceiveCoinsDialog::setModel(WalletModel *_model)
73 {
74     this->model = _model;
75 
76     if(_model && _model->getOptionsModel())
77     {
78         _model->getRecentRequestsTableModel()->sort(RecentRequestsTableModel::Date, Qt::DescendingOrder);
79         connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveCoinsDialog::updateDisplayUnit);
80         updateDisplayUnit();
81 
82         QTableView* tableView = ui->recentRequestsView;
83         tableView->setModel(_model->getRecentRequestsTableModel());
84         tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder);
85 
86         connect(tableView->selectionModel(),
87             &QItemSelectionModel::selectionChanged, this,
88             &ReceiveCoinsDialog::recentRequestsView_selectionChanged);
89 
90         if (model->wallet().getDefaultAddressType() == OutputType::BECH32) {
91             ui->useBech32->setCheckState(Qt::Checked);
92         } else {
93             ui->useBech32->setCheckState(Qt::Unchecked);
94         }
95 
96         // Set the button to be enabled or disabled based on whether the wallet can give out new addresses.
97         ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
98 
99         // Enable/disable the receive button if the wallet is now able/unable to give out new addresses.
100         connect(model, &WalletModel::canGetAddressesChanged, [this] {
101             ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
102         });
103     }
104 }
105 
~ReceiveCoinsDialog()106 ReceiveCoinsDialog::~ReceiveCoinsDialog()
107 {
108     QSettings settings;
109     settings.setValue("RecentRequestsViewHeaderState", ui->recentRequestsView->horizontalHeader()->saveState());
110     delete ui;
111 }
112 
clear()113 void ReceiveCoinsDialog::clear()
114 {
115     ui->reqAmount->clear();
116     ui->reqLabel->setText("");
117     ui->reqMessage->setText("");
118     updateDisplayUnit();
119 }
120 
reject()121 void ReceiveCoinsDialog::reject()
122 {
123     clear();
124 }
125 
accept()126 void ReceiveCoinsDialog::accept()
127 {
128     clear();
129 }
130 
updateDisplayUnit()131 void ReceiveCoinsDialog::updateDisplayUnit()
132 {
133     if(model && model->getOptionsModel())
134     {
135         ui->reqAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
136     }
137 }
138 
on_receiveButton_clicked()139 void ReceiveCoinsDialog::on_receiveButton_clicked()
140 {
141     if(!model || !model->getOptionsModel() || !model->getAddressTableModel() || !model->getRecentRequestsTableModel())
142         return;
143 
144     QString address;
145     QString label = ui->reqLabel->text();
146     /* Generate new receiving address */
147     OutputType address_type;
148     if (ui->useBech32->isChecked()) {
149         address_type = OutputType::BECH32;
150     } else {
151         address_type = model->wallet().getDefaultAddressType();
152         if (address_type == OutputType::BECH32) {
153             address_type = OutputType::P2SH_SEGWIT;
154         }
155     }
156     address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type);
157 
158     switch(model->getAddressTableModel()->getEditStatus())
159     {
160     case AddressTableModel::EditStatus::OK: {
161         // Success
162         SendCoinsRecipient info(address, label,
163             ui->reqAmount->value(), ui->reqMessage->text());
164         ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
165         dialog->setAttribute(Qt::WA_DeleteOnClose);
166         dialog->setModel(model);
167         dialog->setInfo(info);
168         dialog->show();
169 
170         /* Store request for later reference */
171         model->getRecentRequestsTableModel()->addNewRequest(info);
172         break;
173     }
174     case AddressTableModel::EditStatus::WALLET_UNLOCK_FAILURE:
175         QMessageBox::critical(this, windowTitle(),
176             tr("Could not unlock wallet."),
177             QMessageBox::Ok, QMessageBox::Ok);
178         break;
179     case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE:
180         QMessageBox::critical(this, windowTitle(),
181             tr("Could not generate new %1 address").arg(QString::fromStdString(FormatOutputType(address_type))),
182             QMessageBox::Ok, QMessageBox::Ok);
183         break;
184     // These aren't valid return values for our action
185     case AddressTableModel::EditStatus::INVALID_ADDRESS:
186     case AddressTableModel::EditStatus::DUPLICATE_ADDRESS:
187     case AddressTableModel::EditStatus::NO_CHANGES:
188         assert(false);
189     }
190     clear();
191 }
192 
on_recentRequestsView_doubleClicked(const QModelIndex & index)193 void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index)
194 {
195     const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel();
196     ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
197     dialog->setModel(model);
198     dialog->setInfo(submodel->entry(index.row()).recipient);
199     dialog->setAttribute(Qt::WA_DeleteOnClose);
200     dialog->show();
201 }
202 
recentRequestsView_selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)203 void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
204 {
205     // Enable Show/Remove buttons only if anything is selected.
206     bool enable = !ui->recentRequestsView->selectionModel()->selectedRows().isEmpty();
207     ui->showRequestButton->setEnabled(enable);
208     ui->removeRequestButton->setEnabled(enable);
209 }
210 
on_showRequestButton_clicked()211 void ReceiveCoinsDialog::on_showRequestButton_clicked()
212 {
213     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
214         return;
215     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
216 
217     for (const QModelIndex& index : selection) {
218         on_recentRequestsView_doubleClicked(index);
219     }
220 }
221 
on_removeRequestButton_clicked()222 void ReceiveCoinsDialog::on_removeRequestButton_clicked()
223 {
224     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
225         return;
226     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
227     if(selection.empty())
228         return;
229     // correct for selection mode ContiguousSelection
230     QModelIndex firstIndex = selection.at(0);
231     model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent());
232 }
233 
selectedRow()234 QModelIndex ReceiveCoinsDialog::selectedRow()
235 {
236     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
237         return QModelIndex();
238     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
239     if(selection.empty())
240         return QModelIndex();
241     // correct for selection mode ContiguousSelection
242     QModelIndex firstIndex = selection.at(0);
243     return firstIndex;
244 }
245 
246 // copy column of selected row to clipboard
copyColumnToClipboard(int column)247 void ReceiveCoinsDialog::copyColumnToClipboard(int column)
248 {
249     QModelIndex firstIndex = selectedRow();
250     if (!firstIndex.isValid()) {
251         return;
252     }
253     GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString());
254 }
255 
256 // context menu
showMenu(const QPoint & point)257 void ReceiveCoinsDialog::showMenu(const QPoint &point)
258 {
259     const QModelIndex sel = selectedRow();
260     if (!sel.isValid()) {
261         return;
262     }
263 
264     // disable context menu actions when appropriate
265     const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
266     const RecentRequestEntry& req = submodel->entry(sel.row());
267     copyLabelAction->setDisabled(req.recipient.label.isEmpty());
268     copyMessageAction->setDisabled(req.recipient.message.isEmpty());
269     copyAmountAction->setDisabled(req.recipient.amount == 0);
270 
271     contextMenu->exec(QCursor::pos());
272 }
273 
274 // context menu action: copy URI
copyURI()275 void ReceiveCoinsDialog::copyURI()
276 {
277     QModelIndex sel = selectedRow();
278     if (!sel.isValid()) {
279         return;
280     }
281 
282     const RecentRequestsTableModel * const submodel = model->getRecentRequestsTableModel();
283     const QString uri = GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient);
284     GUIUtil::setClipboard(uri);
285 }
286 
287 // context menu action: copy address
copyAddress()288 void ReceiveCoinsDialog::copyAddress()
289 {
290     const QModelIndex sel = selectedRow();
291     if (!sel.isValid()) {
292         return;
293     }
294 
295     const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
296     const QString address = submodel->entry(sel.row()).recipient.address;
297     GUIUtil::setClipboard(address);
298 }
299 
300 // context menu action: copy label
copyLabel()301 void ReceiveCoinsDialog::copyLabel()
302 {
303     copyColumnToClipboard(RecentRequestsTableModel::Label);
304 }
305 
306 // context menu action: copy message
copyMessage()307 void ReceiveCoinsDialog::copyMessage()
308 {
309     copyColumnToClipboard(RecentRequestsTableModel::Message);
310 }
311 
312 // context menu action: copy amount
copyAmount()313 void ReceiveCoinsDialog::copyAmount()
314 {
315     copyColumnToClipboard(RecentRequestsTableModel::Amount);
316 }
317