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