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