Now it can't be told if this is was a Windows App before. All Mac design principles are fulfilled and some cosmetics have been applied to suit the native look and feel. The biggest change there is the proper use of the Dock icon which takes the role of the Tray icon on Mac. The QDoubleSpinBox improves entering of Bitcoin amounts, no two separate fields are required anymore. All functionality and validation effects have been retained; pressing the comma key will be internally translated to a period to keep it consistent throughout the application and eases entering in countries which use the comma as decimal separator. Additionally, Notificator now supports Growl, Mac's native notification system. This is provided via Apple Script in order to avoid linking to Growl on compile time. Other changes involve encapsulation of Toolbar and Menubar creation, loading of Qt's own translation and some clean up.
403 lines
13 KiB
C++
403 lines
13 KiB
C++
#include "transactionview.h"
|
|
|
|
#include "transactionfilterproxy.h"
|
|
#include "transactionrecord.h"
|
|
#include "walletmodel.h"
|
|
#include "addresstablemodel.h"
|
|
#include "transactiontablemodel.h"
|
|
#include "bitcoinunits.h"
|
|
#include "csvmodelwriter.h"
|
|
#include "transactiondescdialog.h"
|
|
#include "editaddressdialog.h"
|
|
#include "optionsmodel.h"
|
|
|
|
#include <QScrollBar>
|
|
#include <QComboBox>
|
|
#include <QDoubleValidator>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QLineEdit>
|
|
#include <QTableView>
|
|
#include <QHeaderView>
|
|
#include <QPushButton>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QPoint>
|
|
#include <QMenu>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QLabel>
|
|
#include <QDateTimeEdit>
|
|
|
|
TransactionView::TransactionView(QWidget *parent) :
|
|
QWidget(parent), model(0), transactionProxyModel(0),
|
|
transactionView(0)
|
|
{
|
|
// Build filter row
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
QHBoxLayout *hlayout = new QHBoxLayout();
|
|
hlayout->setContentsMargins(0,0,0,0);
|
|
#ifdef Q_WS_MAC
|
|
hlayout->setSpacing(5);
|
|
hlayout->addSpacing(26);
|
|
#else
|
|
hlayout->setSpacing(0);
|
|
hlayout->addSpacing(23);
|
|
#endif
|
|
|
|
dateWidget = new QComboBox(this);
|
|
#ifdef Q_WS_MAC
|
|
dateWidget->setFixedWidth(121);
|
|
#else
|
|
dateWidget->setFixedWidth(120);
|
|
#endif
|
|
dateWidget->addItem(tr("All"), All);
|
|
dateWidget->addItem(tr("Today"), Today);
|
|
dateWidget->addItem(tr("This week"), ThisWeek);
|
|
dateWidget->addItem(tr("This month"), ThisMonth);
|
|
dateWidget->addItem(tr("Last month"), LastMonth);
|
|
dateWidget->addItem(tr("This year"), ThisYear);
|
|
dateWidget->addItem(tr("Range..."), Range);
|
|
hlayout->addWidget(dateWidget);
|
|
|
|
typeWidget = new QComboBox(this);
|
|
#ifdef Q_WS_MAC
|
|
typeWidget->setFixedWidth(121);
|
|
#else
|
|
typeWidget->setFixedWidth(120);
|
|
#endif
|
|
|
|
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
|
|
typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
|
|
TransactionFilterProxy::TYPE(TransactionRecord::RecvFromIP));
|
|
typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
|
|
TransactionFilterProxy::TYPE(TransactionRecord::SendToIP));
|
|
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
|
|
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
|
|
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
|
|
|
|
hlayout->addWidget(typeWidget);
|
|
|
|
addressWidget = new QLineEdit(this);
|
|
#if QT_VERSION >= 0x040700
|
|
addressWidget->setPlaceholderText(tr("Enter address or label to search"));
|
|
#endif
|
|
hlayout->addWidget(addressWidget);
|
|
|
|
amountWidget = new QLineEdit(this);
|
|
#if QT_VERSION >= 0x040700
|
|
amountWidget->setPlaceholderText(tr("Min amount"));
|
|
#endif
|
|
#ifdef Q_WS_MAC
|
|
amountWidget->setFixedWidth(97);
|
|
#else
|
|
amountWidget->setFixedWidth(100);
|
|
#endif
|
|
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
|
|
hlayout->addWidget(amountWidget);
|
|
|
|
QVBoxLayout *vlayout = new QVBoxLayout(this);
|
|
vlayout->setContentsMargins(0,0,0,0);
|
|
vlayout->setSpacing(0);
|
|
//vlayout->addLayout(hlayout2);
|
|
|
|
QTableView *view = new QTableView(this);
|
|
vlayout->addLayout(hlayout);
|
|
vlayout->addWidget(createDateRangeWidget());
|
|
vlayout->addWidget(view);
|
|
vlayout->setSpacing(0);
|
|
int width = view->verticalScrollBar()->sizeHint().width();
|
|
// Cover scroll bar width with spacing
|
|
#ifdef Q_WS_MAC
|
|
hlayout->addSpacing(width+2);
|
|
#else
|
|
hlayout->addSpacing(width);
|
|
#endif
|
|
// Always show scroll bar
|
|
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
view->setTabKeyNavigation(false);
|
|
view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
transactionView = view;
|
|
|
|
// Actions
|
|
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
|
|
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
|
|
QAction *editLabelAction = new QAction(tr("Edit label"), this);
|
|
QAction *showDetailsAction = new QAction(tr("Show details..."), this);
|
|
|
|
contextMenu = new QMenu();
|
|
contextMenu->addAction(copyAddressAction);
|
|
contextMenu->addAction(copyLabelAction);
|
|
contextMenu->addAction(editLabelAction);
|
|
contextMenu->addAction(showDetailsAction);
|
|
|
|
// Connect actions
|
|
connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
|
|
connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
|
|
connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
|
|
connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));
|
|
|
|
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
|
|
|
|
connect(view,
|
|
SIGNAL(customContextMenuRequested(QPoint)),
|
|
this,
|
|
SLOT(contextualMenu(QPoint)));
|
|
|
|
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
|
|
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
|
|
connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
|
|
connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
|
|
}
|
|
|
|
void TransactionView::setModel(WalletModel *model)
|
|
{
|
|
this->model = model;
|
|
|
|
transactionProxyModel = new TransactionFilterProxy(this);
|
|
transactionProxyModel->setSourceModel(model->getTransactionTableModel());
|
|
transactionProxyModel->setDynamicSortFilter(true);
|
|
|
|
transactionProxyModel->setSortRole(Qt::EditRole);
|
|
|
|
transactionView->setModel(transactionProxyModel);
|
|
transactionView->setAlternatingRowColors(true);
|
|
transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
transactionView->setSortingEnabled(true);
|
|
transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
|
|
transactionView->verticalHeader()->hide();
|
|
|
|
transactionView->horizontalHeader()->resizeSection(
|
|
TransactionTableModel::Status, 23);
|
|
transactionView->horizontalHeader()->resizeSection(
|
|
TransactionTableModel::Date, 120);
|
|
transactionView->horizontalHeader()->resizeSection(
|
|
TransactionTableModel::Type, 120);
|
|
transactionView->horizontalHeader()->setResizeMode(
|
|
TransactionTableModel::ToAddress, QHeaderView::Stretch);
|
|
transactionView->horizontalHeader()->resizeSection(
|
|
TransactionTableModel::Amount, 100);
|
|
|
|
}
|
|
|
|
void TransactionView::chooseDate(int idx)
|
|
{
|
|
QDate current = QDate::currentDate();
|
|
dateRangeWidget->setVisible(false);
|
|
switch(dateWidget->itemData(idx).toInt())
|
|
{
|
|
case All:
|
|
transactionProxyModel->setDateRange(
|
|
TransactionFilterProxy::MIN_DATE,
|
|
TransactionFilterProxy::MAX_DATE);
|
|
break;
|
|
case Today:
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(current),
|
|
TransactionFilterProxy::MAX_DATE);
|
|
break;
|
|
case ThisWeek: {
|
|
// Find last monday
|
|
QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(startOfWeek),
|
|
TransactionFilterProxy::MAX_DATE);
|
|
|
|
} break;
|
|
case ThisMonth:
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(QDate(current.year(), current.month(), 1)),
|
|
TransactionFilterProxy::MAX_DATE);
|
|
break;
|
|
case LastMonth:
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(QDate(current.year(), current.month()-1, 1)),
|
|
QDateTime(QDate(current.year(), current.month(), 1)));
|
|
break;
|
|
case ThisYear:
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(QDate(current.year(), 1, 1)),
|
|
TransactionFilterProxy::MAX_DATE);
|
|
break;
|
|
case Range:
|
|
dateRangeWidget->setVisible(true);
|
|
dateRangeChanged();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TransactionView::chooseType(int idx)
|
|
{
|
|
transactionProxyModel->setTypeFilter(
|
|
typeWidget->itemData(idx).toInt());
|
|
}
|
|
|
|
void TransactionView::changedPrefix(const QString &prefix)
|
|
{
|
|
transactionProxyModel->setAddressPrefix(prefix);
|
|
}
|
|
|
|
void TransactionView::changedAmount(const QString &amount)
|
|
{
|
|
qint64 amount_parsed = 0;
|
|
if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
|
|
{
|
|
transactionProxyModel->setMinAmount(amount_parsed);
|
|
}
|
|
else
|
|
{
|
|
transactionProxyModel->setMinAmount(0);
|
|
}
|
|
}
|
|
|
|
void TransactionView::exportClicked()
|
|
{
|
|
// CSV is currently the only supported format
|
|
QString filename = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr("Export Transaction Data"),
|
|
QDir::currentPath(),
|
|
tr("Comma separated file (*.csv)"));
|
|
|
|
if (filename.isNull()) return;
|
|
|
|
CSVModelWriter writer(filename);
|
|
|
|
// name, column, role
|
|
writer.setModel(transactionProxyModel);
|
|
writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
|
|
writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
|
|
writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
|
|
writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
|
|
writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
|
|
writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
|
|
writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
|
|
|
|
if(!writer.write())
|
|
{
|
|
QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
|
|
QMessageBox::Abort, QMessageBox::Abort);
|
|
}
|
|
}
|
|
|
|
void TransactionView::contextualMenu(const QPoint &point)
|
|
{
|
|
QModelIndex index = transactionView->indexAt(point);
|
|
if(index.isValid())
|
|
{
|
|
contextMenu->exec(QCursor::pos());
|
|
}
|
|
}
|
|
|
|
void TransactionView::copyAddress()
|
|
{
|
|
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
|
|
if(!selection.isEmpty())
|
|
{
|
|
QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::AddressRole).toString());
|
|
}
|
|
}
|
|
|
|
void TransactionView::copyLabel()
|
|
{
|
|
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
|
|
if(!selection.isEmpty())
|
|
{
|
|
QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::LabelRole).toString());
|
|
}
|
|
}
|
|
|
|
void TransactionView::editLabel()
|
|
{
|
|
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
|
|
if(!selection.isEmpty())
|
|
{
|
|
AddressTableModel *addressBook = model->getAddressTableModel();
|
|
QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
|
|
if(address.isEmpty())
|
|
{
|
|
// If this transaction has no associated address, exit
|
|
return;
|
|
}
|
|
int idx = addressBook->lookupAddress(address);
|
|
if(idx != -1)
|
|
{
|
|
// Edit sending / receiving address
|
|
QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
|
|
// Determine type of address, launch appropriate editor dialog type
|
|
QString type = modelIdx.data(AddressTableModel::TypeRole).toString();
|
|
|
|
EditAddressDialog dlg(type==AddressTableModel::Receive
|
|
? EditAddressDialog::EditReceivingAddress
|
|
: EditAddressDialog::EditSendingAddress,
|
|
this);
|
|
dlg.setModel(addressBook);
|
|
dlg.loadRow(idx);
|
|
dlg.exec();
|
|
}
|
|
else
|
|
{
|
|
// Add sending address
|
|
EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
|
|
this);
|
|
dlg.exec();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TransactionView::showDetails()
|
|
{
|
|
QModelIndexList selection = transactionView->selectionModel()->selectedRows();
|
|
if(!selection.isEmpty())
|
|
{
|
|
TransactionDescDialog dlg(selection.at(0));
|
|
dlg.exec();
|
|
}
|
|
}
|
|
|
|
QWidget *TransactionView::createDateRangeWidget()
|
|
{
|
|
dateRangeWidget = new QFrame();
|
|
dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
|
|
dateRangeWidget->setContentsMargins(1,1,1,1);
|
|
QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
|
|
layout->setContentsMargins(0,0,0,0);
|
|
layout->addSpacing(23);
|
|
layout->addWidget(new QLabel(tr("Range:")));
|
|
|
|
dateFrom = new QDateTimeEdit(this);
|
|
dateFrom->setDisplayFormat("dd/MM/yy");
|
|
dateFrom->setCalendarPopup(true);
|
|
dateFrom->setMinimumWidth(100);
|
|
dateFrom->setDate(QDate::currentDate().addDays(-7));
|
|
layout->addWidget(dateFrom);
|
|
layout->addWidget(new QLabel(tr("to")));
|
|
|
|
dateTo = new QDateTimeEdit(this);
|
|
dateTo->setDisplayFormat("dd/MM/yy");
|
|
dateTo->setCalendarPopup(true);
|
|
dateTo->setMinimumWidth(100);
|
|
dateTo->setDate(QDate::currentDate());
|
|
layout->addWidget(dateTo);
|
|
layout->addStretch();
|
|
|
|
// Hide by default
|
|
dateRangeWidget->setVisible(false);
|
|
|
|
// Notify on change
|
|
connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
|
|
connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
|
|
|
|
return dateRangeWidget;
|
|
}
|
|
|
|
void TransactionView::dateRangeChanged()
|
|
{
|
|
transactionProxyModel->setDateRange(
|
|
QDateTime(dateFrom->date()),
|
|
QDateTime(dateTo->date()).addDays(1));
|
|
}
|