You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transactionview.cpp 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. // Copyright (c) 2011-2013 The Bitcoin developers
  2. // Distributed under the MIT/X11 software license, see the accompanying
  3. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4. #include "transactionview.h"
  5. #include "addresstablemodel.h"
  6. #include "bitcoinunits.h"
  7. #include "csvmodelwriter.h"
  8. #include "editaddressdialog.h"
  9. #include "guiutil.h"
  10. #include "optionsmodel.h"
  11. #include "transactiondescdialog.h"
  12. #include "transactionfilterproxy.h"
  13. #include "transactionrecord.h"
  14. #include "transactiontablemodel.h"
  15. #include "walletmodel.h"
  16. #include "ui_interface.h"
  17. #include <QComboBox>
  18. #include <QDateTimeEdit>
  19. #include <QDesktopServices>
  20. #include <QDoubleValidator>
  21. #include <QHBoxLayout>
  22. #include <QHeaderView>
  23. #include <QLabel>
  24. #include <QLineEdit>
  25. #include <QMenu>
  26. #include <QPoint>
  27. #include <QScrollBar>
  28. #include <QSignalMapper>
  29. #include <QTableView>
  30. #include <QUrl>
  31. #include <QVBoxLayout>
  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. addressWidget->setPlaceholderText(tr("Enter address or label to search"));
  79. #endif
  80. hlayout->addWidget(addressWidget);
  81. amountWidget = new QLineEdit(this);
  82. #if QT_VERSION >= 0x040700
  83. amountWidget->setPlaceholderText(tr("Min amount"));
  84. #endif
  85. #ifdef Q_OS_MAC
  86. amountWidget->setFixedWidth(97);
  87. #else
  88. amountWidget->setFixedWidth(100);
  89. #endif
  90. amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
  91. hlayout->addWidget(amountWidget);
  92. QVBoxLayout *vlayout = new QVBoxLayout(this);
  93. vlayout->setContentsMargins(0,0,0,0);
  94. vlayout->setSpacing(0);
  95. QTableView *view = new QTableView(this);
  96. vlayout->addLayout(hlayout);
  97. vlayout->addWidget(createDateRangeWidget());
  98. vlayout->addWidget(view);
  99. vlayout->setSpacing(0);
  100. int width = view->verticalScrollBar()->sizeHint().width();
  101. // Cover scroll bar width with spacing
  102. #ifdef Q_OS_MAC
  103. hlayout->addSpacing(width+2);
  104. #else
  105. hlayout->addSpacing(width);
  106. #endif
  107. // Always show scroll bar
  108. view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  109. view->setTabKeyNavigation(false);
  110. view->setContextMenuPolicy(Qt::CustomContextMenu);
  111. transactionView = view;
  112. // Actions
  113. QAction *copyAddressAction = new QAction(tr("Copy address"), this);
  114. QAction *copyLabelAction = new QAction(tr("Copy label"), this);
  115. QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
  116. QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
  117. QAction *editLabelAction = new QAction(tr("Edit label"), this);
  118. QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
  119. contextMenu = new QMenu();
  120. contextMenu->addAction(copyAddressAction);
  121. contextMenu->addAction(copyLabelAction);
  122. contextMenu->addAction(copyAmountAction);
  123. contextMenu->addAction(copyTxIDAction);
  124. contextMenu->addAction(editLabelAction);
  125. contextMenu->addAction(showDetailsAction);
  126. mapperThirdPartyTxUrls = new QSignalMapper(this);
  127. // Connect actions
  128. connect(mapperThirdPartyTxUrls, SIGNAL(mapped(QString)), this, SLOT(openThirdPartyTxUrl(QString)));
  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->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  154. transactionView->setModel(transactionProxyModel);
  155. transactionView->setAlternatingRowColors(true);
  156. transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
  157. transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
  158. transactionView->setSortingEnabled(true);
  159. transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
  160. transactionView->verticalHeader()->hide();
  161. transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH);
  162. transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH);
  163. transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH);
  164. transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
  165. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH);
  166. if (model->getOptionsModel())
  167. {
  168. // Add third party transaction URLs to context menu
  169. QStringList listUrls = model->getOptionsModel()->getThirdPartyTxUrls().split("|", QString::SkipEmptyParts);
  170. for (int i = 0; i < listUrls.size(); ++i)
  171. {
  172. QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host();
  173. if (!host.isEmpty())
  174. {
  175. QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label
  176. if (i == 0)
  177. contextMenu->addSeparator();
  178. contextMenu->addAction(thirdPartyTxUrlAction);
  179. connect(thirdPartyTxUrlAction, SIGNAL(triggered()), mapperThirdPartyTxUrls, SLOT(map()));
  180. mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed());
  181. }
  182. }
  183. }
  184. }
  185. }
  186. void TransactionView::chooseDate(int idx)
  187. {
  188. if(!transactionProxyModel)
  189. return;
  190. QDate current = QDate::currentDate();
  191. dateRangeWidget->setVisible(false);
  192. switch(dateWidget->itemData(idx).toInt())
  193. {
  194. case All:
  195. transactionProxyModel->setDateRange(
  196. TransactionFilterProxy::MIN_DATE,
  197. TransactionFilterProxy::MAX_DATE);
  198. break;
  199. case Today:
  200. transactionProxyModel->setDateRange(
  201. QDateTime(current),
  202. TransactionFilterProxy::MAX_DATE);
  203. break;
  204. case ThisWeek: {
  205. // Find last Monday
  206. QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
  207. transactionProxyModel->setDateRange(
  208. QDateTime(startOfWeek),
  209. TransactionFilterProxy::MAX_DATE);
  210. } break;
  211. case ThisMonth:
  212. transactionProxyModel->setDateRange(
  213. QDateTime(QDate(current.year(), current.month(), 1)),
  214. TransactionFilterProxy::MAX_DATE);
  215. break;
  216. case LastMonth:
  217. transactionProxyModel->setDateRange(
  218. QDateTime(QDate(current.year(), current.month()-1, 1)),
  219. QDateTime(QDate(current.year(), current.month(), 1)));
  220. break;
  221. case ThisYear:
  222. transactionProxyModel->setDateRange(
  223. QDateTime(QDate(current.year(), 1, 1)),
  224. TransactionFilterProxy::MAX_DATE);
  225. break;
  226. case Range:
  227. dateRangeWidget->setVisible(true);
  228. dateRangeChanged();
  229. break;
  230. }
  231. }
  232. void TransactionView::chooseType(int idx)
  233. {
  234. if(!transactionProxyModel)
  235. return;
  236. transactionProxyModel->setTypeFilter(
  237. typeWidget->itemData(idx).toInt());
  238. }
  239. void TransactionView::changedPrefix(const QString &prefix)
  240. {
  241. if(!transactionProxyModel)
  242. return;
  243. transactionProxyModel->setAddressPrefix(prefix);
  244. }
  245. void TransactionView::changedAmount(const QString &amount)
  246. {
  247. if(!transactionProxyModel)
  248. return;
  249. qint64 amount_parsed = 0;
  250. if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
  251. {
  252. transactionProxyModel->setMinAmount(amount_parsed);
  253. }
  254. else
  255. {
  256. transactionProxyModel->setMinAmount(0);
  257. }
  258. }
  259. void TransactionView::exportClicked()
  260. {
  261. // CSV is currently the only supported format
  262. QString filename = GUIUtil::getSaveFileName(this,
  263. tr("Export Transaction History"), QString(),
  264. tr("Comma separated file (*.csv)"), NULL);
  265. if (filename.isNull())
  266. return;
  267. CSVModelWriter writer(filename);
  268. // name, column, role
  269. writer.setModel(transactionProxyModel);
  270. writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
  271. writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
  272. writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
  273. writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
  274. writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
  275. writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
  276. writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
  277. if(!writer.write()) {
  278. emit message(tr("Exporting Failed"), tr("There was an error trying to save the transaction history to %1.").arg(filename),
  279. CClientUIInterface::MSG_ERROR);
  280. }
  281. else {
  282. emit message(tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.").arg(filename),
  283. CClientUIInterface::MSG_INFORMATION);
  284. }
  285. }
  286. void TransactionView::contextualMenu(const QPoint &point)
  287. {
  288. QModelIndex index = transactionView->indexAt(point);
  289. if(index.isValid())
  290. {
  291. contextMenu->exec(QCursor::pos());
  292. }
  293. }
  294. void TransactionView::copyAddress()
  295. {
  296. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
  297. }
  298. void TransactionView::copyLabel()
  299. {
  300. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
  301. }
  302. void TransactionView::copyAmount()
  303. {
  304. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
  305. }
  306. void TransactionView::copyTxID()
  307. {
  308. GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole);
  309. }
  310. void TransactionView::editLabel()
  311. {
  312. if(!transactionView->selectionModel() ||!model)
  313. return;
  314. QModelIndexList selection = transactionView->selectionModel()->selectedRows();
  315. if(!selection.isEmpty())
  316. {
  317. AddressTableModel *addressBook = model->getAddressTableModel();
  318. if(!addressBook)
  319. return;
  320. QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
  321. if(address.isEmpty())
  322. {
  323. // If this transaction has no associated address, exit
  324. return;
  325. }
  326. // Is address in address book? Address book can miss address when a transaction is
  327. // sent from outside the UI.
  328. int idx = addressBook->lookupAddress(address);
  329. if(idx != -1)
  330. {
  331. // Edit sending / receiving address
  332. QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
  333. // Determine type of address, launch appropriate editor dialog type
  334. QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
  335. EditAddressDialog dlg(
  336. type == AddressTableModel::Receive
  337. ? EditAddressDialog::EditReceivingAddress
  338. : EditAddressDialog::EditSendingAddress, this);
  339. dlg.setModel(addressBook);
  340. dlg.loadRow(idx);
  341. dlg.exec();
  342. }
  343. else
  344. {
  345. // Add sending address
  346. EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
  347. this);
  348. dlg.setModel(addressBook);
  349. dlg.setAddress(address);
  350. dlg.exec();
  351. }
  352. }
  353. }
  354. void TransactionView::showDetails()
  355. {
  356. if(!transactionView->selectionModel())
  357. return;
  358. QModelIndexList selection = transactionView->selectionModel()->selectedRows();
  359. if(!selection.isEmpty())
  360. {
  361. TransactionDescDialog dlg(selection.at(0));
  362. dlg.exec();
  363. }
  364. }
  365. void TransactionView::openThirdPartyTxUrl(QString url)
  366. {
  367. if(!transactionView || !transactionView->selectionModel())
  368. return;
  369. QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
  370. if(!selection.isEmpty())
  371. QDesktopServices::openUrl(QUrl::fromUserInput(url.replace("%s", selection.at(0).data(TransactionTableModel::TxHashRole).toString())));
  372. }
  373. QWidget *TransactionView::createDateRangeWidget()
  374. {
  375. dateRangeWidget = new QFrame();
  376. dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
  377. dateRangeWidget->setContentsMargins(1,1,1,1);
  378. QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
  379. layout->setContentsMargins(0,0,0,0);
  380. layout->addSpacing(23);
  381. layout->addWidget(new QLabel(tr("Range:")));
  382. dateFrom = new QDateTimeEdit(this);
  383. dateFrom->setDisplayFormat("dd/MM/yy");
  384. dateFrom->setCalendarPopup(true);
  385. dateFrom->setMinimumWidth(100);
  386. dateFrom->setDate(QDate::currentDate().addDays(-7));
  387. layout->addWidget(dateFrom);
  388. layout->addWidget(new QLabel(tr("to")));
  389. dateTo = new QDateTimeEdit(this);
  390. dateTo->setDisplayFormat("dd/MM/yy");
  391. dateTo->setCalendarPopup(true);
  392. dateTo->setMinimumWidth(100);
  393. dateTo->setDate(QDate::currentDate());
  394. layout->addWidget(dateTo);
  395. layout->addStretch();
  396. // Hide by default
  397. dateRangeWidget->setVisible(false);
  398. // Notify on change
  399. connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
  400. connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
  401. return dateRangeWidget;
  402. }
  403. void TransactionView::dateRangeChanged()
  404. {
  405. if(!transactionProxyModel)
  406. return;
  407. transactionProxyModel->setDateRange(
  408. QDateTime(dateFrom->date()),
  409. QDateTime(dateTo->date()).addDays(1));
  410. }
  411. void TransactionView::focusTransaction(const QModelIndex &idx)
  412. {
  413. if(!transactionProxyModel)
  414. return;
  415. QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
  416. transactionView->scrollTo(targetIdx);
  417. transactionView->setCurrentIndex(targetIdx);
  418. transactionView->setFocus();
  419. }
  420. // We override the virtual resizeEvent of the QWidget to adjust tables column
  421. // sizes as the tables width is proportional to the dialogs width.
  422. void TransactionView::resizeEvent(QResizeEvent* event)
  423. {
  424. QWidget::resizeEvent(event);
  425. columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
  426. }