Bitcoin ABC 0.26.3
P2P Digital Currency
Loading...
Searching...
No Matches
transactionview.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2016 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
6
7#include <node/ui_interface.h>
9#include <qt/bitcoinunits.h>
10#include <qt/csvmodelwriter.h>
12#include <qt/guiutil.h>
13#include <qt/optionsmodel.h>
14#include <qt/platformstyle.h>
19#include <qt/walletmodel.h>
20
21#include <QComboBox>
22#include <QDateTimeEdit>
23#include <QDesktopServices>
24#include <QDoubleValidator>
25#include <QHBoxLayout>
26#include <QHeaderView>
27#include <QLabel>
28#include <QLineEdit>
29#include <QMenu>
30#include <QPoint>
31#include <QScrollBar>
32#include <QTableView>
33#include <QTimer>
34#include <QUrl>
35#include <QVBoxLayout>
36
38 QWidget *parent)
39 : QWidget(parent) {
40 // Build filter row
41 setContentsMargins(0, 0, 0, 0);
42
44 hlayout->setContentsMargins(0, 0, 0, 0);
45
46 if (platformStyle->getUseExtraSpacing()) {
47 hlayout->setSpacing(5);
48 hlayout->addSpacing(26);
49 } else {
50 hlayout->setSpacing(0);
51 hlayout->addSpacing(23);
52 }
53
54 watchOnlyWidget = new QComboBox(this);
55 watchOnlyWidget->setFixedWidth(24);
57 watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"),
59 watchOnlyWidget->addItem(
60 platformStyle->SingleColorIcon(":/icons/eye_minus"), "",
62 hlayout->addWidget(watchOnlyWidget);
63
64 dateWidget = new QComboBox(this);
65 if (platformStyle->getUseExtraSpacing()) {
66 dateWidget->setFixedWidth(121);
67 } else {
68 dateWidget->setFixedWidth(120);
69 }
70 dateWidget->addItem(tr("All"), All);
71 dateWidget->addItem(tr("Today"), Today);
72 dateWidget->addItem(tr("This week"), ThisWeek);
73 dateWidget->addItem(tr("This month"), ThisMonth);
74 dateWidget->addItem(tr("Last month"), LastMonth);
75 dateWidget->addItem(tr("This year"), ThisYear);
76 dateWidget->addItem(tr("Range..."), Range);
77 hlayout->addWidget(dateWidget);
78
79 typeWidget = new QComboBox(this);
80 if (platformStyle->getUseExtraSpacing()) {
81 typeWidget->setFixedWidth(121);
82 } else {
83 typeWidget->setFixedWidth(120);
84 }
85
87 typeWidget->addItem(
88 tr("Received with"),
91 typeWidget->addItem(
92 tr("Sent to"),
95 typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(
99 typeWidget->addItem(tr("Other"),
101
102 hlayout->addWidget(typeWidget);
103
104 search_widget = new QLineEdit(this);
105 search_widget->setPlaceholderText(
106 tr("Enter address, transaction id, or label to search"));
107 hlayout->addWidget(search_widget);
108
109 amountWidget = new QLineEdit(this);
110 amountWidget->setPlaceholderText(tr("Min amount"));
111 if (platformStyle->getUseExtraSpacing()) {
112 amountWidget->setFixedWidth(97);
113 } else {
114 amountWidget->setFixedWidth(100);
115 }
117 QLocale amountLocale(QLocale::C);
118 amountLocale.setNumberOptions(QLocale::RejectGroupSeparator);
119 amountValidator->setLocale(amountLocale);
120 amountWidget->setValidator(amountValidator);
121 hlayout->addWidget(amountWidget);
122
123 // Delay before filtering transactions in ms
124 static const int input_filter_delay = 200;
125
126 QTimer *amount_typing_delay = new QTimer(this);
127 amount_typing_delay->setSingleShot(true);
129
130 QTimer *prefix_typing_delay = new QTimer(this);
131 prefix_typing_delay->setSingleShot(true);
133
134 QVBoxLayout *vlayout = new QVBoxLayout(this);
135 vlayout->setContentsMargins(0, 0, 0, 0);
136 vlayout->setSpacing(0);
137
138 QTableView *view = new QTableView(this);
139 vlayout->addLayout(hlayout);
140 vlayout->addWidget(createDateRangeWidget());
141 vlayout->addWidget(view);
142 vlayout->setSpacing(0);
143 int width = view->verticalScrollBar()->sizeHint().width();
144 // Cover scroll bar width with spacing
145 if (platformStyle->getUseExtraSpacing()) {
146 hlayout->addSpacing(width + 2);
147 } else {
148 hlayout->addSpacing(width);
149 }
150 // Always show scroll bar
151 view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
152 view->setTabKeyNavigation(false);
153 view->setContextMenuPolicy(Qt::CustomContextMenu);
154
155 view->installEventFilter(this);
156
158
159 // Actions
160 abandonAction = new QAction(tr("Abandon transaction"), this);
161 copyAddressAction = new QAction(tr("Copy address"), this);
162 copyLabelAction = new QAction(tr("Copy label"), this);
163 QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
164 QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
165 QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this);
167 new QAction(tr("Copy full transaction details"), this);
168 QAction *editLabelAction = new QAction(tr("Edit label"), this);
170 new QAction(tr("Show transaction details"), this);
171
172 contextMenu = new QMenu(this);
173 contextMenu->addAction(copyAddressAction);
174 contextMenu->addAction(copyLabelAction);
175 contextMenu->addAction(copyAmountAction);
176 contextMenu->addAction(copyTxIDAction);
177 contextMenu->addAction(copyTxHexAction);
178 contextMenu->addAction(copyTxPlainText);
179 contextMenu->addAction(showDetailsAction);
180 contextMenu->addSeparator();
181 contextMenu->addAction(abandonAction);
182 contextMenu->addAction(editLabelAction);
183
185 static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
188 static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
191 static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
193 connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay,
194 static_cast<void (QTimer::*)()>(&QTimer::start));
195 connect(amount_typing_delay, &QTimer::timeout, this,
197 connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay,
198 static_cast<void (QTimer::*)()>(&QTimer::start));
199 connect(prefix_typing_delay, &QTimer::timeout, this,
201
202 connect(view, &QTableView::doubleClicked, this,
204 connect(view, &QTableView::customContextMenuRequested, this,
206
207 connect(abandonAction, &QAction::triggered, this,
209 connect(copyAddressAction, &QAction::triggered, this,
211 connect(copyLabelAction, &QAction::triggered, this,
213 connect(copyAmountAction, &QAction::triggered, this,
215 connect(copyTxIDAction, &QAction::triggered, this,
217 connect(copyTxHexAction, &QAction::triggered, this,
219 connect(copyTxPlainText, &QAction::triggered, this,
221 connect(editLabelAction, &QAction::triggered, this,
223 connect(showDetailsAction, &QAction::triggered, this,
225 // Double-clicking on a transaction on the transaction history page shows
226 // details
229}
230
232 this->model = _model;
233 if (_model) {
235 transactionProxyModel->setSourceModel(
236 _model->getTransactionTableModel());
237 transactionProxyModel->setDynamicSortFilter(true);
238 transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
239 transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
240
241 transactionProxyModel->setSortRole(Qt::EditRole);
242
243 transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
245 transactionView->setAlternatingRowColors(true);
246 transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
247 transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
248 transactionView->horizontalHeader()->setSortIndicator(
249 TransactionTableModel::Date, Qt::DescendingOrder);
250 transactionView->setSortingEnabled(true);
251 transactionView->verticalHeader()->hide();
252
263
266 this);
267
268 if (_model->getOptionsModel()) {
269 // Add third party transaction URLs to context menu
271 _model->getOptionsModel()->getThirdPartyTxUrls(), "|");
272 for (int i = 0; i < listUrls.size(); ++i) {
273 QString url = listUrls[i].trimmed();
274 QString host = QUrl(url, QUrl::StrictMode).host();
275 if (!host.isEmpty()) {
276 // use host as menu item label
277 QAction *thirdPartyTxUrlAction = new QAction(host, this);
278 if (i == 0) {
279 contextMenu->addSeparator();
280 }
282 connect(thirdPartyTxUrlAction, &QAction::triggered,
283 [this, url] { openThirdPartyTxUrl(url); });
284 }
285 }
286 }
287
288 // show/hide column Watch-only
289 updateWatchOnlyColumn(_model->wallet().haveWatchOnly());
290
291 // Watch-only signal
294 }
295}
296
299 return;
300 }
301
302 const QDate currentDate = QDate::currentDate();
303
304#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
305 const QDateTime startofDay = currentDate.startOfDay();
306#else
308#endif
309 const QDateTime startOfWeek =
310 startofDay.addDays(-(currentDate.dayOfWeek() - 1));
311 const QDateTime startOfMonth = startofDay.addDays(-(currentDate.day() - 1));
312 const QDateTime startOfYear =
313 startofDay.addDays(-(currentDate.dayOfYear() - 1));
314
315 dateRangeWidget->setVisible(false);
316 switch (dateWidget->itemData(idx).toInt()) {
317 case All:
321 break;
322 case Today:
325 break;
326 case ThisWeek: {
327 // Find last Monday
328
331
332 } break;
333 case ThisMonth:
334
337 break;
338 case LastMonth:
341 break;
342 case ThisYear:
343
346 break;
347 case Range:
348 dateRangeWidget->setVisible(true);
350 break;
351 }
352}
353
356 return;
357 }
358
359 transactionProxyModel->setTypeFilter(typeWidget->itemData(idx).toInt());
360}
361
364 return;
365 }
366
369 watchOnlyWidget->itemData(idx).toInt()));
370}
371
379
393
395 if (!model || !model->getOptionsModel()) {
396 return;
397 }
398
399 // CSV is currently the only supported format
401 this, tr("Export Transaction History"), QString(),
402 tr("Comma separated file (*.csv)"), nullptr);
403
404 if (filename.isNull()) {
405 return;
406 }
407
408 CSVModelWriter writer(filename);
409
410 // name, column, role
412 writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
413 if (model->wallet().haveWatchOnly()) {
414 writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly);
415 }
416 writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
417 writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
418 writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
419 writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
423 writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
424
425 if (!writer.write()) {
426 Q_EMIT message(tr("Exporting Failed"),
427 tr("There was an error trying to save the transaction "
428 "history to %1.")
429 .arg(filename),
431 } else {
433 tr("Exporting Successful"),
434 tr("The transaction history was successfully saved to %1.")
435 .arg(filename),
437 }
438}
439
441 QModelIndex index = transactionView->indexAt(point);
443 transactionView->selectionModel()->selectedRows(0);
444 if (selection.empty()) {
445 return;
446 }
447
448 // check if transaction can be abandoned, disable context menu action in
449 // case it doesn't
450 TxId txid;
451 txid.SetHex(selection.at(0)
453 .toString()
454 .toStdString());
460
461 if (index.isValid()) {
463 transactionView->viewport()->mapToGlobal(point));
464 }
465}
466
468 if (!transactionView || !transactionView->selectionModel()) {
469 return;
470 }
471
473 transactionView->selectionModel()->selectedRows(0);
474
475 // get the hash from the TxHashRole (QVariant / QString)
477 selection.at(0).data(TransactionTableModel::TxHashRole).toString();
478
479 TxId txid;
480 txid.SetHex(hashQStr.toStdString());
481
482 // Abandon the wallet transaction over the walletModel
484
485 // Update the table
487 false);
488}
489
494
499
504
508
513
518
520 if (!transactionView->selectionModel() || !model) {
521 return;
522 }
523
525 transactionView->selectionModel()->selectedRows();
526 if (!selection.isEmpty()) {
528 if (!addressBook) {
529 return;
530 }
531
532 QString address =
534 if (address.isEmpty()) {
535 // If this transaction has no associated address, exit
536 return;
537 }
538 // Is address in address book? Address book can miss address when a
539 // transaction is sent from outside the UI.
540 int idx = addressBook->lookupAddress(address);
541 if (idx != -1) {
542 // Edit sending / receiving address
543 QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
544 // Determine type of address, launch appropriate editor dialog type
545 QString type =
546 modelIdx.data(AddressTableModel::TypeRole).toString();
547
551 this);
553 dlg.loadRow(idx);
554 dlg.exec();
555 } else {
556 // Add sending address
558 dlg.setModel(addressBook);
559 dlg.setAddress(address);
560 dlg.exec();
561 }
562 }
563}
564
566 if (!transactionView->selectionModel()) {
567 return;
568 }
569
571 transactionView->selectionModel()->selectedRows();
572 if (!selection.isEmpty()) {
574 dlg->setAttribute(Qt::WA_DeleteOnClose);
575 dlg->show();
576 }
577}
578
580 if (!transactionView || !transactionView->selectionModel()) {
581 return;
582 }
583
585 transactionView->selectionModel()->selectedRows(0);
586 if (!selection.isEmpty()) {
587 QDesktopServices::openUrl(QUrl::fromUserInput(
588 url.replace("%s", selection.at(0)
590 .toString())));
591 }
592}
593
595 dateRangeWidget = new QFrame();
596 dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
597 dateRangeWidget->setContentsMargins(1, 1, 1, 1);
599 layout->setContentsMargins(0, 0, 0, 0);
600 layout->addSpacing(23);
601 layout->addWidget(new QLabel(tr("Range:")));
602
603 dateFrom = new QDateTimeEdit(this);
604 dateFrom->setDisplayFormat("dd/MM/yy");
605 dateFrom->setCalendarPopup(true);
606 dateFrom->setMinimumWidth(100);
607 dateFrom->setDate(QDate::currentDate().addDays(-7));
608 layout->addWidget(dateFrom);
609 layout->addWidget(new QLabel(tr("to")));
610
611 dateTo = new QDateTimeEdit(this);
612 dateTo->setDisplayFormat("dd/MM/yy");
613 dateTo->setCalendarPopup(true);
614 dateTo->setMinimumWidth(100);
615 dateTo->setDate(QDate::currentDate());
616 layout->addWidget(dateTo);
617 layout->addStretch();
618
619 // Hide by default
620 dateRangeWidget->setVisible(false);
621
622 // Notify on change
623 connect(dateFrom, &QDateTimeEdit::dateChanged, this,
625 connect(dateTo, &QDateTimeEdit::dateChanged, this,
627
628 return dateRangeWidget;
629}
630
633 return;
634 }
635
636#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
637 const QDateTime rangeFrom = dateFrom->date().startOfDay();
638 const QDateTime rangeTo = dateTo->date().endOfDay();
639#else
640 const QDateTime rangeFrom = QDateTime(dateFrom->date());
641 const QDateTime rangeTo = QDateTime(dateTo->date()).addDays(1);
642#endif
643
645}
646
649 return;
650 }
651
652 QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
653 transactionView->scrollTo(targetIdx);
654 transactionView->setCurrentIndex(targetIdx);
655 transactionView->setFocus();
656}
657
660 return;
661 }
662
664 this->model->getTransactionTableModel()->match(
665 this->model->getTransactionTableModel()->index(0, 0),
667 QString::fromStdString(txid.ToString()), -1);
668
669 transactionView->setFocus();
670 transactionView->selectionModel()->clearSelection();
671 for (const QModelIndex &index : results) {
673 transactionProxyModel->mapFromSource(index);
674 transactionView->selectionModel()->select(
676 QItemSelectionModel::Rows | QItemSelectionModel::Select);
677 // Called once per destination to ensure all results are in view, unless
678 // transactions are not ordered by (ascending or descending) date.
679 transactionView->scrollTo(targetIndex);
680 // scrollTo() does not scroll far enough the first time when
681 // transactions are ordered by ascending date.
682 if (index == results[0]) {
683 transactionView->scrollTo(targetIndex);
684 }
685 }
686}
687
688// We override the virtual resizeEvent of the QWidget to adjust tables column
689// sizes as the tables width is proportional to the dialogs width.
694
695// Need to override default Ctrl+C action for amount as default behaviour is
696// just to copy DisplayRole text
698 if (event->type() == QEvent::KeyPress) {
699 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
700 if (ke->key() == Qt::Key_C &&
701 ke->modifiers().testFlag(Qt::ControlModifier)) {
704 return true;
705 }
706 }
707 return QWidget::eventFilter(obj, event);
708}
709
710// show/hide column Watch-only
711void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) {
712 watchOnlyWidget->setVisible(fHaveWatchOnly);
714 !fHaveWatchOnly);
715}
Qt model of the address book in the core.
@ TypeRole
Type of address (Send or Receive)
QVariant data(const QModelIndex &index, int role) const override
static const QString Receive
Specifies receive address.
static bool parse(int unit, const QString &value, Amount *val_out)
Parse string to coin amount.
static QString getAmountColumnTitle(int unit)
Gets title for amount column including current display unit if optionsModel reference available *‍/.
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Export a Qt table model to a CSV file.
Dialog for editing an address and associated information.
void setModel(AddressTableModel *model)
Makes a QTableView last column feel as if it was being resized from its left border.
Definition guiutil.h:227
int getDisplayUnit() const
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getUseExtraSpacing() const
Dialog showing transaction details.
Filter the transaction list according to pre-specified rules.
static const QDateTime MAX_DATE
Last date that can be represented (far in the future).
void setWatchOnlyFilter(WatchOnlyFilter filter)
static const quint32 ALL_TYPES
Type filter bit field (all types).
static quint32 TYPE(int type)
static const QDateTime MIN_DATE
Earliest date that can be represented (far in the past).
void setSearchString(const QString &)
void setMinAmount(const Amount minimum)
void setDateRange(const QDateTime &from, const QDateTime &to)
void setTypeFilter(quint32 modes)
@ TxPlainTextRole
Whole transaction as plain text.
@ LabelRole
Label of address related to transaction.
@ DateRole
Date and time this transaction was created.
@ TxHashRole
Transaction hash.
@ TxHexRole
Transaction data, hex-encoded.
@ TxIDRole
Unique identifier.
@ AddressRole
Address of transaction.
@ ConfirmedRole
Is transaction confirmed?
@ FormattedAmountRole
Formatted amount, without brackets when unconfirmed.
void updateTransaction(const QString &hash, int status, bool showTransaction)
New transaction, or transaction changed status.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
WalletModel * model
void chooseWatchonly(int idx)
TransactionView(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
QComboBox * typeWidget
QLineEdit * search_widget
bool eventFilter(QObject *obj, QEvent *event) override
QDateTimeEdit * dateFrom
QWidget * createDateRangeWidget()
void setModel(WalletModel *model)
GUIUtil::TableViewLastColumnResizingFixer * columnResizingFixer
void updateWatchOnlyColumn(bool fHaveWatchOnly)
void message(const QString &title, const QString &message, unsigned int style)
Fired when a message should be reported to the user.
void chooseType(int idx)
virtual void resizeEvent(QResizeEvent *event) override
QComboBox * watchOnlyWidget
QAction * abandonAction
QFrame * dateRangeWidget
QDateTimeEdit * dateTo
TransactionFilterProxy * transactionProxyModel
QTableView * transactionView
void focusTransaction(const QModelIndex &)
void chooseDate(int idx)
void contextualMenu(const QPoint &)
QComboBox * dateWidget
QAction * copyAddressAction
void openThirdPartyTxUrl(QString url)
QAction * copyLabelAction
void doubleClicked(const QModelIndex &)
QLineEdit * amountWidget
Interface to Bitcoin wallet from Qt view code.
Definition walletmodel.h:47
void notifyWatchonlyChanged(bool fHaveWatchonly)
AddressTableModel * getAddressTableModel()
OptionsModel * getOptionsModel()
interfaces::Wallet & wallet() const
TransactionTableModel * getTransactionTableModel()
void SetHex(const char *psz)
Definition uint256.cpp:24
std::string ToString() const
Definition uint256.h:80
virtual bool transactionCanBeAbandoned(const TxId &txid)=0
Return whether transaction can be abandoned.
virtual bool abandonTransaction(const TxId &txid)=0
Abandon transaction.
virtual bool haveWatchOnly()=0
Return whether wallet has watch only keys.
256-bit opaque blob.
Definition uint256.h:129
void PopupMenu(QMenu *menu, const QPoint &point, QAction *at_action)
Call QMenu::popup() only on supported QT_QPA_PLATFORM.
Definition guiutil.cpp:986
void copyEntryData(const QAbstractItemView *view, int column, int role)
Copy a field of the currently selected entry of a view to the clipboard.
Definition guiutil.cpp:263
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition guiutil.cpp:294
QStringList splitSkipEmptyParts(const QString &s, const QString &separator)
Definition guiutil.cpp:448
bool hasEntryData(const QAbstractItemView *view, int column, int role)
Returns true if the specified field of the currently selected view entry is not empty.
Definition guiutil.cpp:282
T GetRand(T nMax=std::numeric_limits< T >::max()) noexcept
Generate a uniform random integer of type T in the range [0..nMax) nMax defaults to std::numeric_limi...
Definition random.h:85
const char * url
static constexpr Amount zero() noexcept
Definition amount.h:32
A TxId is the identifier of a transaction.
Definition txid.h:14
@ CT_UPDATED