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