// Copyright (c) 2011-2022 The Limenka developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::ReceiveCoinsDialog), platformStyle(_platformStyle) { ui->setupUi(this); m_sort_proxy = new QSortFilterProxyModel(this); m_sort_proxy->setSortRole(Qt::UserRole); if (!_platformStyle->getImagesOnButtons()) { ui->clearButton->setIcon(QIcon()); ui->receiveButton->setIcon(QIcon()); ui->showRequestButton->setIcon(QIcon()); ui->removeRequestButton->setIcon(QIcon()); } else { ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove")); ui->receiveButton->setIcon(_platformStyle->SingleColorIcon(":/icons/receiving_addresses")); ui->showRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/eye")); ui->removeRequestButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove")); } // context menu contextMenu = new QMenu(this); contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI); contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress); copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel); copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage); copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount); connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu); connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear); } void ReceiveCoinsDialog::setModel(WalletModel *_model) { this->model = _model; if(_model && _model->getOptionsModel()) { connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveCoinsDialog::updateDisplayUnit); connect(_model->getOptionsModel(), &OptionsModel::fontForMoneyChanged, this, &ReceiveCoinsDialog::updateFontForMoney); updateDisplayUnit(); QTableView* tableView = ui->recentRequestsView; tableView->verticalHeader()->hide(); tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); tableView->setModel(m_sort_proxy); m_sort_proxy->setSourceModel(_model->getRecentRequestsTableModel()); tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder); tableView->setAlternatingRowColors(true); tableView->setSelectionBehavior(QAbstractItemView::SelectRows); tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); QSettings settings; if (!tableView->horizontalHeader()->restoreState(settings.value("RecentRequestsViewHeaderState").toByteArray())) { tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH); tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH); tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); } connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ReceiveCoinsDialog::recentRequestsView_selectionChanged); // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); // Populate address type dropdown and select default auto add_address_type = [&](OutputType type) { const auto [text, tooltip] = GetOutputTypeDescription(type); const auto index = ui->addressType->count(); ui->addressType->addItem(text, (int) type); ui->addressType->setItemData(index, tooltip, Qt::ToolTipRole); if (model->wallet().getDefaultAddressType() == type) ui->addressType->setCurrentIndex(index); }; add_address_type(OutputType::LEGACY); add_address_type(OutputType::P2SH_SEGWIT); add_address_type(OutputType::BECH32); if (model->wallet().taprootEnabled()) { add_address_type(OutputType::BECH32M); } connect(_model->getOptionsModel(), &OptionsModel::addresstypeChanged, [this](const OutputType type) { const int index = ui->addressType->findData((int) type); if (index != -1) ui->addressType->setCurrentIndex(index); }); // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. ui->receiveButton->setEnabled(model->wallet().canGetAddresses()); // Enable/disable the receive button if the wallet is now able/unable to give out new addresses. connect(model, &WalletModel::canGetAddressesChanged, [this] { ui->receiveButton->setEnabled(model->wallet().canGetAddresses()); }); } } ReceiveCoinsDialog::~ReceiveCoinsDialog() { QSettings settings; settings.setValue("RecentRequestsViewHeaderState", ui->recentRequestsView->horizontalHeader()->saveState()); delete ui; } void ReceiveCoinsDialog::clear() { ui->reqAmount->clear(); ui->reqLabel->setText(""); ui->reqMessage->setText(""); updateDisplayUnit(); } void ReceiveCoinsDialog::reject() { clear(); } void ReceiveCoinsDialog::accept() { clear(); } void ReceiveCoinsDialog::updateDisplayUnit() { if(model && model->getOptionsModel()) { ui->reqAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateFontForMoney(); } } void ReceiveCoinsDialog::updateFontForMoney() { if(model && model->getOptionsModel()) { const LimenkaUnit display_unit = model->getOptionsModel()->getDisplayUnit(); const QFont font_for_money = model->getOptionsModel()->getFontForMoney(display_unit); ui->reqAmount->setFontForMoney(font_for_money); } } void ReceiveCoinsDialog::on_receiveButton_clicked() { if(!model || !model->getOptionsModel() || !model->getAddressTableModel() || !model->getRecentRequestsTableModel()) return; QString address; QString label = ui->reqLabel->text(); /* Generate new receiving address */ const OutputType address_type = (OutputType)ui->addressType->currentData().toInt(); address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type); switch(model->getAddressTableModel()->getEditStatus()) { case AddressTableModel::EditStatus::OK: { // Success SendCoinsRecipient info(address, label, ui->reqAmount->value(), ui->reqMessage->text()); ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModel(model); dialog->setInfo(info); dialog->show(); /* Store request for later reference */ model->getRecentRequestsTableModel()->addNewRequest(info); break; } case AddressTableModel::EditStatus::WALLET_UNLOCK_FAILURE: QMessageBox::critical(this, windowTitle(), tr("Could not unlock wallet."), QMessageBox::Ok, QMessageBox::Ok); break; case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE: QMessageBox::critical(this, windowTitle(), tr("Could not generate new %1 address").arg(QString::fromStdString(FormatOutputType(address_type))), QMessageBox::Ok, QMessageBox::Ok); break; // These aren't valid return values for our action case AddressTableModel::EditStatus::INVALID_ADDRESS: case AddressTableModel::EditStatus::DUPLICATE_ADDRESS: case AddressTableModel::EditStatus::NO_CHANGES: assert(false); } clear(); } void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index) { QModelIndexList selection = SelectedRows(); if (!selection.isEmpty() && selection.at(0).isValid()) { ShowReceiveRequestDialogForItem(selection.at(0)); } } void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { // Enable Show/Remove buttons only if anything is selected. bool enable = !ui->recentRequestsView->selectionModel()->selectedRows().isEmpty(); ui->showRequestButton->setEnabled(enable); ui->removeRequestButton->setEnabled(enable); } void ReceiveCoinsDialog::on_showRequestButton_clicked() { QModelIndexList selection = SelectedRows(); for (const QModelIndex& index : selection) { ShowReceiveRequestDialogForItem(index); } } void ReceiveCoinsDialog::on_removeRequestButton_clicked() { QModelIndexList selection = SelectedRows(); if(selection.empty()) return; // Collect row indices in a set (sorted) and pass in reverse order to removeRows // to avoid having to keep track of changed source indices after each removal std::set row_indices; for (const QModelIndex& ind : selection) { row_indices.insert(ind.row()); } for (auto row_ind : row_indices | std::views::reverse) { model->getRecentRequestsTableModel()->removeRows(row_ind, 1); } } // We override the virtual resizeEvent of the QWidget to adjust tables column // sizes as the tables width is proportional to the dialogs width. void ReceiveCoinsDialog::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); columnResizingFixer->stretchColumnWidth(RecentRequestsTableModel::Message); } QModelIndexList ReceiveCoinsDialog::SelectedRows() { if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) return QModelIndexList(); QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); QModelIndexList source_mapped; for (auto row : selection) { source_mapped.append(m_sort_proxy->mapToSource(row)); } return source_mapped; } // copy column of selected row to clipboard void ReceiveCoinsDialog::copyColumnToClipboard(int column) { const QModelIndexList sel = SelectedRows(); if (sel.isEmpty()) { return; } const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel(); QString column_value; for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) { if (!sel.at(sel_ind).isValid()) { continue; } column_value += submodel->index(sel.at(sel_ind).row(), column).data(Qt::EditRole).toString(); if (sel_ind < sel.size() - 1) { column_value += QString("\n"); } } GUIUtil::setClipboard(column_value); } void ReceiveCoinsDialog::ShowReceiveRequestDialogForItem(const QModelIndex& index) { if (!index.isValid()) { return; } const RecentRequestsTableModel* submodel = model->getRecentRequestsTableModel(); ReceiveRequestDialog* dialog = new ReceiveRequestDialog(this); dialog->setModel(model); dialog->setInfo(submodel->entry(index.row()).recipient); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } // context menu void ReceiveCoinsDialog::showMenu(const QPoint &point) { const QModelIndexList sel = SelectedRows(); if (sel.isEmpty()) { return; } if (sel.size() == 1 && sel.at(0).isValid()) { // disable context menu actions when appropriate const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel(); const RecentRequestEntry& req = submodel->entry(sel.at(0).row()); copyLabelAction->setDisabled(req.recipient.label.isEmpty()); copyMessageAction->setDisabled(req.recipient.message.isEmpty()); copyAmountAction->setDisabled(req.recipient.amount == 0); } else if (sel.size() > 1) { // multiple selection copyLabelAction->setDisabled(true); copyMessageAction->setDisabled(true); copyAmountAction->setDisabled(true); // disable context menu actions when appropriate const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel(); for (auto selection : sel) { if (!selection.isValid()) { continue; } const RecentRequestEntry& req = submodel->entry(selection.row()); if (!req.recipient.label.isEmpty()) { copyLabelAction->setDisabled(false); } if (!req.recipient.message.isEmpty()) { copyMessageAction->setDisabled(false); } if (req.recipient.amount != 0) { copyAmountAction->setDisabled(false); } } } contextMenu->exec(QCursor::pos()); } // context menu action: copy URI void ReceiveCoinsDialog::copyURI() { const QModelIndexList sel = SelectedRows(); if (sel.isEmpty()) { return; } const RecentRequestsTableModel * const submodel = model->getRecentRequestsTableModel(); QString uri; for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) { if (!sel.at(sel_ind).isValid()) { continue; } const RecentRequestEntry& req = submodel->entry(sel.at(sel_ind).row()); uri += GUIUtil::formatLimenkaURI(req.recipient); if (sel_ind < sel.size() - 1) { uri += QString("\n"); } } GUIUtil::setClipboard(uri); } // context menu action: copy address void ReceiveCoinsDialog::copyAddress() { const QModelIndexList sel = SelectedRows(); if (sel.isEmpty()) { return; } const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel(); QString address; for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) { if (!sel.at(sel_ind).isValid()) { continue; } const RecentRequestEntry& req = submodel->entry(sel.at(sel_ind).row()); address += req.recipient.address; if (sel_ind < sel.size() - 1) { address += QString("\n"); } } GUIUtil::setClipboard(address); } // context menu action: copy label void ReceiveCoinsDialog::copyLabel() { copyColumnToClipboard(RecentRequestsTableModel::Label); } // context menu action: copy message void ReceiveCoinsDialog::copyMessage() { copyColumnToClipboard(RecentRequestsTableModel::Message); } // context menu action: copy amount void ReceiveCoinsDialog::copyAmount() { copyColumnToClipboard(RecentRequestsTableModel::Amount); }