receivecoinsdialog.cpp raw

   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 <wallet/wallet.h>
   6  
   7  #include <qt/receivecoinsdialog.h>
   8  #include <qt/forms/ui_receivecoinsdialog.h>
   9  
  10  #include <qt/addresstablemodel.h>
  11  #include <qt/guiutil.h>
  12  #include <qt/optionsmodel.h>
  13  #include <qt/platformstyle.h>
  14  #include <qt/receiverequestdialog.h>
  15  #include <qt/recentrequeststablemodel.h>
  16  #include <qt/walletmodel.h>
  17  
  18  #include <QAction>
  19  #include <QCursor>
  20  #include <QMessageBox>
  21  #include <QScrollBar>
  22  #include <QSettings>
  23  #include <QTextDocument>
  24  
  25  ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
  26      QDialog(parent, GUIUtil::dialog_flags),
  27      ui(new Ui::ReceiveCoinsDialog),
  28      platformStyle(_platformStyle)
  29  {
  30      ui->setupUi(this);
  31  
  32      if (!_platformStyle->getImagesOnButtons()) {
  33          ui->clearButton->setIcon(QIcon());
  34          ui->receiveButton->setIcon(QIcon());
  35          ui->showRequestButton->setIcon(QIcon());
  36          ui->removeRequestButton->setIcon(QIcon());
  37      } else {
  38          ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
  39          ui->receiveButton->setIcon(_platformStyle->SingleColorIcon(":/icons/receiving_addresses"));
  40          ui->showRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/eye"));
  41          ui->removeRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
  42      }
  43  
  44      // context menu
  45      contextMenu = new QMenu(this);
  46      contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI);
  47      contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress);
  48      copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel);
  49      copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage);
  50      copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount);
  51      connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu);
  52  
  53      connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear);
  54  
  55      QTableView* tableView = ui->recentRequestsView;
  56      tableView->verticalHeader()->hide();
  57      tableView->setAlternatingRowColors(true);
  58      tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
  59      tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
  60  
  61      QSettings settings;
  62      if (!tableView->horizontalHeader()->restoreState(settings.value("RecentRequestsViewHeaderState").toByteArray())) {
  63          tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH);
  64          tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH);
  65          tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
  66          tableView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH);
  67          tableView->horizontalHeader()->setStretchLastSection(true);
  68      }
  69  }
  70  
  71  void ReceiveCoinsDialog::setModel(WalletModel *_model)
  72  {
  73      this->model = _model;
  74  
  75      if(_model && _model->getOptionsModel())
  76      {
  77          _model->getRecentRequestsTableModel()->sort(RecentRequestsTableModel::Date, Qt::DescendingOrder);
  78          connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveCoinsDialog::updateDisplayUnit);
  79          updateDisplayUnit();
  80  
  81          QTableView* tableView = ui->recentRequestsView;
  82          tableView->setModel(_model->getRecentRequestsTableModel());
  83          tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder);
  84  
  85          connect(tableView->selectionModel(),
  86              &QItemSelectionModel::selectionChanged, this,
  87              &ReceiveCoinsDialog::recentRequestsView_selectionChanged);
  88  
  89          // Populate address type dropdown and select default
  90          auto add_address_type = [&](OutputType type, const QString& text, const QString& tooltip) {
  91              const auto index = ui->addressType->count();
  92              ui->addressType->addItem(text, (int) type);
  93              ui->addressType->setItemData(index, tooltip, Qt::ToolTipRole);
  94              if (model->wallet().getDefaultAddressType() == type) ui->addressType->setCurrentIndex(index);
  95          };
  96          add_address_type(OutputType::LEGACY, tr("Base58 (Legacy)"), tr("Not recommended due to higher fees and less protection against typos."));
  97          add_address_type(OutputType::P2SH_SEGWIT, tr("Base58 (P2SH-SegWit)"), tr("Generates an address compatible with older wallets."));
  98          add_address_type(OutputType::BECH32, tr("Bech32 (SegWit)"), tr("Generates a native segwit address (BIP-173). Some old wallets don't support it."));
  99          if (model->wallet().taprootEnabled()) {
 100              add_address_type(OutputType::BECH32M, tr("Bech32m (Taproot)"), tr("Bech32m (BIP-350) is an upgrade to Bech32, wallet support is still limited."));
 101          }
 102  
 103          // Set the button to be enabled or disabled based on whether the wallet can give out new addresses.
 104          ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
 105  
 106          // Enable/disable the receive button if the wallet is now able/unable to give out new addresses.
 107          connect(model, &WalletModel::canGetAddressesChanged, [this] {
 108              ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
 109          });
 110      }
 111  }
 112  
 113  ReceiveCoinsDialog::~ReceiveCoinsDialog()
 114  {
 115      QSettings settings;
 116      settings.setValue("RecentRequestsViewHeaderState", ui->recentRequestsView->horizontalHeader()->saveState());
 117      delete ui;
 118  }
 119  
 120  void ReceiveCoinsDialog::clear()
 121  {
 122      ui->reqAmount->clear();
 123      ui->reqLabel->setText("");
 124      ui->reqMessage->setText("");
 125      updateDisplayUnit();
 126  }
 127  
 128  void ReceiveCoinsDialog::reject()
 129  {
 130      clear();
 131  }
 132  
 133  void ReceiveCoinsDialog::accept()
 134  {
 135      clear();
 136  }
 137  
 138  void ReceiveCoinsDialog::updateDisplayUnit()
 139  {
 140      if(model && model->getOptionsModel())
 141      {
 142          ui->reqAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
 143      }
 144  }
 145  
 146  void ReceiveCoinsDialog::on_receiveButton_clicked()
 147  {
 148      if(!model || !model->getOptionsModel() || !model->getAddressTableModel() || !model->getRecentRequestsTableModel())
 149          return;
 150  
 151      QString address;
 152      QString label = ui->reqLabel->text();
 153      /* Generate new receiving address */
 154      const OutputType address_type = (OutputType)ui->addressType->currentData().toInt();
 155      address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type);
 156  
 157      switch(model->getAddressTableModel()->getEditStatus())
 158      {
 159      case AddressTableModel::EditStatus::OK: {
 160          // Success
 161          SendCoinsRecipient info(address, label,
 162              ui->reqAmount->value(), ui->reqMessage->text());
 163          ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
 164          dialog->setAttribute(Qt::WA_DeleteOnClose);
 165          dialog->setModel(model);
 166          dialog->setInfo(info);
 167          dialog->show();
 168  
 169          /* Store request for later reference */
 170          model->getRecentRequestsTableModel()->addNewRequest(info);
 171          break;
 172      }
 173      case AddressTableModel::EditStatus::WALLET_UNLOCK_FAILURE:
 174          QMessageBox::critical(this, windowTitle(),
 175              tr("Could not unlock wallet."),
 176              QMessageBox::Ok, QMessageBox::Ok);
 177          break;
 178      case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE:
 179          QMessageBox::critical(this, windowTitle(),
 180              tr("Could not generate new %1 address").arg(QString::fromStdString(FormatOutputType(address_type))),
 181              QMessageBox::Ok, QMessageBox::Ok);
 182          break;
 183      // These aren't valid return values for our action
 184      case AddressTableModel::EditStatus::INVALID_ADDRESS:
 185      case AddressTableModel::EditStatus::DUPLICATE_ADDRESS:
 186      case AddressTableModel::EditStatus::NO_CHANGES:
 187          assert(false);
 188      }
 189      clear();
 190  }
 191  
 192  void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index)
 193  {
 194      const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel();
 195      ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
 196      dialog->setModel(model);
 197      dialog->setInfo(submodel->entry(index.row()).recipient);
 198      dialog->setAttribute(Qt::WA_DeleteOnClose);
 199      dialog->show();
 200  }
 201  
 202  void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
 203  {
 204      // Enable Show/Remove buttons only if anything is selected.
 205      bool enable = !ui->recentRequestsView->selectionModel()->selectedRows().isEmpty();
 206      ui->showRequestButton->setEnabled(enable);
 207      ui->removeRequestButton->setEnabled(enable);
 208  }
 209  
 210  void ReceiveCoinsDialog::on_showRequestButton_clicked()
 211  {
 212      if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
 213          return;
 214      QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
 215  
 216      for (const QModelIndex& index : selection) {
 217          on_recentRequestsView_doubleClicked(index);
 218      }
 219  }
 220  
 221  void ReceiveCoinsDialog::on_removeRequestButton_clicked()
 222  {
 223      if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
 224          return;
 225      QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
 226      if(selection.empty())
 227          return;
 228      // correct for selection mode ContiguousSelection
 229      QModelIndex firstIndex = selection.at(0);
 230      model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent());
 231  }
 232  
 233  QModelIndex ReceiveCoinsDialog::selectedRow()
 234  {
 235      if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
 236          return QModelIndex();
 237      QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
 238      if(selection.empty())
 239          return QModelIndex();
 240      // correct for selection mode ContiguousSelection
 241      QModelIndex firstIndex = selection.at(0);
 242      return firstIndex;
 243  }
 244  
 245  // copy column of selected row to clipboard
 246  void ReceiveCoinsDialog::copyColumnToClipboard(int column)
 247  {
 248      QModelIndex firstIndex = selectedRow();
 249      if (!firstIndex.isValid()) {
 250          return;
 251      }
 252      GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString());
 253  }
 254  
 255  // context menu
 256  void ReceiveCoinsDialog::showMenu(const QPoint &point)
 257  {
 258      const QModelIndex sel = selectedRow();
 259      if (!sel.isValid()) {
 260          return;
 261      }
 262  
 263      // disable context menu actions when appropriate
 264      const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
 265      const RecentRequestEntry& req = submodel->entry(sel.row());
 266      copyLabelAction->setDisabled(req.recipient.label.isEmpty());
 267      copyMessageAction->setDisabled(req.recipient.message.isEmpty());
 268      copyAmountAction->setDisabled(req.recipient.amount == 0);
 269  
 270      contextMenu->exec(QCursor::pos());
 271  }
 272  
 273  // context menu action: copy URI
 274  void ReceiveCoinsDialog::copyURI()
 275  {
 276      QModelIndex sel = selectedRow();
 277      if (!sel.isValid()) {
 278          return;
 279      }
 280  
 281      const RecentRequestsTableModel * const submodel = model->getRecentRequestsTableModel();
 282      const QString uri = GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient);
 283      GUIUtil::setClipboard(uri);
 284  }
 285  
 286  // context menu action: copy address
 287  void ReceiveCoinsDialog::copyAddress()
 288  {
 289      const QModelIndex sel = selectedRow();
 290      if (!sel.isValid()) {
 291          return;
 292      }
 293  
 294      const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
 295      const QString address = submodel->entry(sel.row()).recipient.address;
 296      GUIUtil::setClipboard(address);
 297  }
 298  
 299  // context menu action: copy label
 300  void ReceiveCoinsDialog::copyLabel()
 301  {
 302      copyColumnToClipboard(RecentRequestsTableModel::Label);
 303  }
 304  
 305  // context menu action: copy message
 306  void ReceiveCoinsDialog::copyMessage()
 307  {
 308      copyColumnToClipboard(RecentRequestsTableModel::Message);
 309  }
 310  
 311  // context menu action: copy amount
 312  void ReceiveCoinsDialog::copyAmount()
 313  {
 314      copyColumnToClipboard(RecentRequestsTableModel::Amount);
 315  }
 316