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 }