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