diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 13a7b1a1d..e3a5c51a7 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,237 +1,243 @@ #include "wallettests.h" #include "chainparams.h" #include "config.h" #include "dstencode.h" #include "qt/bitcoinamountfield.h" #include "qt/optionsmodel.h" #include "qt/overviewpage.h" #include "qt/platformstyle.h" #include "qt/qvalidatedlineedit.h" #include "qt/receivecoinsdialog.h" #include "qt/receiverequestdialog.h" #include "qt/recentrequeststablemodel.h" #include "qt/sendcoinsdialog.h" #include "qt/sendcoinsentry.h" #include "qt/transactiontablemodel.h" #include "qt/walletmodel.h" #include "test/test_bitcoin.h" #include "validation.h" #include "wallet/wallet.h" #include #include #include #include #include #include #include #include namespace { -//! Press "Yes" button in modal send confirmation dialog. -void ConfirmSend() { - QTimer::singleShot(0, Qt::PreciseTimer, []() { +//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. +void ConfirmSend(QString *text = nullptr, bool cancel = false) { + QTimer::singleShot(0, Qt::PreciseTimer, [text, cancel]() { for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog *dialog = qobject_cast(widget); - QAbstractButton *button = dialog->button(QMessageBox::Yes); + if (text) { + *text = dialog->text(); + } + QAbstractButton *button = dialog->button( + cancel ? QMessageBox::Cancel : QMessageBox::Yes); button->setEnabled(true); button->click(); } } }); } //! Send coins to address and return txid. uint256 SendCoins(CWallet &wallet, SendCoinsDialog &sendCoinsDialog, const CTxDestination &address, Amount amount) { QVBoxLayout *entries = sendCoinsDialog.findChild("entries"); SendCoinsEntry *entry = qobject_cast(entries->itemAt(0)->widget()); entry->findChild("payTo")->setText( QString::fromStdString(EncodeDestination(address))); entry->findChild("payAmount")->setValue(amount); uint256 txid; boost::signals2::scoped_connection c = wallet.NotifyTransactionChanged.connect( [&txid](CWallet *, const uint256 &hash, ChangeType status) { if (status == CT_NEW) { txid = hash; } }); ConfirmSend(); QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); return txid; } //! Find index of txid in transaction list. QModelIndex FindTx(const QAbstractItemModel &model, const uint256 &txid) { QString hash = QString::fromStdString(txid.ToString()); int rows = model.rowCount({}); for (int row = 0; row < rows; ++row) { QModelIndex index = model.index(row, 0, {}); if (model.data(index, TransactionTableModel::TxHashRole) == hash) { return index; } } return {}; } //! Simple qt wallet tests. // // Test widgets can be debugged interactively calling show() on them and // manually running the event loop, e.g.: // // sendCoinsDialog.show(); // QEventLoop().exec(); // // This also requires overriding the default minimal Qt platform: // // src/qt/test/test_bitcoin-qt -platform xcb # Linux // src/qt/test/test_bitcoin-qt -platform windows # Windows // src/qt/test/test_bitcoin-qt -platform cocoa # macOS void TestGUI() { #ifdef Q_OS_MAC if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, // and fails to handle returned nulls // (https://bugreports.qt.io/browse/QTBUG-49686). QWARN("Skipping WalletTests on mac build with 'minimal' platform set " "due to Qt bugs. To run AppTests, invoke " "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a " "linux or windows build."); return; } #endif - // Set up wallet and chain with 101 blocks (1 mature block for spending). + // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; - test.CreateAndProcessBlock( - {}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); + for (int i = 0; i < 5; ++i) { + test.CreateAndProcessBlock( + {}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); + } bitdb.MakeMock(); std::unique_ptr dbw( new CWalletDBWrapper(&bitdb, "wallet_test.dat")); CWallet wallet(Params(), std::move(dbw)); bool firstRun; wallet.LoadWallet(firstRun); { LOCK(wallet.cs_wallet); wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive"); wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { LOCK(cs_main); wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); } wallet.SetBroadcastTransactions(true); // Create widgets for sending coins and listing transactions. std::unique_ptr platformStyle( PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); OptionsModel optionsModel; WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel); sendCoinsDialog.setModel(&walletModel); // Send two transactions, and verify they are added to transaction list. TransactionTableModel *transactionTableModel = walletModel.getTransactionTableModel(); - QCOMPARE(transactionTableModel->rowCount({}), 101); + QCOMPARE(transactionTableModel->rowCount({}), 105); uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CTxDestination(CKeyID()), 5 * COIN); uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CTxDestination(CKeyID()), 10 * COIN); - QCOMPARE(transactionTableModel->rowCount({}), 103); + QCOMPARE(transactionTableModel->rowCount({}), 107); QVERIFY(FindTx(*transactionTableModel, txid1).isValid()); QVERIFY(FindTx(*transactionTableModel, txid2).isValid()); // Check current balance on OverviewPage OverviewPage overviewPage(platformStyle.get()); overviewPage.setWalletModel(&walletModel); QLabel *balanceLabel = overviewPage.findChild("labelBalance"); QString balanceText = balanceLabel->text(); int unit = walletModel.getOptionsModel()->getDisplayUnit(); Amount balance = walletModel.getBalance(); QString balanceComparison = BitcoinUnits::formatWithUnit( unit, balance, false, BitcoinUnits::separatorAlways); QCOMPARE(balanceText, balanceComparison); // Check Request Payment button const Config &config = GetConfig(); ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get(), &config); receiveCoinsDialog.setModel(&walletModel); RecentRequestsTableModel *requestTableModel = walletModel.getRecentRequestsTableModel(); // Label input QLineEdit *labelInput = receiveCoinsDialog.findChild("reqLabel"); labelInput->setText("TEST_LABEL_1"); // Amount input BitcoinAmountField *amountInput = receiveCoinsDialog.findChild("reqAmount"); amountInput->setValue(1 * SATOSHI); // Message input QLineEdit *messageInput = receiveCoinsDialog.findChild("reqMessage"); messageInput->setText("TEST_MESSAGE_1"); int initialRowCount = requestTableModel->rowCount({}); QPushButton *requestPaymentButton = receiveCoinsDialog.findChild("receiveButton"); requestPaymentButton->click(); for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("ReceiveRequestDialog")) { ReceiveRequestDialog *receiveRequestDialog = qobject_cast(widget); QTextEdit *rlist = receiveRequestDialog->QObject::findChild("outUri"); QString paymentText = rlist->toPlainText(); QStringList paymentTextList = paymentText.split('\n'); QCOMPARE(paymentTextList.at(0), QString("Payment information")); QVERIFY(paymentTextList.at(1).indexOf( QString("URI: bitcoincash:")) != -1); QVERIFY(paymentTextList.at(2).indexOf(QString("Address:")) != -1); QCOMPARE(paymentTextList.at(3), QString("Amount: 0.00000001 ") + QString::fromStdString(CURRENCY_UNIT)); QCOMPARE(paymentTextList.at(4), QString("Label: TEST_LABEL_1")); QCOMPARE(paymentTextList.at(5), QString("Message: TEST_MESSAGE_1")); } } // Clear button QPushButton *clearButton = receiveCoinsDialog.findChild("clearButton"); clearButton->click(); QCOMPARE(labelInput->text(), QString("")); QCOMPARE(amountInput->value(), Amount::zero()); QCOMPARE(messageInput->text(), QString("")); // Check addition to history int currentRowCount = requestTableModel->rowCount({}); QCOMPARE(currentRowCount, initialRowCount + 1); // Check Remove button QTableView *table = receiveCoinsDialog.findChild("recentRequestsView"); table->selectRow(currentRowCount - 1); QPushButton *removeRequestButton = receiveCoinsDialog.findChild("removeRequestButton"); removeRequestButton->click(); QCOMPARE(requestTableModel->rowCount({}), currentRowCount - 1); bitdb.Flush(true); bitdb.Reset(); } } void WalletTests::walletTests() { TestGUI(); } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index c69d2c881..541ccb0db 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -1,662 +1,662 @@ // 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 "transactionview.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "csvmodelwriter.h" #include "editaddressdialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "transactiondescdialog.h" #include "transactionfilterproxy.h" #include "transactionrecord.h" #include "transactiontablemodel.h" #include "walletmodel.h" #include "ui_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), model(0), transactionProxyModel(0), transactionView(0), abandonAction(0), columnResizingFixer(0) { // Build filter row setContentsMargins(0, 0, 0, 0); QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0, 0, 0, 0); if (platformStyle->getUseExtraSpacing()) { hlayout->setSpacing(5); hlayout->addSpacing(26); } else { hlayout->setSpacing(0); hlayout->addSpacing(23); } watchOnlyWidget = new QComboBox(this); watchOnlyWidget->setFixedWidth(24); watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All); watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"), "", TransactionFilterProxy::WatchOnlyFilter_Yes); watchOnlyWidget->addItem( platformStyle->SingleColorIcon(":/icons/eye_minus"), "", TransactionFilterProxy::WatchOnlyFilter_No); hlayout->addWidget(watchOnlyWidget); dateWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { dateWidget->setFixedWidth(121); } else { dateWidget->setFixedWidth(120); } dateWidget->addItem(tr("All"), All); dateWidget->addItem(tr("Today"), Today); dateWidget->addItem(tr("This week"), ThisWeek); dateWidget->addItem(tr("This month"), ThisMonth); dateWidget->addItem(tr("Last month"), LastMonth); dateWidget->addItem(tr("This year"), ThisYear); dateWidget->addItem(tr("Range..."), Range); hlayout->addWidget(dateWidget); typeWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { typeWidget->setFixedWidth(121); } else { typeWidget->setFixedWidth(120); } typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES); typeWidget->addItem( tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); typeWidget->addItem( tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE( TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE( TransactionRecord::Generated)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); hlayout->addWidget(typeWidget); addressWidget = new QLineEdit(this); addressWidget->setPlaceholderText(tr("Enter address or label to search")); hlayout->addWidget(addressWidget); amountWidget = new QLineEdit(this); amountWidget->setPlaceholderText(tr("Min amount")); if (platformStyle->getUseExtraSpacing()) { amountWidget->setFixedWidth(97); } else { amountWidget->setFixedWidth(100); } amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); hlayout->addWidget(amountWidget); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); QTableView *view = new QTableView(this); vlayout->addLayout(hlayout); vlayout->addWidget(createDateRangeWidget()); vlayout->addWidget(view); vlayout->setSpacing(0); int width = view->verticalScrollBar()->sizeHint().width(); // Cover scroll bar width with spacing if (platformStyle->getUseExtraSpacing()) { hlayout->addSpacing(width + 2); } else { hlayout->addSpacing(width); } // Always show scroll bar view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->setTabKeyNavigation(false); view->setContextMenuPolicy(Qt::CustomContextMenu); view->installEventFilter(this); transactionView = view; // Actions abandonAction = new QAction(tr("Abandon transaction"), this); QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this); QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this); QAction *editLabelAction = new QAction(tr("Edit label"), this); QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTxIDAction); contextMenu->addAction(copyTxHexAction); contextMenu->addAction(copyTxPlainText); contextMenu->addAction(showDetailsAction); contextMenu->addSeparator(); contextMenu->addAction(abandonAction); contextMenu->addAction(editLabelAction); mapperThirdPartyTxUrls = new QSignalMapper(this); // Connect actions connect(mapperThirdPartyTxUrls, SIGNAL(mapped(QString)), this, SLOT(openThirdPartyTxUrl(QString))); connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int))); connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int))); connect(watchOnlyWidget, SIGNAL(activated(int)), this, SLOT(chooseWatchonly(int))); connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString))); connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString))); connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex))); connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx())); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); connect(copyTxHexAction, SIGNAL(triggered()), this, SLOT(copyTxHex())); connect(copyTxPlainText, SIGNAL(triggered()), this, SLOT(copyTxPlainText())); connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel())); connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); } void TransactionView::setModel(WalletModel *_model) { this->model = _model; if (_model) { transactionProxyModel = new TransactionFilterProxy(this); transactionProxyModel->setSourceModel( _model->getTransactionTableModel()); transactionProxyModel->setDynamicSortFilter(true); transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); transactionView->setModel(transactionProxyModel); transactionView->setAlternatingRowColors(true); transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection); transactionView->setSortingEnabled(true); transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); transactionView->verticalHeader()->hide(); transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer( transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this); if (_model->getOptionsModel()) { // Add third party transaction URLs to context menu QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split( "|", QString::SkipEmptyParts); for (int i = 0; i < listUrls.size(); ++i) { QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host(); if (!host.isEmpty()) { // use host as menu item label QAction *thirdPartyTxUrlAction = new QAction(host, this); if (i == 0) { contextMenu->addSeparator(); } contextMenu->addAction(thirdPartyTxUrlAction); connect(thirdPartyTxUrlAction, SIGNAL(triggered()), mapperThirdPartyTxUrls, SLOT(map())); mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed()); } } } // show/hide column Watch-only updateWatchOnlyColumn(_model->haveWatchOnly()); // Watch-only signal connect(_model, SIGNAL(notifyWatchonlyChanged(bool)), this, SLOT(updateWatchOnlyColumn(bool))); } } void TransactionView::chooseDate(int idx) { if (!transactionProxyModel) { return; } QDate current = QDate::currentDate(); dateRangeWidget->setVisible(false); switch (dateWidget->itemData(idx).toInt()) { case All: transactionProxyModel->setDateRange( TransactionFilterProxy::MIN_DATE, TransactionFilterProxy::MAX_DATE); break; case Today: transactionProxyModel->setDateRange( QDateTime(current), TransactionFilterProxy::MAX_DATE); break; case ThisWeek: { // Find last Monday QDate startOfWeek = current.addDays(-(current.dayOfWeek() - 1)); transactionProxyModel->setDateRange( QDateTime(startOfWeek), TransactionFilterProxy::MAX_DATE); } break; case ThisMonth: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), current.month(), 1)), TransactionFilterProxy::MAX_DATE); break; case LastMonth: transactionProxyModel->setDateRange( QDateTime( QDate(current.year(), current.month(), 1).addMonths(-1)), QDateTime(QDate(current.year(), current.month(), 1))); break; case ThisYear: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), 1, 1)), TransactionFilterProxy::MAX_DATE); break; case Range: dateRangeWidget->setVisible(true); dateRangeChanged(); break; } } void TransactionView::chooseType(int idx) { if (!transactionProxyModel) { return; } transactionProxyModel->setTypeFilter(typeWidget->itemData(idx).toInt()); } void TransactionView::chooseWatchonly(int idx) { if (!transactionProxyModel) { return; } transactionProxyModel->setWatchOnlyFilter( (TransactionFilterProxy::WatchOnlyFilter)watchOnlyWidget->itemData(idx) .toInt()); } void TransactionView::changedPrefix(const QString &prefix) { if (!transactionProxyModel) { return; } transactionProxyModel->setAddressPrefix(prefix); } void TransactionView::changedAmount(const QString &amount) { if (!transactionProxyModel) { return; } Amount amount_parsed = Amount::zero(); if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed)) { transactionProxyModel->setMinAmount(amount_parsed); } else { transactionProxyModel->setMinAmount(Amount::zero()); } } void TransactionView::exportClicked() { // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName( this, tr("Export Transaction History"), QString(), tr("Comma separated file (*.csv)"), nullptr); if (filename.isNull()) { return; } CSVModelWriter writer(filename); // name, column, role writer.setModel(transactionProxyModel); writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); if (model && model->haveWatchOnly()) { writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); } writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole); writer.addColumn(BitcoinUnits::getAmountColumnTitle( model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole); writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole); if (!writer.write()) { Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction " "history to %1.") .arg(filename), CClientUIInterface::MSG_ERROR); } else { Q_EMIT message( tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.") .arg(filename), CClientUIInterface::MSG_INFORMATION); } } void TransactionView::contextualMenu(const QPoint &point) { QModelIndex index = transactionView->indexAt(point); QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (selection.empty()) { return; } // check if transaction can be abandoned, disable context menu action in // case it doesn't TxId txid; txid.SetHex(selection.at(0) .data(TransactionTableModel::TxHashRole) .toString() .toStdString()); abandonAction->setEnabled(model->transactionCanBeAbandoned(txid)); if (index.isValid()) { - contextMenu->exec(QCursor::pos()); + contextMenu->popup(transactionView->viewport()->mapToGlobal(point)); } } void TransactionView::abandonTx() { if (!transactionView || !transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); // get the hash from the TxHashRole (QVariant / QString) QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); TxId txid; txid.SetHex(hashQStr.toStdString()); // Abandon the wallet transaction over the walletModel model->abandonTransaction(txid); // Update the table model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false); } void TransactionView::copyAddress() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); } void TransactionView::copyLabel() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole); } void TransactionView::copyAmount() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole); } void TransactionView::copyTxID() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole); } void TransactionView::copyTxHex() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole); } void TransactionView::copyTxPlainText() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); } void TransactionView::editLabel() { if (!transactionView->selectionModel() || !model) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { AddressTableModel *addressBook = model->getAddressTableModel(); if (!addressBook) { return; } QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); if (address.isEmpty()) { // If this transaction has no associated address, exit return; } // Is address in address book? Address book can miss address when a // transaction is sent from outside the UI. int idx = addressBook->lookupAddress(address); if (idx != -1) { // Edit sending / receiving address QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); // Determine type of address, launch appropriate editor dialog type QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); EditAddressDialog dlg(type == AddressTableModel::Receive ? EditAddressDialog::EditReceivingAddress : EditAddressDialog::EditSendingAddress, this); dlg.setModel(addressBook); dlg.loadRow(idx); dlg.exec(); } else { // Add sending address EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this); dlg.setModel(addressBook); dlg.setAddress(address); dlg.exec(); } } } void TransactionView::showDetails() { if (!transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0)); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } } void TransactionView::openThirdPartyTxUrl(QString url) { if (!transactionView || !transactionView->selectionModel()) { return; } QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (!selection.isEmpty()) { QDesktopServices::openUrl(QUrl::fromUserInput( url.replace("%s", selection.at(0) .data(TransactionTableModel::TxHashRole) .toString()))); } } QWidget *TransactionView::createDateRangeWidget() { dateRangeWidget = new QFrame(); dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); dateRangeWidget->setContentsMargins(1, 1, 1, 1); QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(23); layout->addWidget(new QLabel(tr("Range:"))); dateFrom = new QDateTimeEdit(this); dateFrom->setDisplayFormat("dd/MM/yy"); dateFrom->setCalendarPopup(true); dateFrom->setMinimumWidth(100); dateFrom->setDate(QDate::currentDate().addDays(-7)); layout->addWidget(dateFrom); layout->addWidget(new QLabel(tr("to"))); dateTo = new QDateTimeEdit(this); dateTo->setDisplayFormat("dd/MM/yy"); dateTo->setCalendarPopup(true); dateTo->setMinimumWidth(100); dateTo->setDate(QDate::currentDate()); layout->addWidget(dateTo); layout->addStretch(); // Hide by default dateRangeWidget->setVisible(false); // Notify on change connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); return dateRangeWidget; } void TransactionView::dateRangeChanged() { if (!transactionProxyModel) { return; } transactionProxyModel->setDateRange(QDateTime(dateFrom->date()), QDateTime(dateTo->date()).addDays(1)); } void TransactionView::focusTransaction(const QModelIndex &idx) { if (!transactionProxyModel) { return; } QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx); transactionView->scrollTo(targetIdx); transactionView->setCurrentIndex(targetIdx); transactionView->setFocus(); } void TransactionView::focusTransaction(const uint256 &txid) { if (!transactionProxyModel) { return; } const QModelIndexList results = this->model->getTransactionTableModel()->match( this->model->getTransactionTableModel()->index(0, 0), TransactionTableModel::TxHashRole, QString::fromStdString(txid.ToString()), -1); transactionView->setFocus(); transactionView->selectionModel()->clearSelection(); for (const QModelIndex &index : results) { const QModelIndex targetIndex = transactionProxyModel->mapFromSource(index); transactionView->selectionModel()->select( targetIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select); // Called once per destination to ensure all results are in view, unless // transactions are not ordered by (ascending or descending) date. transactionView->scrollTo(targetIndex); // scrollTo() does not scroll far enough the first time when // transactions are ordered by ascending date. if (index == results[0]) { transactionView->scrollTo(targetIndex); } } } // We override the virtual resizeEvent of the QWidget to adjust tables column // sizes as the tables width is proportional to the dialogs width. void TransactionView::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress); } // Need to override default Ctrl+C action for amount as default behaviour is // just to copy DisplayRole text bool TransactionView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier)) { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); return true; } } return QWidget::eventFilter(obj, event); } // show/hide column Watch-only void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) { watchOnlyWidget->setVisible(fHaveWatchOnly); transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly); }