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