transactionview.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // ECOin - Copyright (c) - 2014/2024 - GPLv3 - epsylon@riseup.net (https://03c8.net)
  2. #include "transactionview.h"
  3. #include "transactionfilterproxy.h"
  4. #include "transactionrecord.h"
  5. #include "walletmodel.h"
  6. #include "addresstablemodel.h"
  7. #include "transactiontablemodel.h"
  8. #include "ecoinunits.h"
  9. #include "csvmodelwriter.h"
  10. #include "transactiondescdialog.h"
  11. #include "editaddressdialog.h"
  12. #include "optionsmodel.h"
  13. #include "guiutil.h"
  14. #include <QScrollBar>
  15. #include <QComboBox>
  16. #include <QDoubleValidator>
  17. #include <QHBoxLayout>
  18. #include <QVBoxLayout>
  19. #include <QLineEdit>
  20. #include <QTableView>
  21. #include <QHeaderView>
  22. #include <QPushButton>
  23. #include <QMessageBox>
  24. #include <QPoint>
  25. #include <QMenu>
  26. #include <QApplication>
  27. #include <QClipboard>
  28. #include <QLabel>
  29. #include <QDateTimeEdit>
  30. #include <QString>
  31. #include <QUrl>
  32. TransactionView::TransactionView(QWidget *parent) :
  33. QWidget(parent), model(0), transactionProxyModel(0),
  34. transactionView(0)
  35. {
  36. // Build filter row
  37. setContentsMargins(0,0,0,0);
  38. QHBoxLayout *hlayout = new QHBoxLayout();
  39. hlayout->setContentsMargins(0,0,0,0);
  40. #ifdef Q_OS_MAC
  41. hlayout->setSpacing(5);
  42. hlayout->addSpacing(26);
  43. #else
  44. hlayout->setSpacing(0);
  45. hlayout->addSpacing(23);
  46. #endif
  47. dateWidget = new QComboBox(this);
  48. #ifdef Q_OS_MAC
  49. dateWidget->setFixedWidth(121);
  50. #else
  51. dateWidget->setFixedWidth(120);
  52. #endif
  53. dateWidget->addItem(tr("All"), All);
  54. dateWidget->addItem(tr("Today"), Today);
  55. dateWidget->addItem(tr("This week"), ThisWeek);
  56. dateWidget->addItem(tr("This month"), ThisMonth);
  57. dateWidget->addItem(tr("Last month"), LastMonth);
  58. dateWidget->addItem(tr("This year"), ThisYear);
  59. dateWidget->addItem(tr("Range..."), Range);
  60. hlayout->addWidget(dateWidget);
  61. typeWidget = new QComboBox(this);
  62. #ifdef Q_OS_MAC
  63. typeWidget->setFixedWidth(121);
  64. #else
  65. typeWidget->setFixedWidth(120);
  66. #endif
  67. typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
  68. typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
  69. TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther));
  70. typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
  71. TransactionFilterProxy::TYPE(TransactionRecord::SendToOther));
  72. typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
  73. typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
  74. typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
  75. hlayout->addWidget(typeWidget);
  76. addressWidget = new QLineEdit(this);
  77. #if QT_VERSION >= 0x040700
  78. /* Do not move this to the XML file, Qt before 4.7 will choke on it */
  79. addressWidget->setPlaceholderText(tr("Enter address or label to search"));
  80. #endif
  81. hlayout->addWidget(addressWidget);
  82. amountWidget = new QLineEdit(this);
  83. #if QT_VERSION >= 0x040700
  84. /* Do not move this to the XML file, Qt before 4.7 will choke on it */
  85. amountWidget->setPlaceholderText(tr("Min amount"));
  86. #endif
  87. #ifdef Q_OS_MAC
  88. amountWidget->setFixedWidth(97);
  89. #else
  90. amountWidget->setFixedWidth(100);
  91. #endif
  92. amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
  93. hlayout->addWidget(amountWidget);
  94. QVBoxLayout *vlayout = new QVBoxLayout(this);
  95. vlayout->setContentsMargins(0,0,0,0);
  96. vlayout->setSpacing(0);
  97. QTableView *view = new QTableView(this);
  98. vlayout->addLayout(hlayout);
  99. vlayout->addWidget(createDateRangeWidget());
  100. vlayout->addWidget(view);
  101. vlayout->setSpacing(0);
  102. int width = view->verticalScrollBar()->sizeHint().width();
  103. // Cover scroll bar width with spacing
  104. #ifdef Q_OS_MAC
  105. hlayout->addSpacing(width+2);
  106. #else
  107. hlayout->addSpacing(width);
  108. #endif
  109. // Always show scroll bar
  110. view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  111. view->setTabKeyNavigation(false);
  112. view->setContextMenuPolicy(Qt::CustomContextMenu);
  113. transactionView = view;
  114. // Actions
  115. QAction *copyAddressAction = new QAction(tr("Copy address"), this);
  116. QAction *copyLabelAction = new QAction(tr("Copy label"), this);
  117. QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
  118. QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
  119. QAction *editLabelAction = new QAction(tr("Edit label"), this);
  120. QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
  121. contextMenu = new QMenu();
  122. contextMenu->addAction(copyAddressAction);
  123. contextMenu->addAction(copyLabelAction);
  124. contextMenu->addAction(copyAmountAction);
  125. contextMenu->addAction(copyTxIDAction);
  126. contextMenu->addAction(editLabelAction);
  127. contextMenu->addAction(showDetailsAction);
  128. // Connect actions
  129. connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
  130. connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
  131. connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
  132. connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));
  133. connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
  134. connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
  135. connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
  136. connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
  137. connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
  138. connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID()));
  139. connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
  140. connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
  141. }
  142. void TransactionView::setModel(WalletModel *model)
  143. {
  144. this->model = model;
  145. if(model)
  146. {
  147. transactionProxyModel = new TransactionFilterProxy(this);
  148. transactionProxyModel->setSourceModel(model->getTransactionTableModel());
  149. transactionProxyModel->setDynamicSortFilter(true);
  150. transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
  151. transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
  152. transactionProxyModel->setSortRole(Qt::EditRole);
  153. transactionView->setModel(transactionProxyModel);
  154. transactionView->setAlternatingRowColors(true);
  155. transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
  156. transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
  157. transactionView->setSortingEnabled(true);
  158. transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
  159. transactionView->verticalHeader()->hide();
  160. transactionView->horizontalHeader()->resizeSection(
  161. TransactionTableModel::Status, 23);
  162. transactionView->horizontalHeader()->resizeSection(
  163. TransactionTableModel::Date, 120);
  164. transactionView->horizontalHeader()->resizeSection(
  165. TransactionTableModel::Type, 120);
  166. transactionView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
  167. transactionView->horizontalHeader()->resizeSection(
  168. TransactionTableModel::Amount, 100);
  169. }
  170. }
  171. void TransactionView::chooseDate(int idx)
  172. {
  173. if(!transactionProxyModel)
  174. return;
  175. QDate current = QDate::currentDate();
  176. dateRangeWidget->setVisible(false);
  177. switch(dateWidget->itemData(idx).toInt())
  178. {
  179. case All:
  180. transactionProxyModel->setDateRange(
  181. TransactionFilterProxy::MIN_DATE,
  182. TransactionFilterProxy::MAX_DATE);
  183. break;
  184. case Today:
  185. transactionProxyModel->setDateRange(
  186. QDateTime(current),
  187. TransactionFilterProxy::MAX_DATE);
  188. break;
  189. case ThisWeek: {
  190. // Find last Monday
  191. QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
  192. transactionProxyModel->setDateRange(
  193. QDateTime(startOfWeek),
  194. TransactionFilterProxy::MAX_DATE);
  195. } break;
  196. case ThisMonth:
  197. transactionProxyModel->setDateRange(
  198. QDateTime(QDate(current.year(), current.month(), 1)),
  199. TransactionFilterProxy::MAX_DATE);
  200. break;
  201. case LastMonth:
  202. transactionProxyModel->setDateRange(
  203. QDateTime(QDate(current.year(), current.month()-1, 1)),
  204. QDateTime(QDate(current.year(), current.month(), 1)));
  205. break;
  206. case ThisYear:
  207. transactionProxyModel->setDateRange(
  208. QDateTime(QDate(current.year(), 1, 1)),
  209. TransactionFilterProxy::MAX_DATE);
  210. break;
  211. case Range:
  212. dateRangeWidget->setVisible(true);
  213. dateRangeChanged();
  214. break;
  215. }
  216. }
  217. void TransactionView::chooseType(int idx)
  218. {
  219. if(!transactionProxyModel)
  220. return;
  221. transactionProxyModel->setTypeFilter(
  222. typeWidget->itemData(idx).toInt());
  223. }
  224. void TransactionView::changedPrefix(const QString &prefix)
  225. {
  226. if(!transactionProxyModel)
  227. return;
  228. transactionProxyModel->setAddressPrefix(prefix);
  229. }
  230. void TransactionView::changedAmount(const QString &amount)
  231. {
  232. if(!transactionProxyModel)
  233. return;
  234. qint64 amount_parsed = 0;
  235. if(EcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
  236. {
  237. transactionProxyModel->setMinAmount(amount_parsed);
  238. }
  239. else
  240. {
  241. transactionProxyModel->setMinAmount(0);
  242. }
  243. }
  244. void TransactionView::exportClicked()
  245. {
  246. // CSV is currently the only supported format
  247. QString filename = GUIUtil::getSaveFileName(
  248. this,
  249. tr("Export Transaction Data"), QString(),
  250. tr("Comma separated file (*.csv)"));
  251. if (filename.isNull()) return;
  252. CSVModelWriter writer(filename);
  253. // name, column, role
  254. writer.setModel(transactionProxyModel);
  255. writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
  256. writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
  257. writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
  258. writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
  259. writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
  260. writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
  261. writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
  262. if(!writer.write())
  263. {
  264. QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
  265. QMessageBox::Abort, QMessageBox::Abort);
  266. }
  267. }
  268. void TransactionView::contextualMenu(const QPoint &point)
  269. {
  270. QModelIndex index = transactionView->indexAt(point);
  271. if(index.isValid())
  272. {
  273. contextMenu->exec(QCursor::pos());
  274. }
  275. }
  276. void TransactionView::copyAddress()
  277. {
  278. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
  279. }
  280. void TransactionView::copyLabel()
  281. {
  282. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
  283. }
  284. void TransactionView::copyAmount()
  285. {
  286. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
  287. }
  288. void TransactionView::copyTxID()
  289. {
  290. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole);
  291. }
  292. void TransactionView::editLabel()
  293. {
  294. if(!transactionView->selectionModel() ||!model)
  295. return;
  296. QModelIndexList selection = transactionView->selectionModel()->selectedRows();
  297. if(!selection.isEmpty())
  298. {
  299. AddressTableModel *addressBook = model->getAddressTableModel();
  300. if(!addressBook)
  301. return;
  302. QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
  303. if(address.isEmpty())
  304. {
  305. // If this transaction has no associated address, exit
  306. return;
  307. }
  308. // Is address in address book? Address book can miss address when a transaction is
  309. // sent from outside the UI.
  310. int idx = addressBook->lookupAddress(address);
  311. if(idx != -1)
  312. {
  313. // Edit sending / receiving address
  314. QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
  315. // Determine type of address, launch appropriate editor dialog type
  316. QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
  317. EditAddressDialog dlg(type==AddressTableModel::Receive
  318. ? EditAddressDialog::EditReceivingAddress
  319. : EditAddressDialog::EditSendingAddress,
  320. this);
  321. dlg.setModel(addressBook);
  322. dlg.loadRow(idx);
  323. dlg.exec();
  324. }
  325. else
  326. {
  327. // Add sending address
  328. EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
  329. this);
  330. dlg.setModel(addressBook);
  331. dlg.setAddress(address);
  332. dlg.exec();
  333. }
  334. }
  335. }
  336. void TransactionView::showDetails()
  337. {
  338. if(!transactionView->selectionModel())
  339. return;
  340. QModelIndexList selection = transactionView->selectionModel()->selectedRows();
  341. if(!selection.isEmpty())
  342. {
  343. TransactionDescDialog dlg(selection.at(0));
  344. dlg.exec();
  345. }
  346. }
  347. QWidget *TransactionView::createDateRangeWidget()
  348. {
  349. dateRangeWidget = new QFrame();
  350. dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
  351. dateRangeWidget->setContentsMargins(1,1,1,1);
  352. QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
  353. layout->setContentsMargins(0,0,0,0);
  354. layout->addSpacing(23);
  355. layout->addWidget(new QLabel(tr("Range:")));
  356. dateFrom = new QDateTimeEdit(this);
  357. dateFrom->setDisplayFormat("dd/MM/yy");
  358. dateFrom->setCalendarPopup(true);
  359. dateFrom->setMinimumWidth(100);
  360. dateFrom->setDate(QDate::currentDate().addDays(-7));
  361. layout->addWidget(dateFrom);
  362. layout->addWidget(new QLabel(tr("to")));
  363. dateTo = new QDateTimeEdit(this);
  364. dateTo->setDisplayFormat("dd/MM/yy");
  365. dateTo->setCalendarPopup(true);
  366. dateTo->setMinimumWidth(100);
  367. dateTo->setDate(QDate::currentDate());
  368. layout->addWidget(dateTo);
  369. layout->addStretch();
  370. // Hide by default
  371. dateRangeWidget->setVisible(false);
  372. // Notify on change
  373. connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
  374. connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
  375. return dateRangeWidget;
  376. }
  377. void TransactionView::dateRangeChanged()
  378. {
  379. if(!transactionProxyModel)
  380. return;
  381. transactionProxyModel->setDateRange(
  382. QDateTime(dateFrom->date()),
  383. QDateTime(dateTo->date()).addDays(1));
  384. }
  385. void TransactionView::focusTransaction(const QModelIndex &idx)
  386. {
  387. if(!transactionProxyModel)
  388. return;
  389. QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
  390. transactionView->scrollTo(targetIdx);
  391. transactionView->setCurrentIndex(targetIdx);
  392. transactionView->setFocus();
  393. }