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