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