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 <qt/walletview.h>
6
7 #include <qt/addressbookpage.h>
8 #include <qt/askpassphrasedialog.h>
9 #include <qt/clientmodel.h>
10 #include <qt/guiutil.h>
11 #include <qt/psbtoperationsdialog.h>
12 #include <qt/optionsmodel.h>
13 #include <qt/overviewpage.h>
14 #include <qt/platformstyle.h>
15 #include <qt/receivecoinsdialog.h>
16 #include <qt/sendcoinsdialog.h>
17 #include <qt/signverifymessagedialog.h>
18 #include <qt/transactiontablemodel.h>
19 #include <qt/transactionview.h>
20 #include <qt/walletmodel.h>
21
22 #include <interfaces/node.h>
23 #include <node/ui_interface.h>
24 #include <psbt.h>
25 #include <util/strencodings.h>
26
27 #include <QAction>
28 #include <QActionGroup>
29 #include <QApplication>
30 #include <QClipboard>
31 #include <QFileDialog>
32 #include <QHBoxLayout>
33 #include <QProgressDialog>
34 #include <QPushButton>
35 #include <QVBoxLayout>
36
WalletView(const PlatformStyle * _platformStyle,QWidget * parent)37 WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent):
38 QStackedWidget(parent),
39 clientModel(nullptr),
40 walletModel(nullptr),
41 platformStyle(_platformStyle)
42 {
43 // Create tabs
44 overviewPage = new OverviewPage(platformStyle);
45
46 transactionsPage = new QWidget(this);
47 QVBoxLayout *vbox = new QVBoxLayout();
48 QHBoxLayout *hbox_buttons = new QHBoxLayout();
49 transactionView = new TransactionView(platformStyle, this);
50 vbox->addWidget(transactionView);
51 QPushButton *exportButton = new QPushButton(tr("&Export"), this);
52 exportButton->setToolTip(tr("Export the data in the current tab to a file"));
53 if (platformStyle->getImagesOnButtons()) {
54 exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
55 }
56 hbox_buttons->addStretch();
57 hbox_buttons->addWidget(exportButton);
58 vbox->addLayout(hbox_buttons);
59 transactionsPage->setLayout(vbox);
60
61 receiveCoinsPage = new ReceiveCoinsDialog(platformStyle);
62 sendCoinsPage = new SendCoinsDialog(platformStyle);
63
64 usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this);
65 usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this);
66
67 addWidget(overviewPage);
68 addWidget(transactionsPage);
69 addWidget(receiveCoinsPage);
70 addWidget(sendCoinsPage);
71
72 connect(overviewPage, &OverviewPage::transactionClicked, this, &WalletView::transactionClicked);
73 // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page
74 connect(overviewPage, &OverviewPage::transactionClicked, transactionView, qOverload<const QModelIndex&>(&TransactionView::focusTransaction));
75
76 connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::outOfSyncWarningClicked);
77
78 connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent);
79 // Highlight transaction after send
80 connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, qOverload<const uint256&>(&TransactionView::focusTransaction));
81
82 // Clicking on "Export" allows to export the transaction list
83 connect(exportButton, &QPushButton::clicked, transactionView, &TransactionView::exportClicked);
84
85 // Pass through messages from sendCoinsPage
86 connect(sendCoinsPage, &SendCoinsDialog::message, this, &WalletView::message);
87 // Pass through messages from transactionView
88 connect(transactionView, &TransactionView::message, this, &WalletView::message);
89
90 connect(this, &WalletView::setPrivacy, overviewPage, &OverviewPage::setPrivacy);
91 }
92
~WalletView()93 WalletView::~WalletView()
94 {
95 }
96
setClientModel(ClientModel * _clientModel)97 void WalletView::setClientModel(ClientModel *_clientModel)
98 {
99 this->clientModel = _clientModel;
100
101 overviewPage->setClientModel(_clientModel);
102 sendCoinsPage->setClientModel(_clientModel);
103 if (walletModel) walletModel->setClientModel(_clientModel);
104 }
105
setWalletModel(WalletModel * _walletModel)106 void WalletView::setWalletModel(WalletModel *_walletModel)
107 {
108 this->walletModel = _walletModel;
109
110 // Put transaction list in tabs
111 transactionView->setModel(_walletModel);
112 overviewPage->setWalletModel(_walletModel);
113 receiveCoinsPage->setModel(_walletModel);
114 sendCoinsPage->setModel(_walletModel);
115 usedReceivingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr);
116 usedSendingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr);
117
118 if (_walletModel)
119 {
120 // Receive and pass through messages from wallet model
121 connect(_walletModel, &WalletModel::message, this, &WalletView::message);
122
123 // Handle changes in encryption status
124 connect(_walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged);
125 updateEncryptionStatus();
126
127 // update HD status
128 Q_EMIT hdEnabledStatusChanged();
129
130 // Balloon pop-up for new transaction
131 connect(_walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction);
132
133 // Ask for passphrase if needed
134 connect(_walletModel, &WalletModel::requireUnlock, this, &WalletView::unlockWallet);
135
136 // Show progress dialog
137 connect(_walletModel, &WalletModel::showProgress, this, &WalletView::showProgress);
138 }
139 }
140
processNewTransaction(const QModelIndex & parent,int start,int)141 void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/)
142 {
143 // Prevent balloon-spam when initial block download is in progress
144 if (!walletModel || !clientModel || clientModel->node().isInitialBlockDownload())
145 return;
146
147 TransactionTableModel *ttm = walletModel->getTransactionTableModel();
148 if (!ttm || ttm->processingQueuedTransactions())
149 return;
150
151 QString date = ttm->index(start, TransactionTableModel::Date, parent).data().toString();
152 qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong();
153 QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString();
154 QModelIndex index = ttm->index(start, 0, parent);
155 QString address = ttm->data(index, TransactionTableModel::AddressRole).toString();
156 QString label = GUIUtil::HtmlEscape(ttm->data(index, TransactionTableModel::LabelRole).toString());
157
158 Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName()));
159 }
160
gotoOverviewPage()161 void WalletView::gotoOverviewPage()
162 {
163 setCurrentWidget(overviewPage);
164 }
165
gotoHistoryPage()166 void WalletView::gotoHistoryPage()
167 {
168 setCurrentWidget(transactionsPage);
169 }
170
gotoReceiveCoinsPage()171 void WalletView::gotoReceiveCoinsPage()
172 {
173 setCurrentWidget(receiveCoinsPage);
174 }
175
gotoSendCoinsPage(QString addr)176 void WalletView::gotoSendCoinsPage(QString addr)
177 {
178 setCurrentWidget(sendCoinsPage);
179
180 if (!addr.isEmpty())
181 sendCoinsPage->setAddress(addr);
182 }
183
gotoSignMessageTab(QString addr)184 void WalletView::gotoSignMessageTab(QString addr)
185 {
186 // calls show() in showTab_SM()
187 SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this);
188 signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose);
189 signVerifyMessageDialog->setModel(walletModel);
190 signVerifyMessageDialog->showTab_SM(true);
191
192 if (!addr.isEmpty())
193 signVerifyMessageDialog->setAddress_SM(addr);
194 }
195
gotoVerifyMessageTab(QString addr)196 void WalletView::gotoVerifyMessageTab(QString addr)
197 {
198 // calls show() in showTab_VM()
199 SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this);
200 signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose);
201 signVerifyMessageDialog->setModel(walletModel);
202 signVerifyMessageDialog->showTab_VM(true);
203
204 if (!addr.isEmpty())
205 signVerifyMessageDialog->setAddress_VM(addr);
206 }
207
gotoLoadPSBT(bool from_clipboard)208 void WalletView::gotoLoadPSBT(bool from_clipboard)
209 {
210 std::string data;
211
212 if (from_clipboard) {
213 std::string raw = QApplication::clipboard()->text().toStdString();
214 bool invalid;
215 data = DecodeBase64(raw, &invalid);
216 if (invalid) {
217 Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR);
218 return;
219 }
220 } else {
221 QString filename = GUIUtil::getOpenFileName(this,
222 tr("Load Transaction Data"), QString(),
223 tr("Partially Signed Transaction (*.psbt)"), nullptr);
224 if (filename.isEmpty()) return;
225 if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
226 Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
227 return;
228 }
229 std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
230 data = std::string(std::istreambuf_iterator<char>{in}, {});
231 }
232
233 std::string error;
234 PartiallySignedTransaction psbtx;
235 if (!DecodeRawPSBT(psbtx, data, error)) {
236 Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
237 return;
238 }
239
240 PSBTOperationsDialog* dlg = new PSBTOperationsDialog(this, walletModel, clientModel);
241 dlg->openWithPSBT(psbtx);
242 dlg->setAttribute(Qt::WA_DeleteOnClose);
243 dlg->exec();
244 }
245
handlePaymentRequest(const SendCoinsRecipient & recipient)246 bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
247 {
248 return sendCoinsPage->handlePaymentRequest(recipient);
249 }
250
showOutOfSyncWarning(bool fShow)251 void WalletView::showOutOfSyncWarning(bool fShow)
252 {
253 overviewPage->showOutOfSyncWarning(fShow);
254 }
255
updateEncryptionStatus()256 void WalletView::updateEncryptionStatus()
257 {
258 Q_EMIT encryptionStatusChanged();
259 }
260
encryptWallet()261 void WalletView::encryptWallet()
262 {
263 if(!walletModel)
264 return;
265 AskPassphraseDialog dlg(AskPassphraseDialog::Encrypt, this);
266 dlg.setModel(walletModel);
267 dlg.exec();
268
269 updateEncryptionStatus();
270 }
271
backupWallet()272 void WalletView::backupWallet()
273 {
274 QString filename = GUIUtil::getSaveFileName(this,
275 tr("Backup Wallet"), QString(),
276 //: Name of the wallet data file format.
277 tr("Wallet Data") + QLatin1String(" (*.dat)"), nullptr);
278
279 if (filename.isEmpty())
280 return;
281
282 if (!walletModel->wallet().backupWallet(filename.toLocal8Bit().data())) {
283 Q_EMIT message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to %1.").arg(filename),
284 CClientUIInterface::MSG_ERROR);
285 }
286 else {
287 Q_EMIT message(tr("Backup Successful"), tr("The wallet data was successfully saved to %1.").arg(filename),
288 CClientUIInterface::MSG_INFORMATION);
289 }
290 }
291
changePassphrase()292 void WalletView::changePassphrase()
293 {
294 AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
295 dlg.setModel(walletModel);
296 dlg.exec();
297 }
298
unlockWallet()299 void WalletView::unlockWallet()
300 {
301 if(!walletModel)
302 return;
303 // Unlock wallet when requested by wallet model
304 if (walletModel->getEncryptionStatus() == WalletModel::Locked)
305 {
306 AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
307 dlg.setModel(walletModel);
308 dlg.exec();
309 }
310 }
311
usedSendingAddresses()312 void WalletView::usedSendingAddresses()
313 {
314 if(!walletModel)
315 return;
316
317 GUIUtil::bringToFront(usedSendingAddressesPage);
318 }
319
usedReceivingAddresses()320 void WalletView::usedReceivingAddresses()
321 {
322 if(!walletModel)
323 return;
324
325 GUIUtil::bringToFront(usedReceivingAddressesPage);
326 }
327
showProgress(const QString & title,int nProgress)328 void WalletView::showProgress(const QString &title, int nProgress)
329 {
330 if (nProgress == 0) {
331 progressDialog = new QProgressDialog(title, tr("Cancel"), 0, 100);
332 GUIUtil::PolishProgressDialog(progressDialog);
333 progressDialog->setWindowModality(Qt::ApplicationModal);
334 progressDialog->setAutoClose(false);
335 progressDialog->setValue(0);
336 } else if (nProgress == 100) {
337 if (progressDialog) {
338 progressDialog->close();
339 progressDialog->deleteLater();
340 progressDialog = nullptr;
341 }
342 } else if (progressDialog) {
343 if (progressDialog->wasCanceled()) {
344 getWalletModel()->wallet().abortRescan();
345 } else {
346 progressDialog->setValue(nProgress);
347 }
348 }
349 }
350