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