diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index e91735824..e79d803da 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -1,348 +1,350 @@ // 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 class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel { const QString m_type; public: AddressBookSortFilterProxyModel(const QString &type, QObject *parent) : QSortFilterProxyModel(parent), m_type(type) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); } protected: bool filterAcceptsRow(int row, const QModelIndex &parent) const { auto model = sourceModel(); auto label = model->index(row, AddressTableModel::Label, parent); if (model->data(label, AddressTableModel::TypeRole).toString() != m_type) { return false; } auto address = model->index(row, AddressTableModel::Address, parent); if (filterRegExp().indexIn(model->data(address).toString()) < 0 && filterRegExp().indexIn(model->data(label).toString()) < 0) { return false; } return true; } }; AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), model(nullptr), mode(_mode), tab(_tab) { ui->setupUi(this); if (!platformStyle->getImagesOnButtons()) { ui->newAddress->setIcon(QIcon()); ui->copyAddress->setIcon(QIcon()); ui->deleteAddress->setIcon(QIcon()); ui->exportButton->setIcon(QIcon()); } else { ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add")); ui->copyAddress->setIcon( platformStyle->SingleColorIcon(":/icons/editcopy")); ui->deleteAddress->setIcon( platformStyle->SingleColorIcon(":/icons/remove")); ui->exportButton->setIcon( platformStyle->SingleColorIcon(":/icons/export")); } switch (mode) { case ForSelection: switch (tab) { case SendingTab: setWindowTitle(tr("Choose the address to send coins to")); break; case ReceivingTab: setWindowTitle( tr("Choose the address to receive coins with")); break; } connect(ui->tableView, &QTableView::doubleClicked, this, &QDialog::accept); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableView->setFocus(); ui->closeButton->setText(tr("C&hoose")); ui->exportButton->hide(); break; case ForEditing: switch (tab) { case SendingTab: setWindowTitle(tr("Sending addresses")); break; case ReceivingTab: setWindowTitle(tr("Receiving addresses")); break; } break; } switch (tab) { case SendingTab: ui->labelExplanation->setText( tr("These are your Bitcoin addresses for sending payments. " "Always check the amount and the receiving address before " "sending coins.")); ui->deleteAddress->setVisible(true); ui->newAddress->setVisible(true); break; case ReceivingTab: ui->labelExplanation->setText( tr("These are your Bitcoin addresses for receiving payments. " "Use the 'Create new receiving address' button in the " "receive tab to create new addresses.")); ui->deleteAddress->setVisible(false); ui->newAddress->setVisible(false); break; } // Context menu actions QAction *copyAddressAction = new QAction(tr("&Copy Address"), this); QAction *copyLabelAction = new QAction(tr("Copy &Label"), this); QAction *editAction = new QAction(tr("&Edit"), this); deleteAction = new QAction(ui->deleteAddress->text(), this); // Build context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(editAction); if (tab == SendingTab) { contextMenu->addAction(deleteAction); } contextMenu->addSeparator(); // Connect signals for context menu actions connect(copyAddressAction, &QAction::triggered, this, &AddressBookPage::on_copyAddress_clicked); connect(copyLabelAction, &QAction::triggered, this, &AddressBookPage::onCopyLabelAction); connect(editAction, &QAction::triggered, this, &AddressBookPage::onEditAction); connect(deleteAction, &QAction::triggered, this, &AddressBookPage::on_deleteAddress_clicked); connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept); + + GUIUtil::handleCloseWindowShortcut(this); } AddressBookPage::~AddressBookPage() { delete ui; } void AddressBookPage::setModel(AddressTableModel *_model) { this->model = _model; if (!_model) { return; } auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send; proxyModel = new AddressBookSortFilterProxyModel(type, this); proxyModel->setSourceModel(_model); connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterWildcard); ui->tableView->setModel(proxyModel); ui->tableView->sortByColumn(0, Qt::AscendingOrder); // Set column widths ui->tableView->horizontalHeader()->setSectionResizeMode( AddressTableModel::Label, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode( AddressTableModel::Address, QHeaderView::ResizeToContents); connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AddressBookPage::selectionChanged); // Select row for newly created address connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress); selectionChanged(); } void AddressBookPage::on_copyAddress_clicked() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); } void AddressBookPage::onCopyLabelAction() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label); } void AddressBookPage::onEditAction() { if (!model) { return; } if (!ui->tableView->selectionModel()) { return; } QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows(); if (indexes.isEmpty()) { return; } EditAddressDialog dlg(tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress, this); dlg.setModel(model); QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0)); dlg.loadRow(origIndex.row()); dlg.exec(); } void AddressBookPage::on_newAddress_clicked() { if (!model) { return; } if (tab == ReceivingTab) { return; } EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this); dlg.setModel(model); if (dlg.exec()) { newAddressToSelect = dlg.getAddress(); } } void AddressBookPage::on_deleteAddress_clicked() { QTableView *table = ui->tableView; if (!table->selectionModel()) { return; } QModelIndexList indexes = table->selectionModel()->selectedRows(); if (!indexes.isEmpty()) { table->model()->removeRow(indexes.at(0).row()); } } void AddressBookPage::selectionChanged() { // Set button states based on selected tab and selection QTableView *table = ui->tableView; if (!table->selectionModel()) { return; } if (table->selectionModel()->hasSelection()) { switch (tab) { case SendingTab: // In sending tab, allow deletion of selection ui->deleteAddress->setEnabled(true); ui->deleteAddress->setVisible(true); deleteAction->setEnabled(true); break; case ReceivingTab: // Deleting receiving addresses, however, is not allowed ui->deleteAddress->setEnabled(false); ui->deleteAddress->setVisible(false); deleteAction->setEnabled(false); break; } ui->copyAddress->setEnabled(true); } else { ui->deleteAddress->setEnabled(false); ui->copyAddress->setEnabled(false); } } void AddressBookPage::done(int retval) { QTableView *table = ui->tableView; if (!table->selectionModel() || !table->model()) { return; } // Figure out which address was selected, and return it QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); for (const QModelIndex &index : indexes) { QVariant address = table->model()->data(index); returnValue = address.toString(); } if (returnValue.isEmpty()) { // If no address entry selected, return rejected retval = Rejected; } QDialog::done(retval); } void AddressBookPage::on_exportButton_clicked() { // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Address List"), QString(), tr("Comma separated file (*.csv)"), nullptr); if (filename.isNull()) { return; } CSVModelWriter writer(filename); // name, column, role writer.setModel(proxyModel); writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); if (!writer.write()) { QMessageBox::critical(this, tr("Exporting Failed"), tr("There was an error trying to save the " "address list to %1. Please try again.") .arg(filename)); } } void AddressBookPage::contextualMenu(const QPoint &point) { QModelIndex index = ui->tableView->indexAt(point); if (index.isValid()) { contextMenu->exec(QCursor::pos()); } } void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/) { QModelIndex idx = proxyModel->mapFromSource( model->index(begin, AddressTableModel::Address, parent)); if (idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) { // Select row of newly created address, once ui->tableView->setFocus(); ui->tableView->selectRow(idx.row()); newAddressToSelect.clear(); } } diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 0c0847603..4c406889c 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -1,298 +1,301 @@ // 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 AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString *passphrase_out) : QDialog(parent), ui(new Ui::AskPassphraseDialog), mode(_mode), model(nullptr), fCapsLock(false), m_passphrase_out(passphrase_out) { ui->setupUi(this); ui->passEdit1->setMinimumSize(ui->passEdit1->sizeHint()); ui->passEdit2->setMinimumSize(ui->passEdit2->sizeHint()); ui->passEdit3->setMinimumSize(ui->passEdit3->sizeHint()); ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE); ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE); ui->passEdit3->setMaxLength(MAX_PASSPHRASE_SIZE); // Setup Caps Lock detection. ui->passEdit1->installEventFilter(this); ui->passEdit2->installEventFilter(this); ui->passEdit3->installEventFilter(this); switch (mode) { case Encrypt: // Ask passphrase x2 ui->warningLabel->setText( tr("Enter the new passphrase for the wallet.
Please use a " "passphrase of ten or more random characters, or " "eight or more words.")); ui->passLabel1->hide(); ui->passEdit1->hide(); setWindowTitle(tr("Encrypt wallet")); break; case Unlock: // Ask passphrase ui->warningLabel->setText(tr("This operation needs your wallet " "passphrase to unlock the wallet.")); ui->passLabel2->hide(); ui->passEdit2->hide(); ui->passLabel3->hide(); ui->passEdit3->hide(); setWindowTitle(tr("Unlock wallet")); break; case Decrypt: // Ask passphrase ui->warningLabel->setText(tr("This operation needs your wallet " "passphrase to decrypt the wallet.")); ui->passLabel2->hide(); ui->passEdit2->hide(); ui->passLabel3->hide(); ui->passEdit3->hide(); setWindowTitle(tr("Decrypt wallet")); break; case ChangePass: // Ask old passphrase + new passphrase x2 setWindowTitle(tr("Change passphrase")); ui->warningLabel->setText(tr( "Enter the old passphrase and new passphrase for the wallet.")); break; } textChanged(); connect(ui->toggleShowPasswordButton, &QPushButton::toggled, this, &AskPassphraseDialog::toggleShowPassword); connect(ui->passEdit1, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); connect(ui->passEdit2, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); connect(ui->passEdit3, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); + + GUIUtil::handleCloseWindowShortcut(this); } AskPassphraseDialog::~AskPassphraseDialog() { secureClearPassFields(); delete ui; } void AskPassphraseDialog::setModel(WalletModel *_model) { this->model = _model; } void AskPassphraseDialog::accept() { SecureString oldpass, newpass1, newpass2; if (!model && mode != Encrypt) { return; } oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); newpass2.reserve(MAX_PASSPHRASE_SIZE); // TODO: get rid of this .c_str() by implementing // SecureString::operator=(std::string) // Alternately, find a way to make this input mlock()'d to begin with. oldpass.assign(ui->passEdit1->text().toStdString().c_str()); newpass1.assign(ui->passEdit2->text().toStdString().c_str()); newpass2.assign(ui->passEdit3->text().toStdString().c_str()); secureClearPassFields(); switch (mode) { case Encrypt: { if (newpass1.empty() || newpass2.empty()) { // Cannot encrypt with empty passphrase break; } QMessageBox::StandardButton retval = QMessageBox::question( this, tr("Confirm wallet encryption"), tr("Warning: If you encrypt your wallet and lose your " "passphrase, you will LOSE ALL OF YOUR BITCOINS!") + "

