transactionview.cpp raw
1 // Copyright (c) 2011-2022 The Limenka 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/limenkaunits.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
150 contextMenu = new QMenu(this);
151 contextMenu->setObjectName("contextMenu");
152 copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress);
153 copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel);
154 contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount);
155 contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID);
156 contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex);
157 contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText);
158 contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails);
159 contextMenu->addSeparator();
160 bumpFeeAction = contextMenu->addAction(tr("Increase transaction &fee"));
161 GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee);
162 bumpFeeAction->setObjectName("bumpFeeAction");
163 abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx);
164 contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel);
165
166 connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate);
167 connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType);
168 connect(watchOnlyWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseWatchonly);
169 connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay, qOverload<>(&QTimer::start));
170 connect(amount_typing_delay, &QTimer::timeout, this, &TransactionView::changedAmount);
171 connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay, qOverload<>(&QTimer::start));
172 connect(prefix_typing_delay, &QTimer::timeout, this, &TransactionView::changedSearch);
173
174 connect(transactionView, &QTableView::doubleClicked, this, &TransactionView::doubleClicked);
175 connect(transactionView, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu);
176
177 // Double-clicking on a transaction on the transaction history page shows details
178 connect(this, &TransactionView::doubleClicked, this, &TransactionView::showDetails);
179 // Highlight transaction after fee bump
180 connect(this, &TransactionView::bumpedFee, [this](const uint256& txid) {
181 focusTransaction(txid);
182 });
183 }
184
185 TransactionView::~TransactionView()
186 {
187 QSettings settings;
188 settings.setValue("TransactionViewHeaderState", transactionView->horizontalHeader()->saveState());
189 }
190
191 void TransactionView::setModel(WalletModel *_model)
192 {
193 this->model = _model;
194 if(_model)
195 {
196 transactionProxyModel = new TransactionFilterProxy(this);
197 transactionProxyModel->setSourceModel(_model->getTransactionTableModel());
198 transactionProxyModel->setDynamicSortFilter(true);
199 transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
200 transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
201 transactionProxyModel->setSortRole(Qt::EditRole);
202 transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
203 transactionView->setModel(transactionProxyModel);
204
205 transactionView->setAlternatingRowColors(true);
206 transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
207 transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
208 transactionView->horizontalHeader()->setSortIndicator(TransactionTableModel::Date, Qt::DescendingOrder);
209 transactionView->setSortingEnabled(true);
210 transactionView->verticalHeader()->hide();
211
212 columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this);
213
214 QSettings settings;
215 if (!transactionView->horizontalHeader()->restoreState(settings.value("TransactionViewHeaderState").toByteArray())) {
216 transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH);
217 transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH);
218 transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH);
219 transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
220 transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
221 }
222
223 if (_model->getOptionsModel())
224 {
225 // Add third party transaction URLs to context menu
226 QStringList listUrls = GUIUtil::SplitSkipEmptyParts(_model->getOptionsModel()->getThirdPartyTxUrls(), "|");
227 bool actions_created = false;
228 for (int i = 0; i < listUrls.size(); ++i)
229 {
230 QString url = listUrls[i].trimmed();
231 QString host = QUrl(url, QUrl::StrictMode).host();
232 if (!host.isEmpty())
233 {
234 if (!actions_created) {
235 contextMenu->addSeparator();
236 actions_created = true;
237 }
238 /*: Transactions table context menu action to show the
239 selected transaction in a third-party block explorer.
240 %1 is a stand-in argument for the URL of the explorer. */
241 contextMenu->addAction(tr("Show in %1").arg(host), [this, url] { openThirdPartyTxUrl(url); });
242 }
243 }
244 }
245
246 // show/hide column Watch-only
247 updateWatchOnlyColumn(_model->wallet().haveWatchOnly());
248
249 // Watch-only signal
250 connect(_model, &WalletModel::notifyWatchonlyChanged, this, &TransactionView::updateWatchOnlyColumn);
251 }
252 }
253
254 void TransactionView::changeEvent(QEvent* e)
255 {
256 if (e->type() == QEvent::PaletteChange) {
257 watchOnlyWidget->setItemIcon(
258 TransactionFilterProxy::WatchOnlyFilter_Yes,
259 m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_plus")));
260 watchOnlyWidget->setItemIcon(
261 TransactionFilterProxy::WatchOnlyFilter_No,
262 m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_minus")));
263 }
264
265 QWidget::changeEvent(e);
266 }
267
268 void TransactionView::chooseDate(int idx)
269 {
270 if (!transactionProxyModel) return;
271 QDate current = QDate::currentDate();
272 dateRangeWidget->setVisible(false);
273 switch(dateWidget->itemData(idx).toInt())
274 {
275 case All:
276 transactionProxyModel->setDateRange(
277 std::nullopt,
278 std::nullopt);
279 break;
280 case Today:
281 transactionProxyModel->setDateRange(
282 GUIUtil::StartOfDay(current),
283 std::nullopt);
284 break;
285 case ThisWeek: {
286 // Find last Monday
287 QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
288 transactionProxyModel->setDateRange(
289 GUIUtil::StartOfDay(startOfWeek),
290 std::nullopt);
291
292 } break;
293 case ThisMonth:
294 transactionProxyModel->setDateRange(
295 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1)),
296 std::nullopt);
297 break;
298 case LastMonth:
299 transactionProxyModel->setDateRange(
300 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1).addMonths(-1)),
301 GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1)));
302 break;
303 case ThisYear:
304 transactionProxyModel->setDateRange(
305 GUIUtil::StartOfDay(QDate(current.year(), 1, 1)),
306 std::nullopt);
307 break;
308 case Range:
309 dateRangeWidget->setVisible(true);
310 dateRangeChanged();
311 break;
312 }
313 }
314
315 void TransactionView::chooseType(int idx)
316 {
317 if(!transactionProxyModel)
318 return;
319 transactionProxyModel->setTypeFilter(
320 typeWidget->itemData(idx).toInt());
321 }
322
323 void TransactionView::chooseWatchonly(int idx)
324 {
325 if(!transactionProxyModel)
326 return;
327 transactionProxyModel->setWatchOnlyFilter(
328 static_cast<TransactionFilterProxy::WatchOnlyFilter>(watchOnlyWidget->itemData(idx).toInt()));
329 }
330
331 void TransactionView::changedSearch()
332 {
333 if(!transactionProxyModel)
334 return;
335 transactionProxyModel->setSearchString(search_widget->text());
336 }
337
338 void TransactionView::changedAmount()
339 {
340 if(!transactionProxyModel)
341 return;
342 CAmount amount_parsed = 0;
343 if (LimenkaUnits::parse(model->getOptionsModel()->getDisplayUnit(), amountWidget->text(), &amount_parsed)) {
344 transactionProxyModel->setMinAmount(amount_parsed);
345 }
346 else
347 {
348 transactionProxyModel->setMinAmount(0);
349 }
350 }
351
352 void TransactionView::exportClicked()
353 {
354 if (!model || !model->getOptionsModel()) {
355 return;
356 }
357
358 // CSV is currently the only supported format
359 QString filename = GUIUtil::getSaveFileName(this,
360 tr("Export Transaction History"), QString(),
361 /*: Expanded name of the CSV file format.
362 See: https://en.wikipedia.org/wiki/Comma-separated_values. */
363 tr("Comma separated file") + QLatin1String(" (*.csv)"), nullptr);
364
365 if (filename.isNull())
366 return;
367
368 CSVModelWriter writer(filename);
369
370 // name, column, role
371 writer.setModel(transactionProxyModel);
372 writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
373 if (model->wallet().haveWatchOnly())
374 writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly);
375 writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
376 writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
377 writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
378 writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
379 writer.addColumn(LimenkaUnits::getAmountColumnTitle(model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole);
380 writer.addColumn(tr("ID"), 0, TransactionTableModel::TxHashRole);
381
382 if(!writer.write()) {
383 Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction history to %1.").arg(filename),
384 CClientUIInterface::MSG_ERROR);
385 }
386 else {
387 Q_EMIT message(tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.").arg(filename),
388 CClientUIInterface::MSG_INFORMATION);
389 }
390 }
391
392 void TransactionView::contextualMenu(const QPoint &point)
393 {
394 QModelIndex index = transactionView->indexAt(point);
395 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
396 if (selection.empty())
397 return;
398
399 // check if transaction can be abandoned, disable context menu action in case it doesn't
400 uint256 hash;
401 hash.SetHexDeprecated(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
402 abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash));
403 bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash));
404 copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole));
405 copyLabelAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::LabelRole));
406
407 if (index.isValid()) {
408 GUIUtil::PopupMenu(contextMenu, transactionView->viewport()->mapToGlobal(point));
409 }
410 }
411
412 void TransactionView::abandonTx()
413 {
414 if(!transactionView || !transactionView->selectionModel())
415 return;
416 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
417
418 // get the hash from the TxHashRole (QVariant / QString)
419 uint256 hash;
420 QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
421 hash.SetHexDeprecated(hashQStr.toStdString());
422
423 // Abandon the wallet transaction over the walletModel
424 model->wallet().abandonTransaction(hash);
425 }
426
427 void TransactionView::bumpFee([[maybe_unused]] bool checked)
428 {
429 if(!transactionView || !transactionView->selectionModel())
430 return;
431 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
432
433 // get the hash from the TxHashRole (QVariant / QString)
434 uint256 hash;
435 QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
436 hash.SetHexDeprecated(hashQStr.toStdString());
437
438 // Bump tx fee over the walletModel
439 uint256 newHash;
440 if (model->bumpFee(hash, newHash)) {
441 // Update the table
442 transactionView->selectionModel()->clearSelection();
443 model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, true);
444
445 qApp->processEvents();
446 Q_EMIT bumpedFee(newHash);
447 }
448 }
449
450 void TransactionView::copyAddress()
451 {
452 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
453 }
454
455 void TransactionView::copyLabel()
456 {
457 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
458 }
459
460 void TransactionView::copyAmount()
461 {
462 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
463 }
464
465 void TransactionView::copyTxID()
466 {
467 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHashRole);
468 }
469
470 void TransactionView::copyTxHex()
471 {
472 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole);
473 }
474
475 void TransactionView::copyTxPlainText()
476 {
477 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole);
478 }
479
480 void TransactionView::editLabel()
481 {
482 if(!transactionView->selectionModel() ||!model)
483 return;
484 QModelIndexList selection = transactionView->selectionModel()->selectedRows();
485 if(!selection.isEmpty())
486 {
487 AddressTableModel *addressBook = model->getAddressTableModel();
488 if(!addressBook)
489 return;
490 QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
491 if(address.isEmpty())
492 {
493 // If this transaction has no associated address, exit
494 return;
495 }
496 // Is address in address book? Address book can miss address when a transaction is
497 // sent from outside the UI.
498 int idx = addressBook->lookupAddress(address);
499 if(idx != -1)
500 {
501 // Edit sending / receiving address
502 QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
503 // Determine type of address, launch appropriate editor dialog type
504 QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
505
506 auto dlg = new EditAddressDialog(
507 type == AddressTableModel::Receive
508 ? EditAddressDialog::EditReceivingAddress
509 : EditAddressDialog::EditSendingAddress, this);
510 dlg->setModel(addressBook);
511 dlg->loadRow(idx);
512 GUIUtil::ShowModalDialogAsynchronously(dlg);
513 }
514 else
515 {
516 // Add sending address
517 auto dlg = new EditAddressDialog(EditAddressDialog::NewSendingAddress,
518 this);
519 dlg->setModel(addressBook);
520 dlg->setAddress(address);
521 GUIUtil::ShowModalDialogAsynchronously(dlg);
522 }
523 }
524 }
525
526 void TransactionView::showDetails()
527 {
528 if(!transactionView->selectionModel())
529 return;
530 QModelIndexList selection = transactionView->selectionModel()->selectedRows();
531 if(!selection.isEmpty())
532 {
533 TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0));
534 dlg->setAttribute(Qt::WA_DeleteOnClose);
535 m_opened_dialogs.append(dlg);
536 connect(dlg, &QObject::destroyed, [this, dlg] {
537 m_opened_dialogs.removeOne(dlg);
538 });
539 dlg->show();
540 }
541 }
542
543 void TransactionView::openThirdPartyTxUrl(QString url)
544 {
545 if(!transactionView || !transactionView->selectionModel())
546 return;
547 QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
548 if(!selection.isEmpty())
549 QDesktopServices::openUrl(QUrl::fromUserInput(url.replace("%s", selection.at(0).data(TransactionTableModel::TxHashRole).toString())));
550 }
551
552 QWidget *TransactionView::createDateRangeWidget()
553 {
554 dateRangeWidget = new QFrame();
555 dateRangeWidget->setFrameStyle(static_cast<int>(QFrame::Panel) | static_cast<int>(QFrame::Raised));
556 dateRangeWidget->setContentsMargins(1,1,1,1);
557 QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
558 layout->setContentsMargins(0,0,0,0);
559 layout->addSpacing(23);
560 layout->addWidget(new QLabel(tr("Range:")));
561
562 dateFrom = new QDateTimeEdit(this);
563 dateFrom->setDisplayFormat("dd/MM/yy");
564 dateFrom->setCalendarPopup(true);
565 dateFrom->setMinimumWidth(100);
566 dateFrom->setDate(QDate::currentDate().addDays(-7));
567 layout->addWidget(dateFrom);
568 layout->addWidget(new QLabel(tr("to")));
569
570 dateTo = new QDateTimeEdit(this);
571 dateTo->setDisplayFormat("dd/MM/yy");
572 dateTo->setCalendarPopup(true);
573 dateTo->setMinimumWidth(100);
574 dateTo->setDate(QDate::currentDate());
575 layout->addWidget(dateTo);
576 layout->addStretch();
577
578 // Hide by default
579 dateRangeWidget->setVisible(false);
580
581 // Notify on change
582 connect(dateFrom, &QDateTimeEdit::dateChanged, this, &TransactionView::dateRangeChanged);
583 connect(dateTo, &QDateTimeEdit::dateChanged, this, &TransactionView::dateRangeChanged);
584
585 return dateRangeWidget;
586 }
587
588 void TransactionView::dateRangeChanged()
589 {
590 if(!transactionProxyModel)
591 return;
592 transactionProxyModel->setDateRange(
593 GUIUtil::StartOfDay(dateFrom->date()),
594 GUIUtil::StartOfDay(dateTo->date()).addDays(1));
595 }
596
597 void TransactionView::focusTransaction(const QModelIndex &idx)
598 {
599 if(!transactionProxyModel)
600 return;
601 QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
602 transactionView->scrollTo(targetIdx);
603 transactionView->setCurrentIndex(targetIdx);
604 transactionView->setFocus();
605 }
606
607 void TransactionView::focusTransaction(const uint256& txid)
608 {
609 if (!transactionProxyModel)
610 return;
611
612 const QModelIndexList results = this->model->getTransactionTableModel()->match(
613 this->model->getTransactionTableModel()->index(0,0),
614 TransactionTableModel::TxHashRole,
615 QString::fromStdString(txid.ToString()), -1);
616
617 transactionView->setFocus();
618 transactionView->selectionModel()->clearSelection();
619 for (const QModelIndex& index : results) {
620 const QModelIndex targetIndex = transactionProxyModel->mapFromSource(index);
621 transactionView->selectionModel()->select(
622 targetIndex,
623 QItemSelectionModel::Rows | QItemSelectionModel::Select);
624 // Called once per destination to ensure all results are in view, unless
625 // transactions are not ordered by (ascending or descending) date.
626 transactionView->scrollTo(targetIndex);
627 // scrollTo() does not scroll far enough the first time when transactions
628 // are ordered by ascending date.
629 if (index == results[0]) transactionView->scrollTo(targetIndex);
630 }
631 }
632
633 // We override the virtual resizeEvent of the QWidget to adjust tables column
634 // sizes as the tables width is proportional to the dialogs width.
635 void TransactionView::resizeEvent(QResizeEvent* event)
636 {
637 QWidget::resizeEvent(event);
638 columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
639 }
640
641 // Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text
642 bool TransactionView::eventFilter(QObject *obj, QEvent *event)
643 {
644 if (event->type() == QEvent::KeyPress)
645 {
646 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
647 if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier))
648 {
649 GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole);
650 return true;
651 }
652 }
653 if (event->type() == QEvent::EnabledChange) {
654 if (!isEnabled()) {
655 closeOpenedDialogs();
656 }
657 }
658 return QWidget::eventFilter(obj, event);
659 }
660
661 // show/hide column Watch-only
662 void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly)
663 {
664 watchOnlyWidget->setVisible(fHaveWatchOnly);
665 transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly);
666 }
667
668 void TransactionView::closeOpenedDialogs()
669 {
670 // close all dialogs opened from this view
671 for (QDialog* dlg : m_opened_dialogs) {
672 dlg->close();
673 }
674 m_opened_dialogs.clear();
675 }
676