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