walletframe.cpp raw
1 // Copyright (c) 2011-2022 The Limenka 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/pairingpage.h>
6 #include <qt/walletframe.h>
7
8 #include <node/interface_ui.h>
9 #include <psbt.h>
10 #include <qt/guiutil.h>
11 #include <qt/overviewpage.h>
12 #include <qt/psbtoperationsdialog.h>
13 #include <qt/walletmodel.h>
14 #include <qt/walletview.h>
15 #include <util/fs.h>
16 #include <util/fs_helpers.h>
17
18 #include <cassert>
19 #include <fstream>
20 #include <string>
21
22 #include <Qt>
23 #include <QApplication>
24 #include <QClipboard>
25 #include <QHBoxLayout>
26 #include <QLabel>
27 #include <QPushButton>
28 #include <QVBoxLayout>
29 #include <QWidget>
30
31 WalletFrame::WalletFrame(const PlatformStyle* _platformStyle, QWidget* parent)
32 : QFrame(parent),
33 platformStyle(_platformStyle),
34 m_size_hint(OverviewPage{platformStyle, nullptr}.sizeHint())
35 {
36 // Leave HBox hook for adding a list view later
37 QHBoxLayout *walletFrameLayout = new QHBoxLayout(this);
38 setContentsMargins(0,0,0,0);
39 walletStack = new QStackedWidget(this);
40 m_global_stack = new QStackedWidget(this);
41 m_global_stack->addWidget(walletStack);
42 walletFrameLayout->setContentsMargins(0,0,0,0);
43 walletFrameLayout->addWidget(m_global_stack);
44
45 // hbox for no wallet
46 QWidget* no_wallet_group = new QWidget(walletStack);
47 QVBoxLayout* no_wallet_layout = new QVBoxLayout(no_wallet_group);
48
49 m_label_alerts = new QLabel(this);
50 m_label_alerts->setVisible(false);
51 m_label_alerts->setStyleSheet("QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; }");
52 m_label_alerts->setWordWrap(true);
53 m_label_alerts->setMargin(3);
54 m_label_alerts->setTextInteractionFlags(Qt::TextSelectableByMouse);
55 no_wallet_layout->addWidget(m_label_alerts, 0, Qt::AlignTop);
56
57 QLabel *noWallet = new QLabel(tr("No wallet has been loaded.\nGo to File > Open Wallet to load a wallet.\n- OR -"));
58 noWallet->setAlignment(Qt::AlignCenter);
59 no_wallet_layout->addWidget(noWallet, 0, Qt::AlignHCenter | Qt::AlignBottom);
60
61 // A button for create wallet dialog
62 QPushButton* create_wallet_button = new QPushButton(tr("Create a new wallet"), walletStack);
63 connect(create_wallet_button, &QPushButton::clicked, this, &WalletFrame::createWalletButtonClicked);
64 no_wallet_layout->addWidget(create_wallet_button, 0, Qt::AlignHCenter | Qt::AlignTop);
65 no_wallet_group->setLayout(no_wallet_layout);
66
67 walletStack->addWidget(no_wallet_group);
68
69 m_page_pairing = new PairingPage(this);
70 m_global_stack->addWidget(m_page_pairing);
71 }
72
73 WalletFrame::~WalletFrame() = default;
74
75 void WalletFrame::setClientModel(ClientModel *_clientModel)
76 {
77 this->clientModel = _clientModel;
78
79 m_page_pairing->setClientModel(_clientModel);
80
81 for (auto i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) {
82 i.value()->setClientModel(_clientModel);
83 }
84
85 if (_clientModel) {
86 connect(_clientModel, &ClientModel::alertsChanged, this, &WalletFrame::updateAlerts);
87 updateAlerts(_clientModel->getStatusBarWarnings());
88 }
89 }
90
91 bool WalletFrame::addView(WalletView* walletView)
92 {
93 if (!clientModel) return false;
94
95 if (mapWalletViews.count(walletView->getWalletModel()) > 0) return false;
96
97 walletView->setClientModel(clientModel);
98 walletView->showOutOfSyncWarning(bOutOfSync);
99
100 WalletView* current_wallet_view = currentWalletView();
101 if (current_wallet_view) {
102 walletView->setCurrentIndex(current_wallet_view->currentIndex());
103 } else {
104 walletView->gotoOverviewPage();
105 }
106
107 walletStack->addWidget(walletView);
108 mapWalletViews[walletView->getWalletModel()] = walletView;
109
110 return true;
111 }
112
113 void WalletFrame::setCurrentWallet(WalletModel* wallet_model)
114 {
115 if (mapWalletViews.count(wallet_model) == 0) return;
116
117 // Stop the effect of hidden widgets on the size hint of the shown one in QStackedWidget.
118 WalletView* view_about_to_hide = currentWalletView();
119 if (view_about_to_hide) {
120 QSizePolicy sp = view_about_to_hide->sizePolicy();
121 sp.setHorizontalPolicy(QSizePolicy::Ignored);
122 view_about_to_hide->setSizePolicy(sp);
123 }
124
125 WalletView *walletView = mapWalletViews.value(wallet_model);
126 assert(walletView);
127
128 // Set or restore the default QSizePolicy which could be set to QSizePolicy::Ignored previously.
129 QSizePolicy sp = walletView->sizePolicy();
130 sp.setHorizontalPolicy(QSizePolicy::Preferred);
131 walletView->setSizePolicy(sp);
132 walletView->updateGeometry();
133
134 walletStack->setCurrentWidget(walletView);
135
136 Q_EMIT currentWalletSet();
137 }
138
139 void WalletFrame::removeWallet(WalletModel* wallet_model)
140 {
141 if (mapWalletViews.count(wallet_model) == 0) return;
142
143 WalletView *walletView = mapWalletViews.take(wallet_model);
144 walletStack->removeWidget(walletView);
145 delete walletView;
146 }
147
148 void WalletFrame::removeAllWallets()
149 {
150 QMap<WalletModel*, WalletView*>::const_iterator i;
151 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
152 walletStack->removeWidget(i.value());
153 mapWalletViews.clear();
154 }
155
156 bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient)
157 {
158 WalletView *walletView = currentWalletView();
159 if (!walletView)
160 return false;
161
162 return walletView->handlePaymentRequest(recipient);
163 }
164
165 void WalletFrame::showOutOfSyncWarning(bool fShow)
166 {
167 bOutOfSync = fShow;
168 QMap<WalletModel*, WalletView*>::const_iterator i;
169 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
170 i.value()->showOutOfSyncWarning(fShow);
171 }
172
173 void WalletFrame::gotoOverviewPage()
174 {
175 QMap<WalletModel*, WalletView*>::const_iterator i;
176 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
177 i.value()->gotoOverviewPage();
178 m_global_stack->setCurrentWidget(walletStack);
179 }
180
181 void WalletFrame::gotoPairingPage()
182 {
183 m_global_stack->setCurrentWidget(m_page_pairing);
184 }
185
186 void WalletFrame::gotoHistoryPage()
187 {
188 QMap<WalletModel*, WalletView*>::const_iterator i;
189 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
190 i.value()->gotoHistoryPage();
191 m_global_stack->setCurrentWidget(walletStack);
192 }
193
194 void WalletFrame::gotoReceiveCoinsPage()
195 {
196 QMap<WalletModel*, WalletView*>::const_iterator i;
197 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
198 i.value()->gotoReceiveCoinsPage();
199 m_global_stack->setCurrentWidget(walletStack);
200 }
201
202 void WalletFrame::gotoSendCoinsPage(QString addr)
203 {
204 QMap<WalletModel*, WalletView*>::const_iterator i;
205 for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
206 i.value()->gotoSendCoinsPage(addr);
207 m_global_stack->setCurrentWidget(walletStack);
208 }
209
210 void WalletFrame::gotoSignMessageTab(QString addr)
211 {
212 WalletView *walletView = currentWalletView();
213 if (walletView)
214 walletView->gotoSignMessageTab(addr);
215 }
216
217 void WalletFrame::gotoVerifyMessageTab(QString addr)
218 {
219 WalletView *walletView = currentWalletView();
220 if (walletView)
221 walletView->gotoVerifyMessageTab(addr);
222 }
223
224 void WalletFrame::gotoSweepPrivKeyDialog()
225 {
226 WalletView *walletView = currentWalletView();
227 if (walletView)
228 walletView->gotoSweepPrivKeyDialog();
229 }
230
231 void WalletFrame::gotoLoadPSBT(bool from_clipboard)
232 {
233 std::vector<unsigned char> data;
234
235 if (from_clipboard) {
236 std::string raw = QApplication::clipboard()->text().toStdString();
237 auto result = DecodeBase64(raw);
238 if (!result) {
239 Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR);
240 return;
241 }
242 data = std::move(*result);
243 } else {
244 QString filename = GUIUtil::getOpenFileName(this,
245 tr("Load Transaction Data"), QString(),
246 tr("Partially Signed Transaction (*.psbt)"), nullptr);
247 if (filename.isEmpty()) return;
248 if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
249 Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
250 return;
251 }
252 std::ifstream in{filename.toLocal8Bit().data(), std::ios::binary};
253 data.assign(std::istreambuf_iterator<char>{in}, {});
254
255 // Some psbt files may be base64 strings in the file rather than binary data
256 std::string b64_str{data.begin(), data.end()};
257 b64_str.erase(b64_str.find_last_not_of(" \t\n\r\f\v") + 1); // Trim trailing whitespace
258 auto b64_dec = DecodeBase64(b64_str);
259 if (b64_dec.has_value()) {
260 data = b64_dec.value();
261 }
262 }
263
264 std::string error;
265 PartiallySignedTransaction psbtx;
266 if (!DecodeRawPSBT(psbtx, MakeByteSpan(data), error)) {
267 Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
268 return;
269 }
270
271 auto dlg = new PSBTOperationsDialog(this, currentWalletModel(), clientModel);
272 dlg->openWithPSBT(psbtx);
273 GUIUtil::ShowModalDialogAsynchronously(dlg, Qt::NonModal);
274 }
275
276 void WalletFrame::encryptWallet()
277 {
278 WalletView *walletView = currentWalletView();
279 if (walletView)
280 walletView->encryptWallet();
281 }
282
283 void WalletFrame::backupWallet()
284 {
285 WalletView *walletView = currentWalletView();
286 if (walletView)
287 walletView->backupWallet();
288 }
289
290 void WalletFrame::changePassphrase()
291 {
292 WalletView *walletView = currentWalletView();
293 if (walletView)
294 walletView->changePassphrase();
295 }
296
297 void WalletFrame::unlockWallet()
298 {
299 WalletView *walletView = currentWalletView();
300 if (walletView)
301 walletView->unlockWallet();
302 }
303
304 void WalletFrame::usedSendingAddresses()
305 {
306 WalletView *walletView = currentWalletView();
307 if (walletView)
308 walletView->usedSendingAddresses();
309 }
310
311 void WalletFrame::usedReceivingAddresses()
312 {
313 WalletView *walletView = currentWalletView();
314 if (walletView)
315 walletView->usedReceivingAddresses();
316 }
317
318 WalletView* WalletFrame::currentWalletView() const
319 {
320 return qobject_cast<WalletView*>(walletStack->currentWidget());
321 }
322
323 void WalletFrame::updateAlerts(const QString &warnings)
324 {
325 m_label_alerts->setVisible(!warnings.isEmpty());
326 m_label_alerts->setText(warnings);
327 }
328
329 WalletModel* WalletFrame::currentWalletModel() const
330 {
331 WalletView* wallet_view = currentWalletView();
332 return wallet_view ? wallet_view->getWalletModel() : nullptr;
333 }
334