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