transactionview.cpp
1 // Copyright (c) 2011-2022 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <qt/transactionview.h> 6 7 #include <qt/addresstablemodel.h> 8 #include <qt/bitcoinunits.h> 9 #include <qt/csvmodelwriter.h> 10 #include <qt/editaddressdialog.h> 11 #include <qt/guiutil.h> 12 #include <qt/optionsmodel.h> 13 #include <qt/platformstyle.h> 14 #include <qt/transactiondescdialog.h> 15 #include <qt/transactionfilterproxy.h> 16 #include <qt/transactionrecord.h> 17 #include <qt/transactiontablemodel.h> 18 #include <qt/walletmodel.h> 19 20 #include <node/interface_ui.h> 21 22 #include <chrono> 23 #include <optional> 24 25 #include <QApplication> 26 #include <QComboBox> 27 #include <QDateTimeEdit> 28 #include <QDesktopServices> 29 #include <QDoubleValidator> 30 #include <QHBoxLayout> 31 #include <QHeaderView> 32 #include <QLabel> 33 #include <QLineEdit> 34 #include <QMenu> 35 #include <QPoint> 36 #include <QScrollBar> 37 #include <QSettings> 38 #include <QTableView> 39 #include <QTimer> 40 #include <QUrl> 41 #include <QVBoxLayout> 42 43 TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) 44 : QWidget(parent), m_platform_style{platformStyle} 45 { 46 // Build filter row 47 setContentsMargins(0,0,0,0); 48 49 QHBoxLayout *hlayout = new QHBoxLayout(); 50 hlayout->setContentsMargins(0,0,0,0); 51 52 if (platformStyle->getUseExtraSpacing()) { 53 hlayout->setSpacing(5); 54 hlayout->addSpacing(26); 55 } else { 56 hlayout->setSpacing(0); 57 hlayout->addSpacing(23); 58 } 59 60 watchOnlyWidget = new QComboBox(this); 61 watchOnlyWidget->setFixedWidth(24); 62 watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All); 63 watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"), "", TransactionFilterProxy::WatchOnlyFilter_Yes); 64 watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_minus"), "", TransactionFilterProxy::WatchOnlyFilter_No); 65 hlayout->addWidget(watchOnlyWidget); 66 67 dateWidget = new QComboBox(this); 68 if (platformStyle->getUseExtraSpacing()) { 69 dateWidget->setFixedWidth(121); 70 } else { 71 dateWidget->setFixedWidth(120); 72 } 73 dateWidget->addItem(tr("All"), All); 74 dateWidget->addItem(tr("Today"), Today); 75 dateWidget->addItem(tr("This week"), ThisWeek); 76 dateWidget->addItem(tr("This month"), ThisMonth); 77 dateWidget->addItem(tr("Last month"), LastMonth); 78 dateWidget->addItem(tr("This year"), ThisYear); 79 dateWidget->addItem(tr("Rangeā¦"), Range); 80 hlayout->addWidget(dateWidget); 81 82 typeWidget = new QComboBox(this); 83 if (platformStyle->getUseExtraSpacing()) { 84 typeWidget->setFixedWidth(121); 85 } else { 86 typeWidget->setFixedWidth(120); 87 } 88 89 typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES); 90 typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | 91 TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); 92 typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | 93 TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); 94 typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); 95 typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); 96 97 hlayout->addWidget(typeWidget); 98 99 search_widget = new QLineEdit(this); 100 search_widget->setPlaceholderText(tr("Enter address, transaction id, or label to search")); 101 hlayout->addWidget(search_widget); 102 103 amountWidget = new QLineEdit(this); 104 amountWidget->setPlaceholderText(tr("Min amount")); 105 if (platformStyle->getUseExtraSpacing()) { 106 amountWidget->setFixedWidth(97); 107 } else { 108 amountWidget->setFixedWidth(100); 109 } 110 QDoubleValidator *amountValidator = new QDoubleValidator(0, 1e20, 8, this); 111 QLocale amountLocale(QLocale::C); 112 amountLocale.setNumberOptions(QLocale::RejectGroupSeparator); 113 amountValidator->setLocale(amountLocale); 114 amountWidget->setValidator(amountValidator); 115 hlayout->addWidget(amountWidget); 116 117 // Delay before filtering transactions 118 static constexpr auto input_filter_delay{200ms}; 119 120 QTimer* amount_typing_delay = new QTimer(this); 121 amount_typing_delay->setSingleShot(true); 122 amount_typing_delay->setInterval(input_filter_delay); 123 124 QTimer* prefix_typing_delay = new QTimer(this); 125 prefix_typing_delay->setSingleShot(true); 126 prefix_typing_delay->setInterval(input_filter_delay); 127 128 QVBoxLayout *vlayout = new QVBoxLayout(this); 129 vlayout->setContentsMargins(0,0,0,0); 130 vlayout->setSpacing(0); 131 132 transactionView = new QTableView(this); 133 transactionView->setObjectName("transactionView"); 134 vlayout->addLayout(hlayout); 135 vlayout->addWidget(createDateRangeWidget()); 136 vlayout->addWidget(transactionView); 137 vlayout->setSpacing(0); 138 int width = transactionView->verticalScrollBar()->sizeHint().width(); 139 // Cover scroll bar width with spacing 140 if (platformStyle->getUseExtraSpacing()) { 141 hlayout->addSpacing(width+2); 142 } else { 143 hlayout->addSpacing(width); 144 } 145 transactionView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 146 transactionView->setTabKeyNavigation(false); 147 transactionView->setContextMenuPolicy(Qt::CustomContextMenu); 148 transactionView->installEventFilter(this); 149 transactionView->setAlternatingRowColors(true); 150 transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); 151 transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection); 152 transactionView->setSortingEnabled(true); 153 transactionView->verticalHeader()->hide(); 154 155 QSettings settings; 156 if (!transactionView->horizontalHeader()->restoreState(settings.value("TransactionViewHeaderState").toByteArray())) { 157 transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH); 158 transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH); 159 transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); 160 transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); 161 transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); 162 transactionView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH); 163 transactionView->horizontalHeader()->setStretchLastSection(true); 164 } 165 166 contextMenu = new QMenu(this); 167 contextMenu->setObjectName("contextMenu"); 168 copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress); 169 copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel); 170 contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount); 171 contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID); 172 contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex); 173 contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText); 174 contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails); 175 contextMenu->addSeparator(); 176 bumpFeeAction = contextMenu->addAction(tr("Increase transaction &fee")); 177 GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee); 178 bumpFeeAction->setObjectName("bumpFeeAction"); 179 abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx); 180 contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel); 181 182 connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate); 183 connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType); 184 connect(watchOnlyWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseWatchonly); 185 connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay, qOverload<>(&QTimer::start)); 186 connect(amount_typing_delay, &QTimer::timeout, this, &TransactionView::changedAmount); 187 connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay, qOverload<>(&QTimer::start)); 188 connect(prefix_typing_delay, &QTimer::timeout, this, &TransactionView::changedSearch); 189 190 connect(transactionView, &QTableView::doubleClicked, this, &TransactionView::doubleClicked); 191 connect(transactionView, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu); 192 193 // Double-clicking on a transaction on the transaction history page shows details 194 connect(this, &TransactionView::doubleClicked, this, &TransactionView::showDetails); 195 // Highlight transaction after fee bump 196 connect(this, &TransactionView::bumpedFee, [this](const uint256& txid) { 197 focusTransaction(txid); 198 }); 199 } 200 201 TransactionView::~TransactionView() 202 { 203 QSettings settings; 204 settings.setValue("TransactionViewHeaderState", transactionView->horizontalHeader()->saveState()); 205 } 206 207 void TransactionView::setModel(WalletModel *_model) 208 { 209 this->model = _model; 210 if(_model) 211 { 212 transactionProxyModel = new TransactionFilterProxy(this); 213 transactionProxyModel->setSourceModel(_model->getTransactionTableModel()); 214 transactionProxyModel->setDynamicSortFilter(true); 215 transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); 216 transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 217 transactionProxyModel->setSortRole(Qt::EditRole); 218 transactionView->setModel(transactionProxyModel); 219 transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); 220 221 if (_model->getOptionsModel()) 222 { 223 // Add third party transaction URLs to context menu 224 QStringList listUrls = GUIUtil::SplitSkipEmptyParts(_model->getOptionsModel()->getThirdPartyTxUrls(), "|"); 225 bool actions_created = false; 226 for (int i = 0; i < listUrls.size(); ++i) 227 { 228 QString url = listUrls[i].trimmed(); 229 QString host = QUrl(url, QUrl::StrictMode).host(); 230 if (!host.isEmpty()) 231 { 232 if (!actions_created) { 233 contextMenu->addSeparator(); 234 actions_created = true; 235 } 236 /*: Transactions table context menu action to show the 237 selected transaction in a third-party block explorer. 238 %1 is a stand-in argument for the URL of the explorer. */ 239 contextMenu->addAction(tr("Show in %1").arg(host), [this, url] { openThirdPartyTxUrl(url); }); 240 } 241 } 242 } 243 244 // show/hide column Watch-only 245 updateWatchOnlyColumn(_model->wallet().haveWatchOnly()); 246 247 // Watch-only signal 248 connect(_model, &WalletModel::notifyWatchonlyChanged, this, &TransactionView::updateWatchOnlyColumn); 249 } 250 } 251 252 void TransactionView::changeEvent(QEvent* e) 253 { 254 if (e->type() == QEvent::PaletteChange) { 255 watchOnlyWidget->setItemIcon( 256 TransactionFilterProxy::WatchOnlyFilter_Yes, 257 m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_plus"))); 258 watchOnlyWidget->setItemIcon( 259 TransactionFilterProxy::WatchOnlyFilter_No, 260 m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_minus"))); 261 } 262 263 QWidget::changeEvent(e); 264 } 265 266 void TransactionView::chooseDate(int idx) 267 { 268 if (!transactionProxyModel) return; 269 QDate current = QDate::currentDate(); 270 dateRangeWidget->setVisible(false); 271 switch(dateWidget->itemData(idx).toInt()) 272 { 273 case All: 274 transactionProxyModel->setDateRange( 275 std::nullopt, 276 std::nullopt); 277 break; 278 case Today: 279 transactionProxyModel->setDateRange( 280 GUIUtil::StartOfDay(current), 281 std::nullopt); 282 break; 283 case ThisWeek: { 284 // Find last Monday 285 QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1)); 286 transactionProxyModel->setDateRange( 287 GUIUtil::StartOfDay(startOfWeek), 288 std::nullopt); 289 290 } break; 291 case ThisMonth: 292 transactionProxyModel->setDateRange( 293 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1)), 294 std::nullopt); 295 break; 296 case LastMonth: 297 transactionProxyModel->setDateRange( 298 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1).addMonths(-1)), 299 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1))); 300 break; 301 case ThisYear: 302 transactionProxyModel->setDateRange( 303 GUIUtil::StartOfDay(QDate(current.year(), 1, 1)), 304 std::nullopt); 305 break; 306 case Range: 307 dateRangeWidget->setVisible(true); 308 dateRangeChanged(); 309 break; 310 } 311 } 312 313 void TransactionView::chooseType(int idx) 314 { 315 if(!transactionProxyModel) 316 return; 317 transactionProxyModel->setTypeFilter( 318 typeWidget->itemData(idx).toInt()); 319 } 320 321 void TransactionView::chooseWatchonly(int idx) 322 { 323 if(!transactionProxyModel) 324 return; 325 transactionProxyModel->setWatchOnlyFilter( 326 static_cast<TransactionFilterProxy::WatchOnlyFilter>(watchOnlyWidget->itemData(idx).toInt())); 327 } 328 329 void TransactionView::changedSearch() 330 { 331 if(!transactionProxyModel) 332 return; 333 transactionProxyModel->setSearchString(search_widget->text()); 334 } 335 336 void TransactionView::changedAmount() 337 { 338 if(!transactionProxyModel) 339 return; 340 CAmount amount_parsed = 0; 341 if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amountWidget->text(), &amount_parsed)) { 342 transactionProxyModel->setMinAmount(amount_parsed); 343 } 344 else 345 { 346 transactionProxyModel->setMinAmount(0); 347 } 348 } 349 350 void TransactionView::exportClicked() 351 { 352 if (!model || !model->getOptionsModel()) { 353 return; 354 } 355 356 // CSV is currently the only supported format 357 QString filename = GUIUtil::getSaveFileName(this, 358 tr("Export Transaction History"), QString(), 359 /*: Expanded name of the CSV file format. 360 See: https://en.wikipedia.org/wiki/Comma-separated_values. */ 361 tr("Comma separated file") + QLatin1String(" (*.csv)"), nullptr); 362 363 if (filename.isNull()) 364 return; 365 366 CSVModelWriter writer(filename); 367 368 // name, column, role 369 writer.setModel(transactionProxyModel); 370 writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); 371 if (model->wallet().haveWatchOnly()) 372 writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); 373 writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); 374 writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); 375 writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); 376 writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole); 377 writer.addColumn(BitcoinUnits::getAmountColumnTitle(model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole); 378 writer.addColumn(tr("ID"), 0, TransactionTableModel::TxHashRole); 379 380 if(!writer.write()) { 381 Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction history to %1.").arg(filename), 382 CClientUIInterface::MSG_ERROR); 383 } 384 else { 385 Q_EMIT message(tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.").arg(filename), 386 CClientUIInterface::MSG_INFORMATION); 387 } 388 } 389 390 void TransactionView::contextualMenu(const QPoint &point) 391 { 392 QModelIndex index = transactionView->indexAt(point); 393 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); 394 if (selection.empty()) 395 return; 396 397 // check if transaction can be abandoned, disable context menu action in case it doesn't 398 uint256 hash; 399 hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString()); 400 abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash)); 401 bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash)); 402 copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole)); 403 copyLabelAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::LabelRole)); 404 405 if (index.isValid()) { 406 GUIUtil::PopupMenu(contextMenu, transactionView->viewport()->mapToGlobal(point)); 407 } 408 } 409 410 void TransactionView::abandonTx() 411 { 412 if(!transactionView || !transactionView->selectionModel()) 413 return; 414 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); 415 416 // get the hash from the TxHashRole (QVariant / QString) 417 uint256 hash; 418 QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); 419 hash.SetHex(hashQStr.toStdString()); 420 421 // Abandon the wallet transaction over the walletModel 422 model->wallet().abandonTransaction(hash); 423 } 424 425 void TransactionView::bumpFee([[maybe_unused]] bool checked) 426 { 427 if(!transactionView || !transactionView->selectionModel()) 428 return; 429 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); 430 431 // get the hash from the TxHashRole (QVariant / QString) 432 uint256 hash; 433 QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); 434 hash.SetHex(hashQStr.toStdString()); 435 436 // Bump tx fee over the walletModel 437 uint256 newHash; 438 if (model->bumpFee(hash, newHash)) { 439 // Update the table 440 transactionView->selectionModel()->clearSelection(); 441 model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, true); 442 443 qApp->processEvents(); 444 Q_EMIT bumpedFee(newHash); 445 } 446 } 447 448 void TransactionView::copyAddress() 449 { 450 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); 451 } 452 453 void TransactionView::copyLabel() 454 { 455 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole); 456 } 457 458 void TransactionView::copyAmount() 459 { 460 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole); 461 } 462 463 void TransactionView::copyTxID() 464 { 465 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHashRole); 466 } 467 468 void TransactionView::copyTxHex() 469 { 470 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole); 471 } 472 473 void TransactionView::copyTxPlainText() 474 { 475 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); 476 } 477 478 void TransactionView::editLabel() 479 { 480 if(!transactionView->selectionModel() ||!model) 481 return; 482 QModelIndexList selection = transactionView->selectionModel()->selectedRows(); 483 if(!selection.isEmpty()) 484 { 485 AddressTableModel *addressBook = model->getAddressTableModel(); 486 if(!addressBook) 487 return; 488 QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); 489 if(address.isEmpty()) 490 { 491 // If this transaction has no associated address, exit 492 return; 493 } 494 // Is address in address book? Address book can miss address when a transaction is 495 // sent from outside the UI. 496 int idx = addressBook->lookupAddress(address); 497 if(idx != -1) 498 { 499 // Edit sending / receiving address 500 QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); 501 // Determine type of address, launch appropriate editor dialog type 502 QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); 503 504 auto dlg = new EditAddressDialog( 505 type == AddressTableModel::Receive 506 ? EditAddressDialog::EditReceivingAddress 507 : EditAddressDialog::EditSendingAddress, this); 508 dlg->setModel(addressBook); 509 dlg->loadRow(idx); 510 GUIUtil::ShowModalDialogAsynchronously(dlg); 511 } 512 else 513 { 514 // Add sending address 515 auto dlg = new EditAddressDialog(EditAddressDialog::NewSendingAddress, 516 this); 517 dlg->setModel(addressBook); 518 dlg->setAddress(address); 519 GUIUtil::ShowModalDialogAsynchronously(dlg); 520 } 521 } 522 } 523 524 void TransactionView::showDetails() 525 { 526 if(!transactionView->selectionModel()) 527 return; 528 QModelIndexList selection = transactionView->selectionModel()->selectedRows(); 529 if(!selection.isEmpty()) 530 { 531 TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0)); 532 dlg->setAttribute(Qt::WA_DeleteOnClose); 533 m_opened_dialogs.append(dlg); 534 connect(dlg, &QObject::destroyed, [this, dlg] { 535 m_opened_dialogs.removeOne(dlg); 536 }); 537 dlg->show(); 538 } 539 } 540 541 void TransactionView::openThirdPartyTxUrl(QString url) 542 { 543 if(!transactionView || !transactionView->selectionModel()) 544 return; 545 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); 546 if(!selection.isEmpty()) 547 QDesktopServices::openUrl(QUrl::fromUserInput(url.replace("%s", selection.at(0).data(TransactionTableModel::TxHashRole).toString()))); 548 } 549 550 QWidget *TransactionView::createDateRangeWidget() 551 { 552 dateRangeWidget = new QFrame(); 553 dateRangeWidget->setFrameStyle(static_cast<int>(QFrame::Panel) | static_cast<int>(QFrame::Raised)); 554 dateRangeWidget->setContentsMargins(1,1,1,1); 555 QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); 556 layout->setContentsMargins(0,0,0,0); 557 layout->addSpacing(23); 558 layout->addWidget(new QLabel(tr("Range:"))); 559 560 dateFrom = new QDateTimeEdit(this); 561 dateFrom->setDisplayFormat("dd/MM/yy"); 562 dateFrom->setCalendarPopup(true); 563 dateFrom->setMinimumWidth(100); 564 dateFrom->setDate(QDate::currentDate().addDays(-7)); 565 layout->addWidget(dateFrom); 566 layout->addWidget(new QLabel(tr("to"))); 567 568 dateTo = new QDateTimeEdit(this); 569 dateTo->setDisplayFormat("dd/MM/yy"); 570 dateTo->setCalendarPopup(true); 571 dateTo->setMinimumWidth(100); 572 dateTo->setDate(QDate::currentDate()); 573 layout->addWidget(dateTo); 574 layout->addStretch(); 575 576 // Hide by default 577 dateRangeWidget->setVisible(false); 578 579 // Notify on change 580 connect(dateFrom, &QDateTimeEdit::dateChanged, this, &TransactionView::dateRangeChanged); 581 connect(dateTo, &QDateTimeEdit::dateChanged, this, &TransactionView::dateRangeChanged); 582 583 return dateRangeWidget; 584 } 585 586 void TransactionView::dateRangeChanged() 587 { 588 if(!transactionProxyModel) 589 return; 590 transactionProxyModel->setDateRange( 591 GUIUtil::StartOfDay(dateFrom->date()), 592 GUIUtil::StartOfDay(dateTo->date()).addDays(1)); 593 } 594 595 void TransactionView::focusTransaction(const QModelIndex &idx) 596 { 597 if(!transactionProxyModel) 598 return; 599 QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx); 600 transactionView->scrollTo(targetIdx); 601 transactionView->setCurrentIndex(targetIdx); 602 transactionView->setFocus(); 603 } 604 605 void TransactionView::focusTransaction(const uint256& txid) 606 { 607 if (!transactionProxyModel) 608 return; 609 610 const QModelIndexList results = this->model->getTransactionTableModel()->match( 611 this->model->getTransactionTableModel()->index(0,0), 612 TransactionTableModel::TxHashRole, 613 QString::fromStdString(txid.ToString()), -1); 614 615 transactionView->setFocus(); 616 transactionView->selectionModel()->clearSelection(); 617 for (const QModelIndex& index : results) { 618 const QModelIndex targetIndex = transactionProxyModel->mapFromSource(index); 619 transactionView->selectionModel()->select( 620 targetIndex, 621 QItemSelectionModel::Rows | QItemSelectionModel::Select); 622 // Called once per destination to ensure all results are in view, unless 623 // transactions are not ordered by (ascending or descending) date. 624 transactionView->scrollTo(targetIndex); 625 // scrollTo() does not scroll far enough the first time when transactions 626 // are ordered by ascending date. 627 if (index == results[0]) transactionView->scrollTo(targetIndex); 628 } 629 } 630 631 // Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text 632 bool TransactionView::eventFilter(QObject *obj, QEvent *event) 633 { 634 if (event->type() == QEvent::KeyPress) 635 { 636 QKeyEvent *ke = static_cast<QKeyEvent *>(event); 637 if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier)) 638 { 639 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); 640 return true; 641 } 642 } 643 if (event->type() == QEvent::EnabledChange) { 644 if (!isEnabled()) { 645 closeOpenedDialogs(); 646 } 647 } 648 return QWidget::eventFilter(obj, event); 649 } 650 651 // show/hide column Watch-only 652 void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) 653 { 654 watchOnlyWidget->setVisible(fHaveWatchOnly); 655 transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly); 656 } 657 658 void TransactionView::closeOpenedDialogs() 659 { 660 // close all dialogs opened from this view 661 for (QDialog* dlg : m_opened_dialogs) { 662 dlg->close(); 663 } 664 m_opened_dialogs.clear(); 665 }