diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 152b05243..d27db97b8 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -1,49 +1,48 @@ // Copyright (c) 2011-2014 The Bitcoin Core 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 OpenURIDialog::OpenURIDialog(const CChainParams ¶ms, QWidget *parent) : QDialog(parent), ui(new Ui::OpenURIDialog), uriScheme(QString::fromStdString(params.CashAddrPrefix())) { ui->setupUi(this); ui->uriEdit->setPlaceholderText(uriScheme + ":"); } OpenURIDialog::~OpenURIDialog() { delete ui; } QString OpenURIDialog::getURI() { return ui->uriEdit->text(); } void OpenURIDialog::accept() { SendCoinsRecipient rcp; if (GUIUtil::parseBitcoinURI(uriScheme, getURI(), &rcp)) { /* Only accept value URIs */ QDialog::accept(); } else { ui->uriEdit->setValid(false); } } void OpenURIDialog::on_selectFileButton_clicked() { QString filename = GUIUtil::getOpenFileName( this, tr("Select payment request file to open"), "", "", nullptr); if (filename.isEmpty()) { return; } QUrl fileUri = QUrl::fromLocalFile(filename); ui->uriEdit->setText(uriScheme + ":?r=" + QUrl::toPercentEncoding(fileUri.toString())); } diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index e455474d0..de981bdb5 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -1,228 +1,229 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin 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 #if defined(HAVE_CONFIG_H) #include /* for USE_QRCODE */ #endif #ifdef USE_QRCODE #include #endif QRImageWidget::QRImageWidget(QWidget *parent) : QLabel(parent), contextMenu(nullptr) { contextMenu = new QMenu(this); QAction *saveImageAction = new QAction(tr("&Save Image..."), this); connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); contextMenu->addAction(saveImageAction); QAction *copyImageAction = new QAction(tr("&Copy Image"), this); connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); contextMenu->addAction(copyImageAction); } bool QRImageWidget::hasPixmap() const { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) return !pixmap(Qt::ReturnByValue).isNull(); #else return pixmap() != nullptr; #endif } QImage QRImageWidget::exportImage() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) return pixmap(Qt::ReturnByValue).toImage(); #else return hasPixmap() ? pixmap()->toImage() : QImage(); #endif } void QRImageWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && hasPixmap()) { event->accept(); QMimeData *mimeData = new QMimeData; mimeData->setImageData(exportImage()); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(); } else { QLabel::mousePressEvent(event); } } void QRImageWidget::saveImage() { if (!hasPixmap()) { return; } QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); if (!fn.isEmpty()) { exportImage().save(fn); } } void QRImageWidget::copyImage() { if (!hasPixmap()) { return; } QApplication::clipboard()->setImage(exportImage()); } void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) { if (!hasPixmap()) { return; } contextMenu->exec(event->globalPos()); } ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), model(nullptr) { ui->setupUi(this); #ifndef USE_QRCODE ui->btnSaveAs->setVisible(false); ui->lblQRCode->setVisible(false); #endif connect(ui->btnSaveAs, &QPushButton::clicked, ui->lblQRCode, &QRImageWidget::saveImage); } ReceiveRequestDialog::~ReceiveRequestDialog() { delete ui; } void ReceiveRequestDialog::setModel(WalletModel *_model) { this->model = _model; if (_model) { connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveRequestDialog::update); } // update the display unit if necessary update(); } void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) { this->info = _info; update(); } void ReceiveRequestDialog::update() { if (!model) { return; } QString target = info.label; if (target.isEmpty()) { target = info.address; } setWindowTitle(tr("Request payment to %1").arg(target)); QString uri = GUIUtil::formatBitcoinURI(info); ui->btnSaveAs->setEnabled(false); QString html; html += ""; html += "" + tr("Payment information") + "
"; html += "" + tr("URI") + ": "; html += "" + GUIUtil::HtmlEscape(uri) + "
"; html += "" + tr("Address") + ": " + GUIUtil::HtmlEscape(info.address) + "
"; if (info.amount != Amount::zero()) { html += "" + tr("Amount") + ": " + BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), info.amount) + "
"; } if (!info.label.isEmpty()) { html += "" + tr("Label") + ": " + GUIUtil::HtmlEscape(info.label) + "
"; } if (!info.message.isEmpty()) { html += "" + tr("Message") + ": " + GUIUtil::HtmlEscape(info.message) + "
"; } if (model->isMultiwallet()) { html += "" + tr("Wallet") + ": " + GUIUtil::HtmlEscape(model->getWalletName()) + "
"; } ui->outUri->setText(html); #ifdef USE_QRCODE ui->lblQRCode->setText(""); if (!uri.isEmpty()) { // limit URI length if (uri.length() > MAX_URI_LENGTH) { ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce " "the text for label / message.")); } else { QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (!code) { ui->lblQRCode->setText(tr("Error encoding URI into QR Code.")); return; } QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); qrImage.fill(0xffffff); uint8_t *p = code->data; for (int y = 0; y < code->width; y++) { for (int x = 0; x < code->width; x++) { qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); p++; } } QRcode_free(code); QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE + 20, QImage::Format_RGB32); qrAddrImage.fill(0xffffff); QPainter painter(&qrAddrImage); painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); QFont font = GUIUtil::fixedPitchFont(); QRect paddedRect = qrAddrImage.rect(); // calculate ideal font size qreal font_size = GUIUtil::calculateIdealFontSize( paddedRect.width() - 20, info.address, font); font.setPointSizeF(font_size); paddedRect.setHeight(QR_IMAGE_SIZE + 12); painter.drawText(paddedRect, Qt::AlignBottom | Qt::AlignCenter, info.address); painter.end(); ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); ui->btnSaveAs->setEnabled(true); } } #endif } void ReceiveRequestDialog::on_btnCopyURI_clicked() { GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(info)); } void ReceiveRequestDialog::on_btnCopyAddress_clicked() { GUIUtil::setClipboard(info.address); } diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index 45626cc6c..f08b96c8a 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -1,70 +1,71 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_RECEIVEREQUESTDIALOG_H #define BITCOIN_QT_RECEIVEREQUESTDIALOG_H #include -#include #include #include #include #include +class WalletModel; + namespace Ui { class ReceiveRequestDialog; } QT_BEGIN_NAMESPACE class QMenu; QT_END_NAMESPACE /* Label widget for QR code. This image can be dragged, dropped, copied and * saved * to disk. */ class QRImageWidget : public QLabel { Q_OBJECT public: explicit QRImageWidget(QWidget *parent = nullptr); bool hasPixmap() const; QImage exportImage(); public Q_SLOTS: void saveImage(); void copyImage(); protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void contextMenuEvent(QContextMenuEvent *event) override; private: QMenu *contextMenu; }; class ReceiveRequestDialog : public QDialog { Q_OBJECT public: explicit ReceiveRequestDialog(QWidget *parent = nullptr); ~ReceiveRequestDialog(); void setModel(WalletModel *model); void setInfo(const SendCoinsRecipient &info); private Q_SLOTS: void on_btnCopyURI_clicked(); void on_btnCopyAddress_clicked(); void update(); private: Ui::ReceiveRequestDialog *ui; WalletModel *model; SendCoinsRecipient info; }; #endif // BITCOIN_QT_RECEIVEREQUESTDIALOG_H diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index fb928a03b..45934034d 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -1,240 +1,241 @@ // Copyright (c) 2011-2016 The Bitcoin Core 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 RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent) { // Load entries from wallet std::vector vReceiveRequests; parent->loadReceiveRequests(vReceiveRequests); for (const std::string &request : vReceiveRequests) { addNewRequest(request); } /* These columns must match the indices in the ColumnIndex enumeration */ columns << tr("Date") << tr("Label") << tr("Message") << getAmountTitle(); connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &RecentRequestsTableModel::updateDisplayUnit); } RecentRequestsTableModel::~RecentRequestsTableModel() { /* Intentionally left empty */ } int RecentRequestsTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return list.length(); } int RecentRequestsTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QVariant RecentRequestsTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= list.length()) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { const RecentRequestEntry *rec = &list[index.row()]; switch (index.column()) { case Date: return GUIUtil::dateTimeStr(rec->date); case Label: if (rec->recipient.label.isEmpty() && role == Qt::DisplayRole) { return tr("(no label)"); } else { return rec->recipient.label; } case Message: if (rec->recipient.message.isEmpty() && role == Qt::DisplayRole) { return tr("(no message)"); } else { return rec->recipient.message; } case Amount: if (rec->recipient.amount == ::Amount::zero() && role == Qt::DisplayRole) { return tr("(no amount requested)"); } else if (role == Qt::EditRole) { return BitcoinUnits::format( walletModel->getOptionsModel()->getDisplayUnit(), rec->recipient.amount, false, BitcoinUnits::separatorNever); } else { return BitcoinUnits::format( walletModel->getOptionsModel()->getDisplayUnit(), rec->recipient.amount); } } } else if (role == Qt::TextAlignmentRole) { if (index.column() == Amount) { return (int)(Qt::AlignRight | Qt::AlignVCenter); } } return QVariant(); } bool RecentRequestsTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { return true; } QVariant RecentRequestsTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && section < columns.size()) { return columns[section]; } } return QVariant(); } /** Updates the column title to "Amount (DisplayUnit)" and emits * headerDataChanged() signal for table headers to react. */ void RecentRequestsTableModel::updateAmountColumnTitle() { columns[Amount] = getAmountTitle(); Q_EMIT headerDataChanged(Qt::Horizontal, Amount, Amount); } /** Gets title for amount column including current display unit if optionsModel * reference available. */ QString RecentRequestsTableModel::getAmountTitle() { return (this->walletModel->getOptionsModel() != nullptr) ? tr("Requested") + " (" + BitcoinUnits::shortName( this->walletModel->getOptionsModel() ->getDisplayUnit()) + ")" : ""; } QModelIndex RecentRequestsTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); return createIndex(row, column); } bool RecentRequestsTableModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); if (count > 0 && row >= 0 && (row + count) <= list.size()) { for (int i = 0; i < count; ++i) { const RecentRequestEntry *rec = &list[row + i]; if (!walletModel->saveReceiveRequest( rec->recipient.address.toStdString(), rec->id, "")) { return false; } } beginRemoveRows(parent, row, row + count - 1); list.erase(list.begin() + row, list.begin() + row + count); endRemoveRows(); return true; } else { return false; } } Qt::ItemFlags RecentRequestsTableModel::flags(const QModelIndex &index) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } // called when adding a request from the GUI void RecentRequestsTableModel::addNewRequest( const SendCoinsRecipient &recipient) { RecentRequestEntry newEntry; newEntry.id = ++nReceiveRequestsMaxId; newEntry.date = QDateTime::currentDateTime(); newEntry.recipient = recipient; CDataStream ss(SER_DISK, CLIENT_VERSION); ss << newEntry; if (!walletModel->saveReceiveRequest(recipient.address.toStdString(), newEntry.id, ss.str())) { return; } addNewRequest(newEntry); } // called from ctor when loading from wallet void RecentRequestsTableModel::addNewRequest(const std::string &recipient) { std::vector data(recipient.begin(), recipient.end()); CDataStream ss(data, SER_DISK, CLIENT_VERSION); RecentRequestEntry entry; ss >> entry; // should not happen if (entry.id == 0) { return; } if (entry.id > nReceiveRequestsMaxId) { nReceiveRequestsMaxId = entry.id; } addNewRequest(entry); } // actually add to table in GUI void RecentRequestsTableModel::addNewRequest(RecentRequestEntry &recipient) { beginInsertRows(QModelIndex(), 0, 0); list.prepend(recipient); endInsertRows(); } void RecentRequestsTableModel::sort(int column, Qt::SortOrder order) { std::sort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order)); Q_EMIT dataChanged( index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); } void RecentRequestsTableModel::updateDisplayUnit() { updateAmountColumnTitle(); } bool RecentRequestEntryLessThan::operator()(RecentRequestEntry &left, RecentRequestEntry &right) const { RecentRequestEntry *pLeft = &left; RecentRequestEntry *pRight = &right; if (order == Qt::DescendingOrder) { std::swap(pLeft, pRight); } switch (column) { case RecentRequestsTableModel::Date: return pLeft->date.toTime_t() < pRight->date.toTime_t(); case RecentRequestsTableModel::Label: return pLeft->recipient.label < pRight->recipient.label; case RecentRequestsTableModel::Message: return pLeft->recipient.message < pRight->recipient.message; case RecentRequestsTableModel::Amount: return pLeft->recipient.amount < pRight->recipient.amount; default: return pLeft->id < pRight->id; } } diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index 3da587c3b..c075c519f 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -1,110 +1,111 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_RECENTREQUESTSTABLEMODEL_H #define BITCOIN_QT_RECENTREQUESTSTABLEMODEL_H #include -#include #include #include #include +class WalletModel; + class RecentRequestEntry { public: RecentRequestEntry() : nVersion(RecentRequestEntry::CURRENT_VERSION), id(0) {} static const int CURRENT_VERSION = 1; int nVersion; int64_t id; QDateTime date; SendCoinsRecipient recipient; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { unsigned int nDate = date.toTime_t(); READWRITE(this->nVersion); READWRITE(id); READWRITE(nDate); READWRITE(recipient); if (ser_action.ForRead()) date = QDateTime::fromTime_t(nDate); } }; class RecentRequestEntryLessThan { public: RecentRequestEntryLessThan(int nColumn, Qt::SortOrder fOrder) : column(nColumn), order(fOrder) {} bool operator()(RecentRequestEntry &left, RecentRequestEntry &right) const; private: int column; Qt::SortOrder order; }; /** * Model for list of recently generated payment requests / bitcoincash: URIs. * Part of wallet model. */ class RecentRequestsTableModel : public QAbstractTableModel { Q_OBJECT public: explicit RecentRequestsTableModel(WalletModel *parent); ~RecentRequestsTableModel(); enum ColumnIndex { Date = 0, Label = 1, Message = 2, Amount = 3, NUMBER_OF_COLUMNS }; /** @name Methods overridden from QAbstractTableModel @{*/ int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; Qt::ItemFlags flags(const QModelIndex &index) const override; /*@}*/ const RecentRequestEntry &entry(int row) const { return list[row]; } void addNewRequest(const SendCoinsRecipient &recipient); void addNewRequest(const std::string &recipient); void addNewRequest(RecentRequestEntry &recipient); public Q_SLOTS: void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void updateDisplayUnit(); private: WalletModel *walletModel; QStringList columns; QList list; int64_t nReceiveRequestsMaxId{0}; /** Updates the column title to "Amount (DisplayUnit)" and emits * headerDataChanged() signal for table headers to react. */ void updateAmountColumnTitle(); /** Gets title for amount column including current display unit if * optionsModel reference available. */ QString getAmountTitle(); }; #endif // BITCOIN_QT_RECENTREQUESTSTABLEMODEL_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 76b3fc8ac..63aa9402c 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -1,311 +1,312 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include +#include #include #include SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, WalletModel *_model, QWidget *parent) : QStackedWidget(parent), ui(new Ui::SendCoinsEntry), model(_model), platformStyle(_platformStyle) { ui->setupUi(this); ui->addressBookButton->setIcon( platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton->setIcon( platformStyle->SingleColorIcon(":/icons/editpaste")); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); ui->deleteButton_is->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->deleteButton_s->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); setCurrentWidget(ui->SendCoins); if (platformStyle->getUseExtraSpacing()) { ui->payToLayout->setSpacing(4); } ui->addAsLabel->setPlaceholderText( tr("Enter a label for this address to add it to your address book")); // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); // just a label for displaying bitcoin address(es) ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals connect(ui->payAmount, &BitcoinAmountField::valueChanged, this, &SendCoinsEntry::payAmountChanged); connect(ui->checkboxSubtractFeeFromAmount, &QCheckBox::toggled, this, &SendCoinsEntry::subtractFeeFromAmountChanged); connect(ui->deleteButton, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_is, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_s, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->useAvailableBalanceButton, &QPushButton::clicked, this, &SendCoinsEntry::useAvailableBalanceClicked); // Set the model properly. setModel(model); } SendCoinsEntry::~SendCoinsEntry() { delete ui; } void SendCoinsEntry::on_pasteButton_clicked() { // Paste text from clipboard into recipient field ui->payTo->setText(QApplication::clipboard()->text()); } void SendCoinsEntry::on_addressBookButton_clicked() { if (!model) { return; } AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::SendingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) { ui->payTo->setText(dlg.getReturnValue()); ui->payAmount->setFocus(); } } void SendCoinsEntry::on_payTo_textChanged(const QString &address) { updateLabel(address); } void SendCoinsEntry::setModel(WalletModel *_model) { this->model = _model; if (_model) { ui->messageTextLabel->setToolTip( tr("A message that was attached to the %1 URI which will be stored " "with the transaction for your reference. Note: This message " "will not be sent over the Bitcoin network.") .arg(QString::fromStdString( _model->getChainParams().CashAddrPrefix()))); } if (_model && _model->getOptionsModel()) { connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsEntry::updateDisplayUnit); } clear(); } void SendCoinsEntry::clear() { // clear UI elements for normal payment ui->payTo->clear(); ui->addAsLabel->clear(); ui->payAmount->clear(); ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked); ui->messageTextLabel->clear(); ui->messageTextLabel->hide(); ui->messageLabel->hide(); // clear UI elements for unauthenticated payment request ui->payTo_is->clear(); ui->memoTextLabel_is->clear(); ui->payAmount_is->clear(); // clear UI elements for authenticated payment request ui->payTo_s->clear(); ui->memoTextLabel_s->clear(); ui->payAmount_s->clear(); // update the display unit, to not use the default ("BCH") updateDisplayUnit(); } void SendCoinsEntry::checkSubtractFeeFromAmount() { ui->checkboxSubtractFeeFromAmount->setChecked(true); } void SendCoinsEntry::deleteClicked() { Q_EMIT removeEntry(this); } void SendCoinsEntry::useAvailableBalanceClicked() { Q_EMIT useAvailableBalance(this); } bool SendCoinsEntry::validate(interfaces::Node &node) { if (!model) { return false; } // Check input validity bool retval = true; #ifdef ENABLE_BIP70 // Skip checks for payment request if (recipient.paymentRequest.IsInitialized()) { return retval; } #endif if (!model->validateAddress(ui->payTo->text())) { ui->payTo->setValid(false); retval = false; } if (!ui->payAmount->validate()) { retval = false; } // Sending a zero amount is invalid if (ui->payAmount->value(nullptr) <= Amount::zero()) { ui->payAmount->setValid(false); retval = false; } // Reject dust outputs: if (retval && GUIUtil::isDust(node, ui->payTo->text(), ui->payAmount->value(), model->getChainParams())) { ui->payAmount->setValid(false); retval = false; } return retval; } SendCoinsRecipient SendCoinsEntry::getValue() { #ifdef ENABLE_BIP70 // Payment request if (recipient.paymentRequest.IsInitialized()) { return recipient; } #endif // Normal payment recipient.address = ui->payTo->text(); recipient.label = ui->addAsLabel->text(); recipient.amount = ui->payAmount->value(); recipient.message = ui->messageTextLabel->text(); recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked); return recipient; } QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) { QWidget::setTabOrder(prev, ui->payTo); QWidget::setTabOrder(ui->payTo, ui->addAsLabel); QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel); QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount); QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton); QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); return ui->deleteButton; } void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; #ifdef ENABLE_BIP70 // payment request if (recipient.paymentRequest.IsInitialized()) { // unauthenticated if (recipient.authenticatedMerchant.isEmpty()) { ui->payTo_is->setText(recipient.address); ui->memoTextLabel_is->setText(recipient.message); ui->payAmount_is->setValue(recipient.amount); ui->payAmount_is->setReadOnly(true); setCurrentWidget(ui->SendCoins_UnauthenticatedPaymentRequest); } // authenticated else { ui->payTo_s->setText(recipient.authenticatedMerchant); ui->memoTextLabel_s->setText(recipient.message); ui->payAmount_s->setValue(recipient.amount); ui->payAmount_s->setReadOnly(true); setCurrentWidget(ui->SendCoins_AuthenticatedPaymentRequest); } } // normal payment else #endif { // message ui->messageTextLabel->setText(recipient.message); ui->messageTextLabel->setVisible(!recipient.message.isEmpty()); ui->messageLabel->setVisible(!recipient.message.isEmpty()); ui->addAsLabel->clear(); // this may set a label from addressbook ui->payTo->setText(recipient.address); // if a label had been set from the addressbook, don't overwrite with an // empty label if (!recipient.label.isEmpty()) { ui->addAsLabel->setText(recipient.label); } ui->payAmount->setValue(recipient.amount); } } void SendCoinsEntry::setAddress(const QString &address) { ui->payTo->setText(address); ui->payAmount->setFocus(); } void SendCoinsEntry::setAmount(const Amount amount) { ui->payAmount->setValue(amount); } bool SendCoinsEntry::isClear() { return ui->payTo->text().isEmpty() && ui->payTo_is->text().isEmpty() && ui->payTo_s->text().isEmpty(); } void SendCoinsEntry::setFocus() { ui->payTo->setFocus(); } void SendCoinsEntry::updateDisplayUnit() { if (model && model->getOptionsModel()) { // Update payAmount with the current unit ui->payAmount->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); ui->payAmount_is->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); ui->payAmount_s->setDisplayUnit( model->getOptionsModel()->getDisplayUnit()); } } bool SendCoinsEntry::updateLabel(const QString &address) { if (!model) { return false; } // Fill in label from address book, if address has an associated label QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); if (!associatedLabel.isEmpty()) { ui->addAsLabel->setText(associatedLabel); return true; } return false; } diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 590e16d50..2a8a4f60c 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -1,79 +1,82 @@ // Copyright (c) 2011-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_SENDCOINSENTRY_H #define BITCOIN_QT_SENDCOINSENTRY_H #include -#include #include class WalletModel; class PlatformStyle; +namespace interfaces { +class Node; +} // namespace interfaces + namespace Ui { class SendCoinsEntry; } /** * A single entry in the dialog for sending bitcoins. * Stacked widget, with different UIs for payment requests * with a strong payee identity. */ class SendCoinsEntry : public QStackedWidget { Q_OBJECT public: SendCoinsEntry(const PlatformStyle *platformStyle, WalletModel *model, QWidget *parent = nullptr); ~SendCoinsEntry(); void setModel(WalletModel *model); bool validate(interfaces::Node &node); SendCoinsRecipient getValue(); /** Return whether the entry is still empty and unedited */ bool isClear(); void setValue(const SendCoinsRecipient &value); void setAddress(const QString &address); void setAmount(const Amount amount); /** Set up the tab chain manually, as Qt messes up the tab chain by default * in some cases * (issue https://bugreports.qt-project.org/browse/QTBUG-10907). */ QWidget *setupTabChain(QWidget *prev); void setFocus(); public Q_SLOTS: void clear(); void checkSubtractFeeFromAmount(); Q_SIGNALS: void removeEntry(SendCoinsEntry *entry); void useAvailableBalance(SendCoinsEntry *entry); void payAmountChanged(); void subtractFeeFromAmountChanged(); private Q_SLOTS: void deleteClicked(); void useAvailableBalanceClicked(); void on_payTo_textChanged(const QString &address); void on_addressBookButton_clicked(); void on_pasteButton_clicked(); void updateDisplayUnit(); private: SendCoinsRecipient recipient; Ui::SendCoinsEntry *ui; WalletModel *model; const PlatformStyle *platformStyle; bool updateLabel(const QString &address); }; #endif // BITCOIN_QT_SENDCOINSENTRY_H diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 5fce79c1a..ac15c21a5 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -1,317 +1,317 @@ // Copyright (c) 2019 The Bitcoin Core 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 WalletController::WalletController(interfaces::Node &node, const PlatformStyle *platform_style, OptionsModel *options_model, QObject *parent) : QObject(parent), m_activity_thread(new QThread(this)), m_activity_worker(new QObject), m_node(node), m_platform_style(platform_style), m_options_model(options_model) { m_handler_load_wallet = m_node.handleLoadWallet( [this](std::unique_ptr wallet) { getOrCreateWallet(std::move(wallet)); }); for (std::unique_ptr &wallet : m_node.getWallets()) { getOrCreateWallet(std::move(wallet)); } m_activity_worker->moveToThread(m_activity_thread); m_activity_thread->start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. WalletController::~WalletController() { m_activity_thread->quit(); m_activity_thread->wait(); delete m_activity_worker; } std::vector WalletController::getOpenWallets() const { QMutexLocker locker(&m_mutex); return m_wallets; } std::map WalletController::listWalletDir() const { QMutexLocker locker(&m_mutex); std::map wallets; for (const std::string &name : m_node.listWalletDir()) { wallets[name] = false; } for (WalletModel *wallet_model : m_wallets) { auto it = wallets.find(wallet_model->wallet().getWalletName()); if (it != wallets.end()) { it->second = true; } } return wallets; } void WalletController::closeWallet(WalletModel *wallet_model, QWidget *parent) { QMessageBox box(parent); box.setWindowTitle(tr("Close wallet")); box.setText(tr("Are you sure you wish to close wallet %1?") .arg(wallet_model->getDisplayName())); box.setInformativeText( tr("Closing the wallet for too long can result in having to resync the " "entire chain if pruning is enabled.")); box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); box.setDefaultButton(QMessageBox::Yes); if (box.exec() != QMessageBox::Yes) { return; } // First remove wallet from node. wallet_model->wallet().remove(); // Now release the model. removeAndDeleteWallet(wallet_model); } WalletModel *WalletController::getOrCreateWallet( std::unique_ptr wallet) { QMutexLocker locker(&m_mutex); // Return model instance if exists. if (!m_wallets.empty()) { std::string name = wallet->getWalletName(); for (WalletModel *wallet_model : m_wallets) { if (wallet_model->wallet().getWalletName() == name) { return wallet_model; } } } // Instantiate model and register it. WalletModel *wallet_model = new WalletModel( std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); // Handler callback runs in a different thread so fix wallet model thread // affinity. wallet_model->moveToThread(thread()); wallet_model->setParent(this); m_wallets.push_back(wallet_model); // WalletModel::startPollBalance needs to be called in a thread managed by // Qt because of startTimer. Considering the current thread can be a RPC // thread, better delegate the calling to Qt with Qt::AutoConnection. const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance"); assert(called); connect( wallet_model, &WalletModel::unload, this, [this, wallet_model] { // Defer removeAndDeleteWallet when no modal widget is active. // TODO: remove this workaround by removing usage of QDiallog::exec. if (QApplication::activeModalWidget()) { connect( qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() { if (!QApplication::activeModalWidget()) { removeAndDeleteWallet(wallet_model); } }, Qt::QueuedConnection); } else { removeAndDeleteWallet(wallet_model); } }, Qt::QueuedConnection); // Re-emit coinsSent signal from wallet model. connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); // Notify walletAdded signal on the GUI thread. Q_EMIT walletAdded(wallet_model); return wallet_model; } void WalletController::removeAndDeleteWallet(WalletModel *wallet_model) { // Unregister wallet model. { QMutexLocker locker(&m_mutex); m_wallets.erase( std::remove(m_wallets.begin(), m_wallets.end(), wallet_model)); } Q_EMIT walletRemoved(wallet_model); // Currently this can trigger the unload since the model can hold the last // CWallet shared pointer. delete wallet_model; } WalletControllerActivity::WalletControllerActivity( WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams) : QObject(wallet_controller), m_wallet_controller(wallet_controller), m_parent_widget(parent_widget), m_chainparams(chainparams) {} WalletControllerActivity::~WalletControllerActivity() { delete m_progress_dialog; } void WalletControllerActivity::showProgressDialog(const QString &label_text) { m_progress_dialog = new QProgressDialog(m_parent_widget); m_progress_dialog->setLabelText(label_text); m_progress_dialog->setRange(0, 0); m_progress_dialog->setCancelButton(nullptr); m_progress_dialog->setWindowModality(Qt::ApplicationModal); GUIUtil::PolishProgressDialog(m_progress_dialog); } CreateWalletActivity::CreateWalletActivity(WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams) : WalletControllerActivity(wallet_controller, parent_widget, chainparams) { m_passphrase.reserve(MAX_PASSPHRASE_SIZE); } CreateWalletActivity::~CreateWalletActivity() { delete m_create_wallet_dialog; delete m_passphrase_dialog; } void CreateWalletActivity::askPassphrase() { m_passphrase_dialog = new AskPassphraseDialog( AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase); m_passphrase_dialog->show(); connect(m_passphrase_dialog, &QObject::destroyed, [this] { m_passphrase_dialog = nullptr; }); connect(m_passphrase_dialog, &QDialog::accepted, [this] { createWallet(); }); connect(m_passphrase_dialog, &QDialog::rejected, [this] { Q_EMIT finished(); }); } void CreateWalletActivity::createWallet() { showProgressDialog( tr("Creating Wallet %1...") .arg(m_create_wallet_dialog->walletName().toHtmlEscaped())); std::string name = m_create_wallet_dialog->walletName().toStdString(); uint64_t flags = 0; if (m_create_wallet_dialog->disablePrivateKeys()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } if (m_create_wallet_dialog->blank()) { flags |= WALLET_FLAG_BLANK_WALLET; } QTimer::singleShot(500, worker(), [this, name, flags] { std::unique_ptr wallet; WalletCreationStatus status = node().createWallet(this->m_chainparams, m_passphrase, flags, name, m_error_message, m_warning_message, wallet); if (status == WalletCreationStatus::SUCCESS) { m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); } QTimer::singleShot(500, this, &CreateWalletActivity::finish); }); } void CreateWalletActivity::finish() { m_progress_dialog->hide(); if (!m_error_message.empty()) { QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message)); } else if (!m_warning_message.empty()) { QMessageBox::warning( m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, "\n"))); } if (m_wallet_model) { Q_EMIT created(m_wallet_model); } Q_EMIT finished(); } void CreateWalletActivity::create() { m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); m_create_wallet_dialog->show(); connect(m_create_wallet_dialog, &QObject::destroyed, [this] { m_create_wallet_dialog = nullptr; }); connect(m_create_wallet_dialog, &QDialog::rejected, [this] { Q_EMIT finished(); }); connect(m_create_wallet_dialog, &QDialog::accepted, [this] { if (m_create_wallet_dialog->encrypt()) { askPassphrase(); } else { createWallet(); } }); } OpenWalletActivity::OpenWalletActivity(WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams) : WalletControllerActivity(wallet_controller, parent_widget, chainparams) {} void OpenWalletActivity::finish() { m_progress_dialog->hide(); if (!m_error_message.empty()) { QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message)); } else if (!m_warning_message.empty()) { QMessageBox::warning( m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, "\n"))); } if (m_wallet_model) { Q_EMIT opened(m_wallet_model); } Q_EMIT finished(); } void OpenWalletActivity::open(const std::string &path) { QString name = path.empty() ? QString("[" + tr("default wallet") + "]") : QString::fromStdString(path); showProgressDialog( tr("Opening Wallet %1...").arg(name.toHtmlEscaped())); QTimer::singleShot(0, worker(), [this, path] { std::unique_ptr wallet = node().loadWallet( this->m_chainparams, path, m_error_message, m_warning_message); if (wallet) { m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); } QTimer::singleShot(0, this, &OpenWalletActivity::finish); }); } diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index ea6fb727b..3c0091862 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -1,154 +1,156 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_WALLETCONTROLLER_H #define BITCOIN_QT_WALLETCONTROLLER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class OptionsModel; class PlatformStyle; +class WalletModel; namespace interfaces { class Handler; class Node; +class Wallet; } // namespace interfaces class AskPassphraseDialog; class CreateWalletActivity; class CreateWalletDialog; class OpenWalletActivity; class WalletControllerActivity; /** * Controller between interfaces::Node, WalletModel instances and the GUI. */ class WalletController : public QObject { Q_OBJECT void removeAndDeleteWallet(WalletModel *wallet_model); public: WalletController(interfaces::Node &node, const PlatformStyle *platform_style, OptionsModel *options_model, QObject *parent); ~WalletController(); //! Returns wallet models currently open. std::vector getOpenWallets() const; WalletModel *getOrCreateWallet(std::unique_ptr wallet); //! Returns all wallet names in the wallet dir mapped to whether the wallet //! is loaded. std::map listWalletDir() const; void closeWallet(WalletModel *wallet_model, QWidget *parent = nullptr); Q_SIGNALS: void walletAdded(WalletModel *wallet_model); void walletRemoved(WalletModel *wallet_model); void coinsSent(interfaces::Wallet &wallet, SendCoinsRecipient recipient, QByteArray transaction); private: QThread *const m_activity_thread; QObject *const m_activity_worker; interfaces::Node &m_node; const PlatformStyle *const m_platform_style; OptionsModel *const m_options_model; mutable QMutex m_mutex; std::vector m_wallets; std::unique_ptr m_handler_load_wallet; friend class WalletControllerActivity; }; class WalletControllerActivity : public QObject { Q_OBJECT public: WalletControllerActivity(WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams); virtual ~WalletControllerActivity(); Q_SIGNALS: void finished(); protected: interfaces::Node &node() const { return m_wallet_controller->m_node; } QObject *worker() const { return m_wallet_controller->m_activity_worker; } void showProgressDialog(const QString &label_text); WalletController *const m_wallet_controller; QWidget *const m_parent_widget; QProgressDialog *m_progress_dialog{nullptr}; WalletModel *m_wallet_model{nullptr}; std::string m_error_message; std::vector m_warning_message; const CChainParams &m_chainparams; }; class CreateWalletActivity : public WalletControllerActivity { Q_OBJECT public: CreateWalletActivity(WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams); virtual ~CreateWalletActivity(); void create(); Q_SIGNALS: void created(WalletModel *wallet_model); private: void askPassphrase(); void createWallet(); void finish(); SecureString m_passphrase; CreateWalletDialog *m_create_wallet_dialog{nullptr}; AskPassphraseDialog *m_passphrase_dialog{nullptr}; }; class OpenWalletActivity : public WalletControllerActivity { Q_OBJECT public: OpenWalletActivity(WalletController *wallet_controller, QWidget *parent_widget, const CChainParams &chainparams); void open(const std::string &path); Q_SIGNALS: void opened(WalletModel *wallet_model); private: void finish(); }; #endif // BITCOIN_QT_WALLETCONTROLLER_H