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