/ src / qt / receivecoinsdialog.cpp
receivecoinsdialog.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 <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  }