" + tr("Are you sure you wish to encrypt your wallet?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if (retval == QMessageBox::Yes) { if (newpass1 == newpass2) { QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully " "protect your bitcoins from being stolen by malware " "infecting your computer."); if (m_passphrase_out) { m_passphrase_out->assign(newpass1); QMessageBox::warning( this, tr("Wallet to be encrypted"), "" + tr("Your wallet is about to be encrypted. ") + encryption_reminder + ""); } else { assert(model != nullptr); if (model->setWalletEncrypted(true, newpass1)) { QMessageBox::warning( this, tr("Wallet encrypted"), "" + tr("Your wallet is now encrypted. ") + encryption_reminder + "

" + tr("IMPORTANT: Any previous backups you " "have made of your wallet file should " "be replaced with the newly generated, " "encrypted wallet file. For security " "reasons, previous backups of the " "unencrypted wallet file will become " "useless as soon as you start using the " "new, encrypted wallet.") + "
"); } else { QMessageBox::critical( this, tr("Wallet encryption failed"), tr("Wallet encryption failed due to an " "internal error. Your wallet was not " "encrypted.")); } } QDialog::accept(); // Success } else { QMessageBox::critical( this, tr("Wallet encryption failed"), tr("The supplied passphrases do not match.")); } } else { QDialog::reject(); // Cancelled } } break; case Unlock: try { if (!model->setWalletLocked(false, oldpass)) { QMessageBox::critical( this, tr("Wallet unlock failed"), tr("The passphrase entered for the wallet decryption " "was incorrect.")); } else { // Success QDialog::accept(); } } catch (const std::runtime_error &e) { QMessageBox::critical(this, tr("Wallet unlock failed"), e.what()); } break; case Decrypt: if (!model->setWalletEncrypted(false, oldpass)) { QMessageBox::critical(this, tr("Wallet decryption failed"), tr("The passphrase entered for the " "wallet decryption was incorrect.")); } else { QDialog::accept(); // Success } break; case ChangePass: if (newpass1 == newpass2) { if (model->changePassphrase(oldpass, newpass1)) { QMessageBox::information( this, tr("Wallet encrypted"), tr("Wallet passphrase was successfully changed.")); QDialog::accept(); // Success } else { QMessageBox::critical( this, tr("Wallet encryption failed"), tr("The passphrase entered for the wallet decryption " "was incorrect.")); } } else { QMessageBox::critical( this, tr("Wallet encryption failed"), tr("The supplied passphrases do not match.")); } break; } } void AskPassphraseDialog::textChanged() { // Validate input, set Ok button to enabled when acceptable bool acceptable = false; switch (mode) { case Encrypt: // New passphrase x2 acceptable = !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty(); break; case Unlock: // Old passphrase x1 case Decrypt: acceptable = !ui->passEdit1->text().isEmpty(); break; case ChangePass: // Old passphrase x1, new passphrase x2 acceptable = !ui->passEdit1->text().isEmpty() && !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty(); break; } ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(acceptable); } bool AskPassphraseDialog::event(QEvent *event) { // Detect Caps Lock key press. if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if (ke->key() == Qt::Key_CapsLock) { fCapsLock = !fCapsLock; } if (fCapsLock) { ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); } else { ui->capsLabel->clear(); } } return QWidget::event(event); } void AskPassphraseDialog::toggleShowPassword(bool show) { ui->toggleShowPasswordButton->setDown(show); const auto renderingMode = show ? QLineEdit::Normal : QLineEdit::Password; ui->passEdit1->setEchoMode(renderingMode); ui->passEdit2->setEchoMode(renderingMode); ui->passEdit3->setEchoMode(renderingMode); } bool AskPassphraseDialog::eventFilter(QObject *object, QEvent *event) { /* Detect Caps Lock. * There is no good OS-independent way to check a key state in Qt, but * we can detect Caps Lock by checking for the following condition: * Shift key is down and the result is a lower case character, or * Shift key is not down and the result is an upper case character. */ if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QString str = ke->text(); if (str.length() != 0) { const QChar *psz = str.unicode(); bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0; if ((fShift && *psz >= 'a' && *psz <= 'z') || (!fShift && *psz >= 'A' && *psz <= 'Z')) { fCapsLock = true; ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); } else if (psz->isLetter()) { fCapsLock = false; ui->capsLabel->clear(); } } } return QDialog::eventFilter(object, event); } static void SecureClearQLineEdit(QLineEdit *edit) { // Attempt to overwrite text so that they do not linger around in memory edit->setText(QString(" ").repeated(edit->text().size())); edit->clear(); } void AskPassphraseDialog::secureClearPassFields() { SecureClearQLineEdit(ui->passEdit1); SecureClearQLineEdit(ui->passEdit2); SecureClearQLineEdit(ui->passEdit3); } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6bf460b9e..8b8e7c722 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,1625 +1,1627 @@ // Copyright (c) 2011-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 #ifdef Q_OS_MAC #include #endif #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #include #include #endif // ENABLE_WALLET #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) "macosx" #elif defined(Q_OS_WIN) "windows" #else "other" #endif ; BitcoinGUI::BitcoinGUI(interfaces::Node &node, const Config *configIn, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), m_node(node), trayIconMenu{new QMenu()}, config(configIn), platformStyle(_platformStyle), m_network_style(networkStyle) { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon()); setWindowIcon(m_network_style->getTrayAndWindowIcon()); updateWindowTitle(); rpcConsole = new RPCConsole(node, _platformStyle, nullptr); helpMessageDialog = new HelpMessageDialog(this, false); #ifdef ENABLE_WALLET if (enableWallet) { /** Create wallet frame and make it the central widget */ walletFrame = new WalletFrame(_platformStyle, this); setCentralWidget(walletFrame); } else #endif // ENABLE_WALLET { /** * When compiled without wallet or -disablewallet is provided, the * central widget is the rpc console. */ setCentralWidget(rpcConsole); Q_EMIT consoleShown(rpcConsole); } // Accept D&D of URIs setAcceptDrops(true); // Create actions for the toolbar, menu bar and tray/dock icon // Needs walletFrame to be initialized createActions(); // Create application menu bar createMenuBar(); // Create the toolbars createToolBars(); // Create system tray icon and notification if (QSystemTrayIcon::isSystemTrayAvailable()) { createTrayIcon(); } notificator = new Notificator(QApplication::applicationName(), trayIcon, this); // Create status bar statusBar(); // Disable size grip because it looks ugly and nobody needs it statusBar()->setSizeGripEnabled(false); // Status bar notification icons QFrame *frameBlocks = new QFrame(); frameBlocks->setContentsMargins(0, 0, 0, 0); frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3, 0, 3, 0); frameBlocksLayout->setSpacing(3); unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelWalletEncryptionIcon = new QLabel(); labelWalletHDStatusIcon = new QLabel(); labelProxyIcon = new GUIUtil::ClickableLabel(); connectionsControl = new GUIUtil::ClickableLabel(); labelBlocksIcon = new GUIUtil::ClickableLabel(); if (enableWallet) { frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(unitDisplayControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelWalletEncryptionIcon); frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } frameBlocksLayout->addWidget(labelProxyIcon); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(connectionsControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelBlocksIcon); frameBlocksLayout->addStretch(); // Progress bar and label for blocks download progressBarLabel = new QLabel(); progressBarLabel->setVisible(false); progressBar = new GUIUtil::ProgressBar(); progressBar->setAlignment(Qt::AlignCenter); progressBar->setVisible(false); // Override style sheet for progress bar for styles that have a segmented // progress bar, as they make the text unreadable (workaround for issue // #1071) // See https://doc.qt.io/qt-5/gallery.html QString curStyle = QApplication::style()->metaObject()->className(); if (curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") { progressBar->setStyleSheet( "QProgressBar { background-color: #e8e8e8; border: 1px solid grey; " "border-radius: 7px; padding: 1px; text-align: center; } " "QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, " "x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: " "7px; margin: 0px; }"); } statusBar()->addWidget(progressBarLabel); statusBar()->addWidget(progressBar); statusBar()->addPermanentWidget(frameBlocks); // Install event filter to be able to catch status tip events // (QEvent::StatusTip) this->installEventFilter(this); // Initially wallet actions should be disabled setWalletActionsEnabled(false); // Subscribe to notifications from core subscribeToCoreSignals(); connect(connectionsControl, &GUIUtil::ClickableLabel::clicked, [this] { m_node.setNetworkActive(!m_node.getNetworkActive()); }); connect(labelProxyIcon, &GUIUtil::ClickableLabel::clicked, [this] { openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK); }); modalOverlay = new ModalOverlay(enableWallet, this->centralWidget()); connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); #ifdef ENABLE_WALLET if (enableWallet) { connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay); } #endif #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; #endif + + GUIUtil::handleCloseWindowShortcut(this); } BitcoinGUI::~BitcoinGUI() { // Unsubscribe from notifications from core unsubscribeFromCoreSignals(); QSettings settings; settings.setValue("MainWindowGeometry", saveGeometry()); // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) if (trayIcon) { trayIcon->hide(); } #ifdef Q_OS_MAC delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); #endif delete rpcConsole; } void BitcoinGUI::createActions() { QActionGroup *tabGroup = new QActionGroup(this); overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this); overviewAction->setStatusTip(tr("Show general overview of wallet")); overviewAction->setToolTip(overviewAction->statusTip()); overviewAction->setCheckable(true); overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); sendCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this); sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); sendCoinsMenuAction = new QAction(sendCoinsAction->text(), this); sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip()); sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); receiveCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip( tr("Request payments (generates QR codes and %1: URIs)") .arg(QString::fromStdString( config->GetChainParams().CashAddrPrefix()))); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(receiveCoinsAction); receiveCoinsMenuAction = new QAction(receiveCoinsAction->text(), this); receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip()); receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip()); historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive // Coins can be triggered from the tray menu, and need to show the GUI to be // useful. connect(overviewAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage); connect(sendCoinsAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(sendCoinsAction, &QAction::triggered, [this] { gotoSendCoinsPage(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this] { gotoSendCoinsPage(); }); connect(receiveCoinsAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(receiveCoinsMenuAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(historyAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET quitAction = new QAction(tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this); aboutAction->setStatusTip( tr("Show information about %1").arg(PACKAGE_NAME)); aboutAction->setMenuRole(QAction::AboutRole); aboutAction->setEnabled(false); aboutQtAction = new QAction(tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(tr("&Options..."), this); optionsAction->setStatusTip( tr("Modify configuration options for %1").arg(PACKAGE_NAME)); optionsAction->setMenuRole(QAction::PreferencesRole); optionsAction->setEnabled(false); toggleHideAction = new QAction(tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); encryptWalletAction = new QAction(tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip( tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); backupWalletAction = new QAction(tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(tr("&Change Passphrase..."), this); changePassphraseAction->setStatusTip( tr("Change the passphrase used for wallet encryption")); signMessageAction = new QAction(tr("Sign &message..."), this); signMessageAction->setStatusTip( tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(tr("&Verify message..."), this); verifyMessageAction->setStatusTip( tr("Verify messages to ensure they were signed with specified Bitcoin " "addresses")); m_load_psbt_action = new QAction(tr("Load PSBT..."), this); m_load_psbt_action->setStatusTip( tr("Load Partially Signed Bitcoin Transaction")); openRPCConsoleAction = new QAction(tr("&Debug window"), this); openRPCConsoleAction->setStatusTip( tr("Open node debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); openRPCConsoleAction->setObjectName("openRPCConsoleAction"); usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this); usedSendingAddressesAction->setStatusTip( tr("Show the list of used sending addresses and labels")); usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this); usedReceivingAddressesAction->setStatusTip( tr("Show the list of used receiving addresses and labels")); openAction = new QAction(tr("Open &URI..."), this); openAction->setStatusTip( tr("Open a %1: URI or payment request") .arg(QString::fromStdString( config->GetChainParams().CashAddrPrefix()))); m_open_wallet_action = new QAction(tr("Open Wallet"), this); m_open_wallet_action->setEnabled(false); m_open_wallet_action->setStatusTip(tr("Open a wallet")); m_open_wallet_menu = new QMenu(this); m_close_wallet_action = new QAction(tr("Close Wallet..."), this); m_close_wallet_action->setStatusTip(tr("Close wallet")); m_create_wallet_action = new QAction(tr("Create Wallet..."), this); m_create_wallet_action->setEnabled(false); m_create_wallet_action->setStatusTip(tr("Create a new wallet")); showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip( tr("Show the %1 help message to get a list with possible Bitcoin " "command-line options") .arg(PACKAGE_NAME)); connect(quitAction, &QAction::triggered, qApp, QApplication::quit); connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked); connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt); connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked); connect(toggleHideAction, &QAction::triggered, this, &BitcoinGUI::toggleHidden); connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked); connect(openRPCConsoleAction, &QAction::triggered, this, &BitcoinGUI::showDebugWindow); // prevents an open debug window from becoming stuck/unusable on client // shutdown connect(quitAction, &QAction::triggered, rpcConsole, &QWidget::hide); #ifdef ENABLE_WALLET if (walletFrame) { connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet); connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); connect(signMessageAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this] { gotoSignMessageTab(); }); connect(verifyMessageAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this] { gotoVerifyMessageTab(); }); connect(m_load_psbt_action, &QAction::triggered, [this] { gotoLoadPSBT(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] { m_open_wallet_menu->clear(); for (const std::pair &i : m_wallet_controller->listWalletDir()) { const std::string &path = i.first; QString name = path.empty() ? QString("[" + tr("default wallet") + "]") : QString::fromStdString(path); // Menu items remove single &. Single & are shown when && is in // the string, but only the first occurrence. So replace only // the first & with && name.replace(name.indexOf(QChar('&')), 1, QString("&&")); QAction *action = m_open_wallet_menu->addAction(name); if (i.second) { // This wallet is already loaded action->setEnabled(false); continue; } connect(action, &QAction::triggered, [this, path] { auto activity = new OpenWalletActivity(m_wallet_controller, this, this->config->GetChainParams()); connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); activity->open(path); }); } if (m_open_wallet_menu->isEmpty()) { QAction *action = m_open_wallet_menu->addAction(tr("No wallets available")); action->setEnabled(false); } }); connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); connect(m_create_wallet_action, &QAction::triggered, [this] { auto activity = new CreateWalletActivity( m_wallet_controller, this, this->config->GetChainParams()); connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); activity->create(); }); } #endif // ENABLE_WALLET connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole); connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow); } void BitcoinGUI::createMenuBar() { #ifdef Q_OS_MAC // Create a decoupled menu bar on Mac which stays even if the window is // closed appMenuBar = new QMenuBar(); #else // Get the main window's menu bar on other platforms appMenuBar = menuBar(); #endif // Configure the menus QMenu *file = appMenuBar->addMenu(tr("&File")); if (walletFrame) { file->addAction(m_create_wallet_action); file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addSeparator(); file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addAction(m_load_psbt_action); file->addSeparator(); } file->addAction(quitAction); QMenu *settings = appMenuBar->addMenu(tr("&Settings")); if (walletFrame) { settings->addAction(encryptWalletAction); settings->addAction(changePassphraseAction); settings->addSeparator(); } settings->addAction(optionsAction); QMenu *window_menu = appMenuBar->addMenu(tr("&Window")); QAction *minimize_action = window_menu->addAction(tr("Minimize")); minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); connect(minimize_action, &QAction::triggered, [] { QApplication::activeWindow()->showMinimized(); }); connect(qApp, &QApplication::focusWindowChanged, [minimize_action](QWindow *window) { minimize_action->setEnabled( window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); }); #ifdef Q_OS_MAC QAction *zoom_action = window_menu->addAction(tr("Zoom")); connect(zoom_action, &QAction::triggered, [] { QWindow *window = qApp->focusWindow(); if (window->windowState() != Qt::WindowMaximized) { window->showMaximized(); } else { window->showNormal(); } }); connect(qApp, &QApplication::focusWindowChanged, [zoom_action](QWindow *window) { zoom_action->setEnabled(window != nullptr); }); #endif if (walletFrame) { #ifdef Q_OS_MAC window_menu->addSeparator(); QAction *main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { GUIUtil::bringToFront(this); }); #endif window_menu->addSeparator(); window_menu->addAction(usedSendingAddressesAction); window_menu->addAction(usedReceivingAddressesAction); } window_menu->addSeparator(); for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) { QAction *tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type)); tab_action->setShortcut(rpcConsole->tabShortcut(tab_type)); connect(tab_action, &QAction::triggered, [this, tab_type] { rpcConsole->setTabFocus(tab_type); showDebugWindow(); }); } QMenu *help = appMenuBar->addMenu(tr("&Help")); help->addAction(showHelpMessageAction); help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } void BitcoinGUI::createToolBars() { if (walletFrame) { QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); appToolBar = toolbar; toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); overviewAction->setChecked(true); #ifdef ENABLE_WALLET QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); toolbar->addWidget(spacer); m_wallet_selector = new QComboBox(); m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(m_wallet_selector, static_cast( &QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex); m_wallet_selector_label = new QLabel(); m_wallet_selector_label->setText(tr("Wallet:") + " "); m_wallet_selector_label->setBuddy(m_wallet_selector); m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label); m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector); m_wallet_selector_label_action->setVisible(false); m_wallet_selector_action->setVisible(false); #endif } } void BitcoinGUI::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; if (_clientModel) { // Create system tray menu (or setup the dock menu) that late to prevent // users from calling actions, while the client has not yet fully loaded createTrayIconMenu(); // Keep up to date with client updateNetworkState(); connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections); connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive); modalOverlay->setKnownBestHeight( _clientModel->getHeaderTipHeight(), QDateTime::fromTime_t(_clientModel->getHeaderTipTime())); setNumBlocks(m_node.getNumBlocks(), QDateTime::fromTime_t(m_node.getLastBlockTime()), m_node.getVerificationProgress(), false, SynchronizationState::INIT_DOWNLOAD); connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks); // Receive and report messages from client model connect(_clientModel, &ClientModel::message, [this](const QString &title, const QString &message, unsigned int style) { this->message(title, message, style); }); // Show progress dialog connect(_clientModel, &ClientModel::showProgress, this, &BitcoinGUI::showProgress); rpcConsole->setClientModel(_clientModel); updateProxyIcon(); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(_clientModel); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel *optionsModel = _clientModel->getOptionsModel(); if (optionsModel && trayIcon) { // be aware of the tray icon disable state change reported by the // OptionsModel object. connect(optionsModel, &OptionsModel::hideTrayIconChanged, this, &BitcoinGUI::setTrayIconVisible); // initialize the disable state of the tray icon with the current // value in the model. setTrayIconVisible(optionsModel->getHideTrayIcon()); } } else { // Disable possibility to show main window via action toggleHideAction->setEnabled(false); if (trayIconMenu) { // Disable context menu on tray icon trayIconMenu->clear(); } // Propagate cleared model to child objects rpcConsole->setClientModel(nullptr); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(nullptr); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(nullptr); } } #ifdef ENABLE_WALLET void BitcoinGUI::setWalletController(WalletController *wallet_controller) { assert(!m_wallet_controller); assert(wallet_controller); m_wallet_controller = wallet_controller; m_create_wallet_action->setEnabled(true); m_open_wallet_action->setEnabled(true); m_open_wallet_action->setMenu(m_open_wallet_menu); connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); for (WalletModel *wallet_model : m_wallet_controller->getOpenWallets()) { addWallet(wallet_model); } } void BitcoinGUI::addWallet(WalletModel *walletModel) { if (!walletFrame) { return; } if (!walletFrame->addWallet(walletModel)) { return; } const QString display_name = walletModel->getDisplayName(); setWalletActionsEnabled(true); rpcConsole->addWallet(walletModel); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); if (m_wallet_selector->count() == 2) { m_wallet_selector_label_action->setVisible(true); m_wallet_selector_action->setVisible(true); } } void BitcoinGUI::removeWallet(WalletModel *walletModel) { if (!walletFrame) { return; } labelWalletHDStatusIcon->hide(); labelWalletEncryptionIcon->hide(); int index = m_wallet_selector->findData(QVariant::fromValue(walletModel)); m_wallet_selector->removeItem(index); if (m_wallet_selector->count() == 0) { setWalletActionsEnabled(false); } else if (m_wallet_selector->count() == 1) { m_wallet_selector_label_action->setVisible(false); m_wallet_selector_action->setVisible(false); } rpcConsole->removeWallet(walletModel); walletFrame->removeWallet(walletModel); updateWindowTitle(); } void BitcoinGUI::setCurrentWallet(WalletModel *wallet_model) { if (!walletFrame) { return; } walletFrame->setCurrentWallet(wallet_model); for (int index = 0; index < m_wallet_selector->count(); ++index) { if (m_wallet_selector->itemData(index).value() == wallet_model) { m_wallet_selector->setCurrentIndex(index); break; } } updateWindowTitle(); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { WalletModel *wallet_model = m_wallet_selector->itemData(index).value(); if (wallet_model) { setCurrentWallet(wallet_model); } } void BitcoinGUI::removeAllWallets() { if (!walletFrame) { return; } setWalletActionsEnabled(false); walletFrame->removeAllWallets(); } #endif // ENABLE_WALLET void BitcoinGUI::setWalletActionsEnabled(bool enabled) { overviewAction->setEnabled(enabled); sendCoinsAction->setEnabled(enabled); sendCoinsMenuAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); m_close_wallet_action->setEnabled(enabled); } void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); trayIcon->setToolTip(toolTip); } #endif } void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC // Return if trayIcon is unset (only on non-macOSes) if (!trayIcon) { return; } trayIcon->setContextMenu(trayIconMenu.get()); connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); #else // Note: On macOS, the Dock icon is used to provide the tray's // functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); trayIconMenu->setAsDockMenu(); #endif // Configuration of the tray icon (or Dock icon) menu #ifndef Q_OS_MAC // Note: On macOS, the Dock icon's menu already has Show / Hide action. trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); #endif if (enableWallet) { trayIconMenu->addAction(sendCoinsMenuAction); trayIconMenu->addAction(receiveCoinsMenuAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); } trayIconMenu->addAction(optionsAction); trayIconMenu->addAction(openRPCConsoleAction); #ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif } #ifndef Q_OS_MAC void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { // Click on system tray icon triggers show/hide of the main window toggleHidden(); } } #else void BitcoinGUI::macosDockIconActivated() { show(); activateWindow(); } #endif void BitcoinGUI::optionsClicked() { openOptionsDialogWithTab(OptionsDialog::TAB_MAIN); } void BitcoinGUI::aboutClicked() { if (!clientModel) { return; } HelpMessageDialog dlg(this, true); dlg.exec(); } void BitcoinGUI::showDebugWindow() { GUIUtil::bringToFront(rpcConsole); Q_EMIT consoleShown(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() { rpcConsole->setTabFocus(RPCConsole::TabTypes::CONSOLE); showDebugWindow(); } void BitcoinGUI::showHelpMessageClicked() { helpMessageDialog->show(); } #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { OpenURIDialog dlg(config->GetChainParams(), this); if (dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); } } void BitcoinGUI::gotoOverviewPage() { overviewAction->setChecked(true); if (walletFrame) { walletFrame->gotoOverviewPage(); } } void BitcoinGUI::gotoHistoryPage() { historyAction->setChecked(true); if (walletFrame) { walletFrame->gotoHistoryPage(); } } void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); if (walletFrame) { walletFrame->gotoReceiveCoinsPage(); } } void BitcoinGUI::gotoSendCoinsPage(QString addr) { sendCoinsAction->setChecked(true); if (walletFrame) { walletFrame->gotoSendCoinsPage(addr); } } void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) { walletFrame->gotoSignMessageTab(addr); } } void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) { walletFrame->gotoVerifyMessageTab(addr); } } void BitcoinGUI::gotoLoadPSBT() { if (walletFrame) { walletFrame->gotoLoadPSBT(); } } #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() { int count = clientModel->getNumConnections(); QString icon; switch (count) { case 0: icon = ":/icons/connect_0"; break; case 1: case 2: case 3: icon = ":/icons/connect_1"; break; case 4: case 5: case 6: icon = ":/icons/connect_2"; break; case 7: case 8: case 9: icon = ":/icons/connect_3"; break; default: icon = ":/icons/connect_4"; break; } QString tooltip; if (m_node.getNetworkActive()) { tooltip = tr("%n active connection(s) to Bitcoin network", "", count) + QString(".
") + tr("Click to disable network activity."); } else { tooltip = tr("Network activity disabled.") + QString("
") + tr("Click to enable network activity again."); icon = ":/icons/network_disabled"; } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); connectionsControl->setToolTip(tooltip); connectionsControl->setPixmap(platformStyle->SingleColorIcon(icon).pixmap( STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); } void BitcoinGUI::setNumConnections(int count) { updateNetworkState(); } void BitcoinGUI::setNetworkActive(bool networkActive) { updateNetworkState(); } void BitcoinGUI::updateHeadersSyncProgressLabel() { int64_t headersTipTime = clientModel->getHeaderTipTime(); int headersTipHeight = clientModel->getHeaderTipHeight(); int estHeadersLeft = (GetTime() - headersTipTime) / config->GetChainParams().GetConsensus().nPowTargetSpacing; if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) { progressBarLabel->setText( tr("Syncing Headers (%1%)...") .arg(QString::number(100.0 / (headersTipHeight + estHeadersLeft) * headersTipHeight, 'f', 1))); } } void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) { if (!clientModel || !clientModel->getOptionsModel()) { return; } OptionsDialog dlg(this, enableWallet); dlg.setCurrentTab(tab); dlg.setModel(clientModel->getOptionsModel()); dlg.exec(); } void BitcoinGUI::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. #ifdef Q_OS_MAC if (sync_state == SynchronizationState::POST_INIT) { m_app_nap_inhibitor->enableAppNap(); } else { m_app_nap_inhibitor->disableAppNap(); } #endif if (modalOverlay) { if (header) { modalOverlay->setKnownBestHeight(count, blockDate); } else { modalOverlay->tipUpdate(count, blockDate, nVerificationProgress); } } if (!clientModel) { return; } // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait // until chain-sync starts -> garbled text) statusBar()->clearMessage(); // Acquire current block source enum BlockSource blockSource = clientModel->getBlockSource(); switch (blockSource) { case BlockSource::NETWORK: if (header) { updateHeadersSyncProgressLabel(); return; } progressBarLabel->setText(tr("Synchronizing with network...")); updateHeadersSyncProgressLabel(); break; case BlockSource::DISK: if (header) { progressBarLabel->setText(tr("Indexing blocks on disk...")); } else { progressBarLabel->setText(tr("Processing blocks on disk...")); } break; case BlockSource::REINDEX: progressBarLabel->setText(tr("Reindexing blocks on disk...")); break; case BlockSource::NONE: if (header) { return; } progressBarLabel->setText(tr("Connecting to peers...")); break; } QString tooltip; QDateTime currentDate = QDateTime::currentDateTime(); qint64 secs = blockDate.secsTo(currentDate); tooltip = tr("Processed %n block(s) of transaction history.", "", count); // Set icon state: spinning if catching up, tick otherwise if (secs < MAX_BLOCK_TIME_GAP) { tooltip = tr("Up to date") + QString(".
") + tooltip; labelBlocksIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/synced") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(false); modalOverlay->showHide(true, true); } #endif // ENABLE_WALLET progressBarLabel->setVisible(false); progressBar->setVisible(false); } else { QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs); progressBarLabel->setVisible(true); progressBar->setFormat(tr("%1 behind").arg(timeBehindText)); progressBar->setMaximum(1000000000); progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5); progressBar->setVisible(true); tooltip = tr("Catching up...") + QString("
") + tooltip; if (count != prevBlocks) { labelBlocksIcon->setPixmap( platformStyle ->SingleColorIcon(QString(":/movies/spinner-%1") .arg(spinnerFrame, 3, 10, QChar('0'))) .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES; } prevBlocks = count; #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(true); modalOverlay->showHide(); } #endif // ENABLE_WALLET tooltip += QString("
"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); tooltip += QString("
"); tooltip += tr("Transactions after this will not yet be visible."); } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); } void BitcoinGUI::message(const QString &title, QString message, unsigned int style, bool *ret, const QString &detailed_message) { // Default title. On macOS, the window title is ignored (as required by the // macOS Guidelines). QString strTitle{PACKAGE_NAME}; // Default to information icon int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; QString msgType; if (!title.isEmpty()) { msgType = title; } else { switch (style) { case CClientUIInterface::MSG_ERROR: msgType = tr("Error"); message = tr("Error: %1").arg(message); break; case CClientUIInterface::MSG_WARNING: msgType = tr("Warning"); message = tr("Warning: %1").arg(message); break; case CClientUIInterface::MSG_INFORMATION: msgType = tr("Information"); // No need to prepend the prefix here. break; default: break; } } if (!msgType.isEmpty()) { strTitle += " - " + msgType; } if (style & CClientUIInterface::ICON_ERROR) { nMBoxIcon = QMessageBox::Critical; nNotifyIcon = Notificator::Critical; } else if (style & CClientUIInterface::ICON_WARNING) { nMBoxIcon = QMessageBox::Warning; nNotifyIcon = Notificator::Warning; } if (style & CClientUIInterface::MODAL) { // Check for buttons, use OK as default, if none was supplied QMessageBox::StandardButton buttons; if (!(buttons = (QMessageBox::StandardButton)( style & CClientUIInterface::BTN_MASK))) { buttons = QMessageBox::Ok; } showNormalIfMinimized(); QMessageBox mBox(static_cast(nMBoxIcon), strTitle, message, buttons, this); mBox.setTextFormat(Qt::PlainText); mBox.setDetailedText(detailed_message); int r = mBox.exec(); if (ret != nullptr) { *ret = r == QMessageBox::Ok; } } else { notificator->notify(static_cast(nNotifyIcon), strTitle, message); } } void BitcoinGUI::changeEvent(QEvent *e) { QMainWindow::changeEvent(e); #ifndef Q_OS_MAC // Ignored on Mac if (e->type() == QEvent::WindowStateChange) { if (clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) { QWindowStateChangeEvent *wsevt = static_cast(e); if (!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized()) { QTimer::singleShot(0, this, &BitcoinGUI::hide); e->ignore(); } else if ((wsevt->oldState() & Qt::WindowMinimized) && !isMinimized()) { QTimer::singleShot(0, this, &BitcoinGUI::show); e->ignore(); } } } #endif } void BitcoinGUI::closeEvent(QCloseEvent *event) { #ifndef Q_OS_MAC // Ignored on Mac if (clientModel && clientModel->getOptionsModel()) { if (!clientModel->getOptionsModel()->getMinimizeOnClose()) { // close rpcConsole in case it was open to make some space for the // shutdown window rpcConsole->close(); QApplication::quit(); } else { QMainWindow::showMinimized(); event->ignore(); } } #else QMainWindow::closeEvent(event); #endif } void BitcoinGUI::showEvent(QShowEvent *event) { // enable the debug window when the main window shows up openRPCConsoleAction->setEnabled(true); aboutAction->setEnabled(true); optionsAction->setEnabled(true); } #ifdef ENABLE_WALLET void BitcoinGUI::incomingTransaction(const QString &date, int unit, const Amount amount, const QString &type, const QString &address, const QString &label, const QString &walletName) { // On new transaction, make an info balloon QString msg = tr("Date: %1\n").arg(date) + tr("Amount: %1\n") .arg(BitcoinUnits::formatWithUnit(unit, amount, true)); if (m_node.getWallets().size() > 1 && !walletName.isEmpty()) { msg += tr("Wallet: %1\n").arg(walletName); } msg += tr("Type: %1\n").arg(type); if (!label.isEmpty()) { msg += tr("Label: %1\n").arg(label); } else if (!address.isEmpty()) { msg += tr("Address: %1\n").arg(address); } message(amount < Amount::zero() ? tr("Sent transaction") : tr("Incoming transaction"), msg, CClientUIInterface::MSG_INFORMATION); } #endif // ENABLE_WALLET void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void BitcoinGUI::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { for (const QUrl &uri : event->mimeData()->urls()) { Q_EMIT receivedURI(uri.toString()); } } event->acceptProposedAction(); } bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) { // Catch status tip events if (event->type() == QEvent::StatusTip) { // Prevent adding text from setStatusTip(), if we currently use the // status bar for displaying other stuff if (progressBarLabel->isVisible() || progressBar->isVisible()) { return true; } } return QMainWindow::eventFilter(object, event); } #ifdef ENABLE_WALLET bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient &recipient) { // URI has to be valid if (walletFrame && walletFrame->handlePaymentRequest(recipient)) { showNormalIfMinimized(); gotoSendCoinsPage(); return true; } return false; } void BitcoinGUI::setHDStatus(bool privkeyDisabled, int hdEnabled) { labelWalletHDStatusIcon->setPixmap( platformStyle ->SingleColorIcon(privkeyDisabled ? ":/icons/eye" : hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletHDStatusIcon->setToolTip( privkeyDisabled ? tr("Private key disabled") : hdEnabled ? tr("HD key generation is enabled") : tr("HD key generation is disabled")); labelWalletHDStatusIcon->show(); // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); } void BitcoinGUI::setEncryptionStatus(int status) { switch (status) { case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); changePassphraseAction->setEnabled(false); encryptWalletAction->setEnabled(true); break; case WalletModel::Unlocked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_open") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently unlocked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_closed") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently locked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; } } void BitcoinGUI::updateWalletStatus() { if (!walletFrame) { return; } WalletView *const walletView = walletFrame->currentWalletView(); if (!walletView) { return; } WalletModel *const walletModel = walletView->getWalletModel(); setEncryptionStatus(walletModel->getEncryptionStatus()); setHDStatus(walletModel->wallet().privateKeysDisabled(), walletModel->wallet().hdEnabled()); } #endif // ENABLE_WALLET void BitcoinGUI::updateProxyIcon() { std::string ip_port; bool proxy_enabled = clientModel->getProxyInfo(ip_port); if (proxy_enabled) { if (!labelProxyIcon->hasPixmap()) { QString ip_port_q = QString::fromStdString(ip_port); labelProxyIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/proxy") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelProxyIcon->setToolTip( tr("Proxy is enabled: %1").arg(ip_port_q)); } else { labelProxyIcon->show(); } } else { labelProxyIcon->hide(); } } void BitcoinGUI::updateWindowTitle() { QString window_title = PACKAGE_NAME; #ifdef ENABLE_WALLET if (walletFrame) { WalletModel *const wallet_model = walletFrame->currentWalletModel(); if (wallet_model && !wallet_model->getWalletName().isEmpty()) { window_title += " - " + wallet_model->getDisplayName(); } } #endif if (!m_network_style->getTitleAddText().isEmpty()) { window_title += " - " + m_network_style->getTitleAddText(); } setWindowTitle(window_title); } void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if (!clientModel) { return; } if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) { hide(); } else { GUIUtil::bringToFront(this); } } void BitcoinGUI::toggleHidden() { showNormalIfMinimized(true); } void BitcoinGUI::detectShutdown() { if (m_node.shutdownRequested()) { if (rpcConsole) { rpcConsole->hide(); } qApp->quit(); } } void BitcoinGUI::showProgress(const QString &title, int nProgress) { if (nProgress == 0) { progressDialog = new QProgressDialog(title, QString(), 0, 100); GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); } else if (nProgress == 100) { if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); progressDialog = nullptr; } } else if (progressDialog) { progressDialog->setValue(nProgress); } } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) { if (trayIcon) { trayIcon->setVisible(!fHideTrayIcon); } } void BitcoinGUI::showModalOverlay() { if (modalOverlay && (progressBar->isVisible() || modalOverlay->isLayerVisible())) { modalOverlay->toggleVisibility(); } } static bool ThreadSafeMessageBox(BitcoinGUI *gui, const bilingual_str &message, const std::string &caption, unsigned int style) { bool modal = (style & CClientUIInterface::MODAL); // The SECURE flag has no effect in the Qt GUI. // bool secure = (style & CClientUIInterface::SECURE); style &= ~CClientUIInterface::SECURE; bool ret = false; // This is original message, in English, for googling and referencing. QString detailed_message; if (message.original != message.translated) { detailed_message = BitcoinGUI::tr("Original message:") + "\n" + QString::fromStdString(message.original); } // In case of modal message, use blocking connection to wait for user to // click a button bool invoked = QMetaObject::invokeMethod( gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message.translated)), Q_ARG(unsigned int, style), Q_ARG(bool *, &ret), Q_ARG(QString, detailed_message)); assert(invoked); return ret; } void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client m_handler_message_box = m_node.handleMessageBox( std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_handler_question = m_node.handleQuestion( std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_3, std::placeholders::_4)); } void BitcoinGUI::unsubscribeFromCoreSignals() { // Disconnect signals from client m_handler_message_box->disconnect(); m_handler_question->disconnect(); } UnitDisplayStatusBarControl::UnitDisplayStatusBarControl( const PlatformStyle *platformStyle) : optionsModel(nullptr), menu(nullptr) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); QList units = BitcoinUnits::availableUnits(); int max_width = 0; const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); setStyleSheet(QString("QLabel { color : %1 }") .arg(platformStyle->SingleColor().name())); } /** So that it responds to button clicks */ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) { onDisplayUnitsClicked(event->pos()); } /** Creates context menu, its actions, and wires up all the relevant signals for * mouse events. */ void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { QAction *menuAction = new QAction(QString(BitcoinUnits::longName(u)), this); menuAction->setData(QVariant(u)); menu->addAction(menuAction); } connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection); } /** Lets the control know about the Options Model (and its signals) */ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) { if (_optionsModel) { this->optionsModel = _optionsModel; // be aware of a display unit change reported by the OptionsModel // object. connect(_optionsModel, &OptionsModel::displayUnitChanged, this, &UnitDisplayStatusBarControl::updateDisplayUnit); // initialize the display units label with the current value in the // model. updateDisplayUnit(_optionsModel->getDisplayUnit()); } } /** When Display Units are changed on OptionsModel it will refresh the display * text of the control on the status bar */ void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) { setText(BitcoinUnits::longName(newUnits)); } /** Shows context menu with Display Unit options by the mouse coordinates */ void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint &point) { QPoint globalPos = mapToGlobal(point); menu->exec(globalPos); } /** Tells underlying optionsModel to update its current display unit. */ void UnitDisplayStatusBarControl::onMenuSelection(QAction *action) { if (action) { optionsModel->setDisplayUnit(action->data()); } } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 3111ba3fa..c0708a7cc 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -1,819 +1,821 @@ // 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 #include #include #include #include #include #include #include #include #include #include #include QList CoinControlDialog::payAmounts; bool CoinControlDialog::fSubtractFeeFromAmount = false; bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS) { return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); } return QTreeWidgetItem::operator<(other); } CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); // context menu actions QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // we need to enable/disable this copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this unlockAction = new QAction(tr("Unlock unspent"), this); // context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTransactionHashAction); contextMenu->addSeparator(); contextMenu->addAction(lockAction); contextMenu->addAction(unlockAction); // context menu signals connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu); connect(copyAddressAction, &QAction::triggered, this, &CoinControlDialog::copyAddress); connect(copyLabelAction, &QAction::triggered, this, &CoinControlDialog::copyLabel); connect(copyAmountAction, &QAction::triggered, this, &CoinControlDialog::copyAmount); connect(copyTransactionHashAction, &QAction::triggered, this, &CoinControlDialog::copyTransactionHash); connect(lockAction, &QAction::triggered, this, &CoinControlDialog::lockCoin); connect(unlockAction, &QAction::triggered, this, &CoinControlDialog::unlockCoin); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, &QAction::triggered, this, &CoinControlDialog::clipboardQuantity); connect(clipboardAmountAction, &QAction::triggered, this, &CoinControlDialog::clipboardAmount); connect(clipboardFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardFee); connect(clipboardAfterFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardAfterFee); connect(clipboardBytesAction, &QAction::triggered, this, &CoinControlDialog::clipboardBytes); connect(clipboardLowOutputAction, &QAction::triggered, this, &CoinControlDialog::clipboardLowOutput); connect(clipboardChangeAction, &QAction::triggered, this, &CoinControlDialog::clipboardChange); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // toggle tree/list mode connect(ui->radioTreeMode, &QRadioButton::toggled, this, &CoinControlDialog::radioTreeMode); connect(ui->radioListMode, &QRadioButton::toggled, this, &CoinControlDialog::radioListMode); // click on checkbox connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &CoinControlDialog::viewItemChanged); // click on header ui->treeWidget->header()->setSectionsClickable(true); connect(ui->treeWidget->header(), &QHeaderView::sectionClicked, this, &CoinControlDialog::headerSectionClicked); // ok button connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CoinControlDialog::buttonBoxClicked); // (un)select all connect(ui->pushButtonSelectAll, &QPushButton::clicked, this, &CoinControlDialog::buttonSelectAllClicked); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); // restore list mode and sortorder as a convenience feature QSettings settings; if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) { ui->radioTreeMode->click(); } if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) { sortView(settings.value("nCoinControlSortColumn").toInt(), (static_cast( settings.value("nCoinControlSortOrder").toInt()))); } + + GUIUtil::handleCloseWindowShortcut(this); } CoinControlDialog::~CoinControlDialog() { QSettings settings; settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); settings.setValue("nCoinControlSortColumn", sortColumn); settings.setValue("nCoinControlSortOrder", (int)sortOrder); delete ui; } void CoinControlDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel() && _model->getAddressTableModel()) { updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(_model, this); } } // ok button void CoinControlDialog::buttonBoxClicked(QAbstractButton *button) { if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // closes the dialog done(QDialog::Accepted); } } // (un)select all void CoinControlDialog::buttonSelectAllClicked() { Qt::CheckState state = Qt::Checked; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) { state = Qt::Unchecked; break; } } ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) { ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); } } ui->treeWidget->setEnabled(true); if (state == Qt::Unchecked) { // just to be sure coinControl()->UnSelectAll(); } CoinControlDialog::updateLabels(model, this); } // context menu void CoinControlDialog::showMenu(const QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item) { contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree // roots in context menu if (item->data(COLUMN_ADDRESS, TxIdRole).toString().length() == 64) { COutPoint outpoint = buildOutPoint(item); // transaction hash is 64 characters (this means it is a child node, // so it is not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->wallet().isLockedCoin(outpoint)) { lockAction->setEnabled(false); unlockAction->setEnabled(true); } else { lockAction->setEnabled(true); unlockAction->setEnabled(false); } } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); } // show context menu contextMenu->exec(QCursor::pos()); } } // context menu action: copy amount void CoinControlDialog::copyAmount() { GUIUtil::setClipboard( BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); } // context menu action: copy label void CoinControlDialog::copyLabel() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); } } // context menu action: copy address void CoinControlDialog::copyAddress() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); } } // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { GUIUtil::setClipboard( contextMenuItem->data(COLUMN_ADDRESS, TxIdRole).toString()); } // context menu action: lock coin void CoinControlDialog::lockCoin() { if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) { contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } COutPoint outpoint = buildOutPoint(contextMenuItem); model->wallet().lockCoin(outpoint); contextMenuItem->setDisabled(true); contextMenuItem->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); updateLabelLocked(); } // context menu action: unlock coin void CoinControlDialog::unlockCoin() { COutPoint outpoint = buildOutPoint(contextMenuItem); model->wallet().unlockCoin(outpoint); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); } // copy label "Quantity" to clipboard void CoinControlDialog::clipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // copy label "Amount" to clipboard void CoinControlDialog::clipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // copy label "Dust" to clipboard void CoinControlDialog::clipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // treeview: sort void CoinControlDialog::sortView(int column, Qt::SortOrder order) { sortColumn = column; sortOrder = order; ui->treeWidget->sortItems(column, order); ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { // click on most left column -> do nothing if (logicalIndex == COLUMN_CHECKBOX) { ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) { sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); } else { sortColumn = logicalIndex; // if label or address then default => asc, else default => desc sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); } sortView(sortColumn, sortOrder); } } // toggle tree mode void CoinControlDialog::radioTreeMode(bool checked) { if (checked && model) { updateView(); } } // toggle list mode void CoinControlDialog::radioListMode(bool checked) { if (checked && model) { updateView(); } } // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem *item, int column) { // transaction hash is 64 characters (this means it is a child node, so it // is not a parent node in tree mode) if (column == COLUMN_CHECKBOX && item->data(COLUMN_ADDRESS, TxIdRole).toString().length() == 64) { COutPoint outpoint = buildOutPoint(item); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { coinControl()->UnSelect(outpoint); } else if (item->isDisabled()) { // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } else { coinControl()->Select(outpoint); } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all CoinControlDialog::updateLabels(model, this); } } } // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { std::vector vOutpts; model->wallet().listLockedCoins(vOutpts); if (vOutpts.size() > 0) { ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); ui->labelLocked->setVisible(true); } else { ui->labelLocked->setVisible(false); } } void CoinControlDialog::updateLabels(WalletModel *model, QDialog *dialog) { if (!model) { return; } // nPayAmount Amount nPayAmount = Amount::zero(); bool fDust = false; CMutableTransaction txDummy; for (const Amount &amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > Amount::zero()) { // Assumes a p2pkh script size CTxOut txout(amount, CScript() << std::vector(24, 0)); txDummy.vout.push_back(txout); fDust |= IsDust(txout, model->node().getDustRelayFee()); } } Amount nAmount = Amount::zero(); Amount nPayFee = Amount::zero(); Amount nAfterFee = Amount::zero(); Amount nChange = Amount::zero(); unsigned int nBytes = 0; unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; std::vector vCoinControl; coinControl()->ListSelected(vCoinControl); size_t i = 0; for (const auto &out : model->wallet().getCoins(vCoinControl)) { if (out.depth_in_main_chain < 0) { continue; } // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer const COutPoint &output = vCoinControl[i++]; if (out.is_spent) { coinControl()->UnSelect(output); continue; } // Quantity nQuantity++; // Amount nAmount += out.txout.nValue; // Bytes CTxDestination address; if (ExtractDestination(out.txout.scriptPubKey, address)) { CPubKey pubkey; PKHash *pkhash = boost::get(&address); if (pkhash && model->wallet().getPubKey(out.txout.scriptPubKey, CKeyID(*pkhash), pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); } else { // in all error cases, simply assume 148 here nBytesInputs += 148; } } else { nBytesInputs += 148; } } // calculation if (nQuantity > 0) { // Bytes // always assume +1 output for change here nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // in the subtract fee from amount case, we can tell if zero change // already and subtract the bytes, so that fee calculation afterwards is // accurate if (CoinControlDialog::fSubtractFeeFromAmount) { if (nAmount - nPayAmount == Amount::zero()) { nBytes -= 34; } } // Fee nPayFee = model->wallet().getMinimumFee(nBytes, *coinControl()); if (nPayAmount > Amount::zero()) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) { nChange -= nPayFee; } // Never create dust outputs; if we would, just add the dust to the // fee. if (nChange > Amount::zero() && nChange < MIN_CHANGE) { // Assumes a p2pkh script size CTxOut txout(nChange, CScript() << std::vector(24, 0)); if (IsDust(txout, model->node().getDustRelayFee())) { nPayFee += nChange; nChange = Amount::zero(); if (CoinControlDialog::fSubtractFeeFromAmount) { // we didn't detect lack of change above nBytes -= 34; } } } if (nChange == Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { nBytes -= 34; } } // after fee nAfterFee = std::max(nAmount - nPayFee, Amount::zero()); } // actually update labels int nDisplayUnit = BitcoinUnits::BCH; if (model && model->getOptionsModel()) { nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); } QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild("labelCoinControlAmount"); QLabel *l3 = dialog->findChild("labelCoinControlFee"); QLabel *l4 = dialog->findChild("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild("labelCoinControlBytes"); QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild("labelCoinControlLowOutputText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > Amount::zero()); // stats // Quantity l1->setText(QString::number(nQuantity)); // Amount l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Fee l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // After Fee l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // Bytes l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Dust l7->setText(fDust ? tr("yes") : tr("no")); // Change l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); if (nPayFee > Amount::zero()) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { l8->setText(ASYMP_UTF8 + l8->text()); } } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller " "than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary = (nBytes != 0) ? double(nPayFee / SATOSHI) / nBytes : 0; QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild("labelCoinControlLowOutputText") ->setToolTip(l7->toolTip()); dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); if (label) { label->setVisible(nChange < Amount::zero()); } } CCoinControl *CoinControlDialog::coinControl() { static CCoinControl coin_control; return &coin_control; } COutPoint CoinControlDialog::buildOutPoint(const QTreeWidgetItem *item) { TxId txid; txid.SetHex(item->data(COLUMN_ADDRESS, TxIdRole).toString().toStdString()); return COutPoint(txid, item->data(COLUMN_ADDRESS, VOutRole).toUInt()); } void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) { return; } bool treeMode = ui->radioTreeMode->isChecked(); ui->treeWidget->clear(); // performance, otherwise updateLabels would be called for every checked // checkbox ui->treeWidget->setEnabled(false); ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); for (const auto &coins : model->wallet().listCoins()) { CCoinControlWidgetItem *itemWalletAddress = new CCoinControlWidgetItem(); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); QString sWalletAddress = QString::fromStdString( EncodeCashAddr(coins.first, model->getChainParams())); QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); if (sWalletLabel.isEmpty()) { sWalletLabel = tr("(no label)"); } if (treeMode) { // wallet address ui->treeWidget->addTopLevelItem(itemWalletAddress); itemWalletAddress->setFlags(flgTristate); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); // address itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); } Amount nSum = Amount::zero(); int nChildren = 0; for (const auto &outpair : coins.second) { const COutPoint &output = std::get<0>(outpair); const interfaces::WalletTxOut &out = std::get<1>(outpair); nSum += out.txout.nValue; nChildren++; CCoinControlWidgetItem *itemOutput; if (treeMode) { itemOutput = new CCoinControlWidgetItem(itemWalletAddress); } else { itemOutput = new CCoinControlWidgetItem(ui->treeWidget); } itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; QString sAddress = ""; if (ExtractDestination(out.txout.scriptPubKey, outputAddress)) { sAddress = QString::fromStdString( EncodeCashAddr(outputAddress, model->getChainParams())); // if listMode or change => show bitcoin address. In tree mode, // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) { itemOutput->setText(COLUMN_ADDRESS, sAddress); } } // label if (!(sAddress == sWalletAddress)) { // change tooltip from where the change comes from itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)") .arg(sWalletLabel) .arg(sWalletAddress)); itemOutput->setText(COLUMN_LABEL, tr("(change)")); } else if (!treeMode) { QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); if (sLabel.isEmpty()) { sLabel = tr("(no label)"); } itemOutput->setText(COLUMN_LABEL, sLabel); } // amount itemOutput->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.txout.nValue)); // padding so that sorting works correctly itemOutput->setData( COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(out.txout.nValue / SATOSHI))); // date itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.time)); itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.time)); // confirmations itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.depth_in_main_chain)); itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.depth_in_main_chain)); // transaction id itemOutput->setData( COLUMN_ADDRESS, TxIdRole, QString::fromStdString(output.GetTxId().GetHex())); // vout index itemOutput->setData(COLUMN_ADDRESS, VOutRole, output.GetN()); // disable locked coins if (model->wallet().isLockedCoin(output)) { // just to be sure coinControl()->UnSelect(output); itemOutput->setDisabled(true); itemOutput->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); } // set checkbox if (coinControl()->IsSelected(output)) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } // amount if (treeMode) { itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(nSum / SATOSHI))); } } // expand all partially selected if (treeMode) { for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) { ui->treeWidget->topLevelItem(i)->setExpanded(true); } } } // sort view sortView(sortColumn, sortOrder); ui->treeWidget->setEnabled(true); } diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index ec02ec715..e1b5695c8 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -1,150 +1,152 @@ // 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 EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) : QDialog(parent), ui(new Ui::EditAddressDialog), mapper(nullptr), mode(_mode), model(nullptr) { ui->setupUi(this); GUIUtil::setupAddressWidget(ui->addressEdit, this); switch (mode) { case NewSendingAddress: setWindowTitle(tr("New sending address")); break; case EditReceivingAddress: setWindowTitle(tr("Edit receiving address")); ui->addressEdit->setEnabled(false); break; case EditSendingAddress: setWindowTitle(tr("Edit sending address")); break; } mapper = new QDataWidgetMapper(this); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); GUIUtil::ItemDelegate *delegate = new GUIUtil::ItemDelegate(mapper); connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this, &EditAddressDialog::reject); mapper->setItemDelegate(delegate); + + GUIUtil::handleCloseWindowShortcut(this); } EditAddressDialog::~EditAddressDialog() { delete ui; } void EditAddressDialog::setModel(AddressTableModel *_model) { this->model = _model; if (!_model) { return; } mapper->setModel(_model); mapper->addMapping(ui->labelEdit, AddressTableModel::Label); mapper->addMapping(ui->addressEdit, AddressTableModel::Address); } void EditAddressDialog::loadRow(int row) { mapper->setCurrentIndex(row); } bool EditAddressDialog::saveCurrentRow() { if (!model) { return false; } switch (mode) { case NewSendingAddress: address = model->addRow( AddressTableModel::Send, ui->labelEdit->text(), ui->addressEdit->text(), model->GetDefaultAddressType()); break; case EditReceivingAddress: case EditSendingAddress: if (mapper->submit()) { address = ui->addressEdit->text(); } break; } return !address.isEmpty(); } void EditAddressDialog::accept() { if (!model) { return; } if (!saveCurrentRow()) { switch (model->getEditStatus()) { case AddressTableModel::OK: // Failed with unknown reason. Just reject. break; case AddressTableModel::NO_CHANGES: // No changes were made during edit operation. Just reject. break; case AddressTableModel::INVALID_ADDRESS: QMessageBox::warning(this, windowTitle(), tr("The entered address \"%1\" is not a " "valid Bitcoin address.") .arg(ui->addressEdit->text()), QMessageBox::Ok, QMessageBox::Ok); break; case AddressTableModel::DUPLICATE_ADDRESS: QMessageBox::warning(this, windowTitle(), getDuplicateAddressWarning(), QMessageBox::Ok, QMessageBox::Ok); break; case AddressTableModel::WALLET_UNLOCK_FAILURE: QMessageBox::critical(this, windowTitle(), tr("Could not unlock wallet."), QMessageBox::Ok, QMessageBox::Ok); break; case AddressTableModel::KEY_GENERATION_FAILURE: QMessageBox::critical(this, windowTitle(), tr("New key generation failed."), QMessageBox::Ok, QMessageBox::Ok); break; } return; } QDialog::accept(); } QString EditAddressDialog::getDuplicateAddressWarning() const { QString dup_address = ui->addressEdit->text(); QString existing_label = model->labelForAddress(dup_address); QString existing_purpose = model->purposeForAddress(dup_address); if (existing_purpose == "receive" && (mode == NewSendingAddress || mode == EditSendingAddress)) { return tr("Address \"%1\" already exists as a receiving address with " "label " "\"%2\" and so cannot be added as a sending address.") .arg(dup_address) .arg(existing_label); } return tr("The entered address \"%1\" is already in the address book with " "label \"%2\".") .arg(dup_address) .arg(existing_label); } QString EditAddressDialog::getAddress() const { return address; } void EditAddressDialog::setAddress(const QString &_address) { this->address = _address; ui->addressEdit->setText(_address); } diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 116b686ff..7ad5f9dcc 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,918 +1,924 @@ // Copyright (c) 2011-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