Merge pull request #521 from laanwj/qt

Qt GUI
This commit is contained in:
Gavin Andresen
2011-09-26 06:05:11 -07:00
154 changed files with 16008 additions and 33 deletions

View File

@@ -50,13 +50,13 @@ Object JSONRPCError(int code, const string& message)
}
void PrintConsole(const char* format, ...)
void PrintConsole(const std::string &format, ...)
{
char buffer[50000];
int limit = sizeof(buffer);
va_list arg_ptr;
va_start(arg_ptr, format);
int ret = _vsnprintf(buffer, limit, format, arg_ptr);
int ret = _vsnprintf(buffer, limit, format.c_str(), arg_ptr);
va_end(arg_ptr);
if (ret < 0 || ret >= limit)
{

View File

@@ -99,8 +99,12 @@
#include "uibase.h"
#include "ui.h"
#else
#ifdef QT_GUI
#include "qtui.h"
#else
#include "noui.h"
#endif
#endif
#ifdef GUI
#include "xpm/addressbook16.xpm"

View File

@@ -4,7 +4,7 @@
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
#include "headers.h"
#include "db.h"
#include "rpc.h"
#include "bitcoinrpc.h"
#include "net.h"
#include "init.h"
#include "strlcpy.h"
@@ -80,7 +80,7 @@ void HandleSIGTERM(int)
//
// Start
//
#ifndef GUI
#if !defined(QT_GUI) && !defined(GUI)
int main(int argc, char* argv[])
{
bool fRet = false;
@@ -240,10 +240,9 @@ bool AppInit2(int argc, char* argv[])
fServer = GetBoolArg("-server");
/* force fServer when running without GUI */
#ifndef GUI
#if !defined(QT_GUI) && !defined(GUI)
fServer = true;
#endif
fPrintToConsole = GetBoolArg("-printtoconsole");
fPrintToDebugger = GetBoolArg("-printtodebugger");
@@ -252,6 +251,7 @@ bool AppInit2(int argc, char* argv[])
fNoListen = GetBoolArg("-nolisten") || fTOR;
fLogTimestamps = GetBoolArg("-logtimestamps");
#ifndef QT_GUI
for (int i = 1; i < argc; i++)
if (!IsSwitchChar(argv[i][0]))
fCommandLine = true;
@@ -261,6 +261,7 @@ bool AppInit2(int argc, char* argv[])
int ret = CommandLineRPC(argc, argv);
exit(ret);
}
#endif
#ifndef __WXMSW__
if (fDaemon)
@@ -373,18 +374,21 @@ bool AppInit2(int argc, char* argv[])
strErrors = "";
int64 nStart;
InitMessage(_("Loading addresses..."));
printf("Loading addresses...\n");
nStart = GetTimeMillis();
if (!LoadAddresses())
strErrors += _("Error loading addr.dat \n");
printf(" addresses %15"PRI64d"ms\n", GetTimeMillis() - nStart);
InitMessage(_("Loading block index..."));
printf("Loading block index...\n");
nStart = GetTimeMillis();
if (!LoadBlockIndex())
strErrors += _("Error loading blkindex.dat \n");
printf(" block index %15"PRI64d"ms\n", GetTimeMillis() - nStart);
InitMessage(_("Loading wallet..."));
printf("Loading wallet...\n");
nStart = GetTimeMillis();
bool fFirstRun;
@@ -415,12 +419,14 @@ bool AppInit2(int argc, char* argv[])
}
if (pindexBest != pindexRescan)
{
InitMessage(_("Rescanning..."));
printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight);
nStart = GetTimeMillis();
pwalletMain->ScanForWalletTransactions(pindexRescan, true);
printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart);
}
InitMessage(_("Done loading"));
printf("Done loading\n");
//// debug print
@@ -543,7 +549,7 @@ bool AppInit2(int argc, char* argv[])
RandAddSeedPerfmon();
if (!CreateThread(StartNode, NULL))
wxMessageBox("Error: CreateThread(StartNode) failed", "Bitcoin");
wxMessageBox(_("Error: CreateThread(StartNode) failed"), "Bitcoin");
if (fServer)
CreateThread(ThreadRPCServer, NULL);
@@ -553,7 +559,7 @@ bool AppInit2(int argc, char* argv[])
SetStartOnSystemStartup(true);
#endif
#ifndef GUI
#if !defined(QT_GUI) && !defined(GUI)
while (1)
Sleep(5000);
#endif

View File

@@ -32,6 +32,7 @@ uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3
static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
const int nTotalBlocksEstimate = 140700; // Conservative estimate of total nr of blocks on main chain
const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download"
int nMaxBlocksOfPeers = 0; // Amount of blocks that other nodes claim to have
CBlockIndex* pindexGenesisBlock = NULL;
int nBestHeight = -1;
CBigNum bnBestChainWork = 0;
@@ -726,6 +727,12 @@ int GetTotalBlocksEstimate()
}
}
// Return maximum amount of blocks that other nodes claim to have
int GetNumBlocksOfPeers()
{
return std::max(nMaxBlocksOfPeers, GetTotalBlocksEstimate());
}
bool IsInitialBlockDownload()
{
if (pindexBest == NULL || nBestHeight < (GetTotalBlocksEstimate()-nInitialBlockThreshold))
@@ -1840,6 +1847,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->fSuccessfullyConnected = true;
printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight);
if(pfrom->nStartingHeight > nMaxBlocksOfPeers)
{
nMaxBlocksOfPeers = pfrom->nStartingHeight;
}
}

View File

@@ -99,6 +99,7 @@ void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash
bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey);
bool CheckProofOfWork(uint256 hash, unsigned int nBits);
int GetTotalBlocksEstimate();
int GetNumBlocksOfPeers();
bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor);

View File

@@ -49,7 +49,7 @@ HEADERS = \
net.h \
noui.h \
protocol.h \
rpc.h \
bitcoinrpc.h \
script.h \
serialize.h \
strlcpy.h \
@@ -76,7 +76,7 @@ OBJS= \
obj/main.o \
obj/net.o \
obj/protocol.o \
obj/rpc.o \
obj/bitcoinrpc.o \
obj/script.o \
obj/util.o \
obj/wallet.o \

View File

@@ -46,7 +46,7 @@ HEADERS = \
net.h \
noui.h \
protocol.h \
rpc.h \
bitcoinrpc.h \
script.h \
serialize.h \
strlcpy.h \
@@ -74,7 +74,7 @@ OBJS= \
obj/main.o \
obj/net.o \
obj/protocol.o \
obj/rpc.o \
obj/bitcoinrpc.o \
obj/script.o \
obj/util.o \
obj/wallet.o \

View File

@@ -46,7 +46,7 @@ HEADERS = \
net.h \
noui.h \
protocol.h \
rpc.h \
bitcoinrpc.h \
script.h \
serialize.h \
strlcpy.h \
@@ -65,7 +65,7 @@ OBJS= \
obj/main.o \
obj/net.o \
obj/protocol.o \
obj/rpc.o \
obj/bitcoinrpc.o \
obj/script.o \
obj/util.o \
obj/wallet.o \

View File

@@ -52,7 +52,7 @@ HEADERS = \
net.h \
noui.h \
protocol.h \
rpc.h \
bitcoinrpc.h \
script.h \
serialize.h \
strlcpy.h \
@@ -71,7 +71,7 @@ OBJS= \
obj/main.o \
obj/net.o \
obj/protocol.o \
obj/rpc.o \
obj/bitcoinrpc.o \
obj/script.o \
obj/util.o \
obj/wallet.o \

View File

@@ -59,7 +59,7 @@ HEADERS = \
net.h \
noui.h \
protocol.h \
rpc.h \
bitcoinrpc.h \
script.h \
serialize.h \
strlcpy.h \

View File

@@ -67,4 +67,8 @@ inline void MainFrameRepaint()
{
}
inline void InitMessage(const std::string &message)
{
}
#endif

26
src/qt/aboutdialog.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "aboutdialog.h"
#include "ui_aboutdialog.h"
#include "clientmodel.h"
AboutDialog::AboutDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::AboutDialog)
{
ui->setupUi(this);
}
void AboutDialog::setModel(ClientModel *model)
{
ui->versionLabel->setText(model->formatFullVersion());
}
AboutDialog::~AboutDialog()
{
delete ui;
}
void AboutDialog::on_buttonBox_accepted()
{
close();
}

27
src/qt/aboutdialog.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef ABOUTDIALOG_H
#define ABOUTDIALOG_H
#include <QDialog>
namespace Ui {
class AboutDialog;
}
class ClientModel;
class AboutDialog : public QDialog
{
Q_OBJECT
public:
explicit AboutDialog(QWidget *parent = 0);
~AboutDialog();
void setModel(ClientModel *model);
private:
Ui::AboutDialog *ui;
private slots:
void on_buttonBox_accepted();
};
#endif // ABOUTDIALOG_H

215
src/qt/addressbookpage.cpp Normal file
View File

@@ -0,0 +1,215 @@
#include "addressbookpage.h"
#include "ui_addressbookpage.h"
#include "addresstablemodel.h"
#include "editaddressdialog.h"
#include "csvmodelwriter.h"
#include <QSortFilterProxyModel>
#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>
AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
QDialog(parent),
ui(new Ui::AddressBookPage),
model(0),
mode(mode),
tab(tab)
{
ui->setupUi(this);
switch(mode)
{
case ForSending:
connect(ui->tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(accept()));
ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableView->setFocus();
break;
case ForEditing:
ui->buttonBox->hide();
break;
}
switch(tab)
{
case SendingTab:
ui->labelExplanation->hide();
break;
case ReceivingTab:
break;
}
ui->tableView->setTabKeyNavigation(false);
connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
}
AddressBookPage::~AddressBookPage()
{
delete ui;
}
void AddressBookPage::setModel(AddressTableModel *model)
{
this->model = model;
// Refresh list from core
model->updateList();
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->setDynamicSortFilter(true);
switch(tab)
{
case ReceivingTab:
// Receive filter
proxyModel->setFilterRole(AddressTableModel::TypeRole);
proxyModel->setFilterFixedString(AddressTableModel::Receive);
break;
case SendingTab:
// Send filter
proxyModel->setFilterRole(AddressTableModel::TypeRole);
proxyModel->setFilterFixedString(AddressTableModel::Send);
break;
}
ui->tableView->setModel(proxyModel);
ui->tableView->sortByColumn(0, Qt::AscendingOrder);
// Set column widths
ui->tableView->horizontalHeader()->resizeSection(
AddressTableModel::Address, 320);
ui->tableView->horizontalHeader()->setResizeMode(
AddressTableModel::Label, QHeaderView::Stretch);
connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(selectionChanged()));
if(mode == ForSending)
{
// Auto-select first row when in sending mode
ui->tableView->selectRow(0);
}
selectionChanged();
}
QTableView *AddressBookPage::getCurrentTable()
{
return ui->tableView;
}
void AddressBookPage::on_copyToClipboard_clicked()
{
// Copy currently selected address to clipboard
// (or nothing, if nothing selected)
QTableView *table = getCurrentTable();
QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
foreach (QModelIndex index, indexes)
{
QVariant address = index.data();
QApplication::clipboard()->setText(address.toString());
}
}
void AddressBookPage::on_newAddressButton_clicked()
{
EditAddressDialog dlg(
tab == SendingTab ?
EditAddressDialog::NewSendingAddress :
EditAddressDialog::NewReceivingAddress);
dlg.setModel(model);
if(dlg.exec())
{
// Select row for newly created address
QString address = dlg.getAddress();
QModelIndexList lst = proxyModel->match(proxyModel->index(0,
AddressTableModel::Address, QModelIndex()),
Qt::EditRole, address, 1, Qt::MatchExactly);
if(!lst.isEmpty())
{
ui->tableView->setFocus();
ui->tableView->selectRow(lst.at(0).row());
}
}
}
void AddressBookPage::on_deleteButton_clicked()
{
QTableView *table = getCurrentTable();
QModelIndexList indexes = table->selectionModel()->selectedRows();
if(!indexes.isEmpty())
{
table->model()->removeRow(indexes.at(0).row());
}
}
void AddressBookPage::selectionChanged()
{
// Set button states based on selected tab and selection
QTableView *table = getCurrentTable();
if(table->selectionModel()->hasSelection())
{
switch(tab)
{
case SendingTab:
ui->deleteButton->setEnabled(true);
break;
case ReceivingTab:
ui->deleteButton->setEnabled(false);
break;
}
ui->copyToClipboard->setEnabled(true);
}
else
{
ui->deleteButton->setEnabled(false);
ui->copyToClipboard->setEnabled(false);
}
}
void AddressBookPage::done(int retval)
{
// When this is a tab/widget and not a model dialog, ignore "done"
if(mode == ForEditing)
return;
// Figure out which address was selected, and return it
QTableView *table = getCurrentTable();
QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
foreach (QModelIndex index, indexes)
{
QVariant address = table->model()->data(index);
returnValue = address.toString();
}
if(returnValue.isEmpty())
{
retval = Rejected;
}
QDialog::done(retval);
}
void AddressBookPage::exportClicked()
{
// CSV is currently the only supported format
QString filename = QFileDialog::getSaveFileName(
this,
tr("Export Address Book Data"),
QDir::currentPath(),
tr("Comma separated file (*.csv)"));
if (filename.isNull()) return;
CSVModelWriter writer(filename);
// name, column, role
writer.setModel(proxyModel);
writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole);
writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole);
if(!writer.write())
{
QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
QMessageBox::Abort, QMessageBox::Abort);
}
}

59
src/qt/addressbookpage.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef ADDRESSBOOKPAGE_H
#define ADDRESSBOOKPAGE_H
#include <QDialog>
namespace Ui {
class AddressBookPage;
}
class AddressTableModel;
QT_BEGIN_NAMESPACE
class QTableView;
class QItemSelection;
class QSortFilterProxyModel;
QT_END_NAMESPACE
class AddressBookPage : public QDialog
{
Q_OBJECT
public:
enum Tabs {
SendingTab = 0,
ReceivingTab = 1
};
enum Mode {
ForSending, // Pick address for sending
ForEditing // Open address book for editing
};
explicit AddressBookPage(Mode mode, Tabs tab, QWidget *parent = 0);
~AddressBookPage();
void setModel(AddressTableModel *model);
const QString &getReturnValue() const { return returnValue; }
public slots:
void done(int retval);
void exportClicked();
private:
Ui::AddressBookPage *ui;
AddressTableModel *model;
Mode mode;
Tabs tab;
QString returnValue;
QSortFilterProxyModel *proxyModel;
QTableView *getCurrentTable();
private slots:
void on_deleteButton_clicked();
void on_newAddressButton_clicked();
void on_copyToClipboard_clicked();
void selectionChanged();
};
#endif // ADDRESSBOOKDIALOG_H

View File

@@ -0,0 +1,347 @@
#include "addresstablemodel.h"
#include "guiutil.h"
#include "walletmodel.h"
#include "headers.h"
#include <QFont>
#include <QColor>
const QString AddressTableModel::Send = "S";
const QString AddressTableModel::Receive = "R";
struct AddressTableEntry
{
enum Type {
Sending,
Receiving
};
Type type;
QString label;
QString address;
AddressTableEntry() {}
AddressTableEntry(Type type, const QString &label, const QString &address):
type(type), label(label), address(address) {}
};
// Private implementation
struct AddressTablePriv
{
CWallet *wallet;
QList<AddressTableEntry> cachedAddressTable;
AddressTablePriv(CWallet *wallet):
wallet(wallet) {}
void refreshAddressTable()
{
cachedAddressTable.clear();
CRITICAL_BLOCK(wallet->cs_wallet)
{
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
const std::string& strName = item.second;
bool fMine = wallet->HaveKey(address);
cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending,
QString::fromStdString(strName),
QString::fromStdString(address.ToString())));
}
}
}
int size()
{
return cachedAddressTable.size();
}
AddressTableEntry *index(int idx)
{
if(idx >= 0 && idx < cachedAddressTable.size())
{
return &cachedAddressTable[idx];
}
else
{
return 0;
}
}
};
AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) :
QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0)
{
columns << tr("Label") << tr("Address");
priv = new AddressTablePriv(wallet);
priv->refreshAddressTable();
}
AddressTableModel::~AddressTableModel()
{
delete priv;
}
int AddressTableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return priv->size();
}
int AddressTableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return columns.length();
}
QVariant AddressTableModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
if(role == Qt::DisplayRole || role == Qt::EditRole)
{
switch(index.column())
{
case Label:
if(rec->label.isEmpty() && role == Qt::DisplayRole)
{
return tr("(no label)");
}
else
{
return rec->label;
}
case Address:
return rec->address;
}
}
else if (role == Qt::FontRole)
{
QFont font;
if(index.column() == Address)
{
font = GUIUtil::bitcoinAddressFont();
}
return font;
}
else if (role == TypeRole)
{
switch(rec->type)
{
case AddressTableEntry::Sending:
return Send;
case AddressTableEntry::Receiving:
return Receive;
default: break;
}
}
return QVariant();
}
bool AddressTableModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if(!index.isValid())
return false;
AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
editStatus = OK;
if(role == Qt::EditRole)
{
switch(index.column())
{
case Label:
wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString());
rec->label = value.toString();
break;
case Address:
// Refuse to set invalid address, set error status and return false
if(!walletModel->validateAddress(value.toString()))
{
editStatus = INVALID_ADDRESS;
return false;
}
// Double-check that we're not overwriting a receiving address
if(rec->type == AddressTableEntry::Sending)
{
CRITICAL_BLOCK(wallet->cs_wallet)
{
// Remove old entry
wallet->DelAddressBookName(rec->address.toStdString());
// Add new entry with new address
wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString());
}
rec->address = value.toString();
}
break;
}
emit dataChanged(index, index);
return true;
}
return false;
}
QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal)
{
if(role == Qt::DisplayRole)
{
return columns[section];
}
}
return QVariant();
}
Qt::ItemFlags AddressTableModel::flags(const QModelIndex & index) const
{
if(!index.isValid())
return 0;
AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
// Can edit address and label for sending addresses,
// and only label for receiving addresses.
if(rec->type == AddressTableEntry::Sending ||
(rec->type == AddressTableEntry::Receiving && index.column()==Label))
{
retval |= Qt::ItemIsEditable;
}
return retval;
}
QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & parent) const
{
Q_UNUSED(parent);
AddressTableEntry *data = priv->index(row);
if(data)
{
return createIndex(row, column, priv->index(row));
}
else
{
return QModelIndex();
}
}
void AddressTableModel::updateList()
{
// Update address book model from Bitcoin core
beginResetModel();
priv->refreshAddressTable();
endResetModel();
}
QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
{
std::string strLabel = label.toStdString();
std::string strAddress = address.toStdString();
editStatus = OK;
if(type == Send)
{
if(!walletModel->validateAddress(address))
{
editStatus = INVALID_ADDRESS;
return QString();
}
// Check for duplicate addresses
CRITICAL_BLOCK(wallet->cs_wallet)
{
if(wallet->mapAddressBook.count(strAddress))
{
editStatus = DUPLICATE_ADDRESS;
return QString();
}
}
}
else if(type == Receive)
{
// Generate a new address to associate with given label
WalletModel::UnlockContext ctx(walletModel->requestUnlock());
if(!ctx.isValid())
{
// Unlock wallet failed or was cancelled
editStatus = WALLET_UNLOCK_FAILURE;
return QString();
}
std::vector<unsigned char> newKey;
if(!wallet->GetKeyFromPool(newKey, true))
{
editStatus = KEY_GENERATION_FAILURE;
return QString();
}
strAddress = CBitcoinAddress(newKey).ToString();
}
else
{
return QString();
}
// Add entry and update list
CRITICAL_BLOCK(wallet->cs_wallet)
wallet->SetAddressBookName(strAddress, strLabel);
updateList();
return QString::fromStdString(strAddress);
}
bool AddressTableModel::removeRows(int row, int count, const QModelIndex & parent)
{
Q_UNUSED(parent);
AddressTableEntry *rec = priv->index(row);
if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
{
// Can only remove one row at a time, and cannot remove rows not in model.
// Also refuse to remove receiving addresses.
return false;
}
CRITICAL_BLOCK(wallet->cs_wallet)
{
wallet->DelAddressBookName(rec->address.toStdString());
}
updateList();
return true;
}
void AddressTableModel::update()
{
}
/* Look up label for address in address book, if not found return empty string.
*/
QString AddressTableModel::labelForAddress(const QString &address) const
{
CRITICAL_BLOCK(wallet->cs_wallet)
{
CBitcoinAddress address_parsed(address.toStdString());
std::map<CBitcoinAddress, std::string>::iterator mi = wallet->mapAddressBook.find(address_parsed);
if (mi != wallet->mapAddressBook.end())
{
return QString::fromStdString(mi->second);
}
}
return QString();
}
int AddressTableModel::lookupAddress(const QString &address) const
{
QModelIndexList lst = match(index(0, Address, QModelIndex()),
Qt::EditRole, address, 1, Qt::MatchExactly);
if(lst.isEmpty())
{
return -1;
}
else
{
return lst.at(0).row();
}
}

View File

@@ -0,0 +1,83 @@
#ifndef ADDRESSTABLEMODEL_H
#define ADDRESSTABLEMODEL_H
#include <QAbstractTableModel>
#include <QStringList>
class AddressTablePriv;
class CWallet;
class WalletModel;
class AddressTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit AddressTableModel(CWallet *wallet, WalletModel *parent = 0);
~AddressTableModel();
enum ColumnIndex {
Label = 0, /* User specified label */
Address = 1 /* Bitcoin address */
};
enum RoleIndex {
TypeRole = Qt::UserRole
};
// Return status of last edit/insert operation
enum EditStatus {
OK,
INVALID_ADDRESS,
DUPLICATE_ADDRESS,
WALLET_UNLOCK_FAILURE,
KEY_GENERATION_FAILURE
};
static const QString Send; /* Send addres */
static const QString Receive; /* Receive address */
/* Overridden methods from QAbstractTableModel */
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex & index, const QVariant & value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QModelIndex index(int row, int column, const QModelIndex & parent) const;
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex());
Qt::ItemFlags flags(const QModelIndex & index) const;
/* Add an address to the model.
Returns the added address on success, and an empty string otherwise.
*/
QString addRow(const QString &type, const QString &label, const QString &address);
/* Update address list from core. Invalidates any indices.
*/
void updateList();
/* Look up label for address in address book, if not found return empty string.
*/
QString labelForAddress(const QString &address) const;
/* Look up row index of an address in the model.
Return -1 if not found.
*/
int lookupAddress(const QString &address) const;
EditStatus getEditStatus() const { return editStatus; }
private:
WalletModel *walletModel;
CWallet *wallet;
AddressTablePriv *priv;
QStringList columns;
EditStatus editStatus;
signals:
void defaultAddressChanged(const QString &address);
public slots:
void update();
};
#endif // ADDRESSTABLEMODEL_H

View File

@@ -0,0 +1,186 @@
#include "askpassphrasedialog.h"
#include "ui_askpassphrasedialog.h"
#include "guiconstants.h"
#include "walletmodel.h"
#include <QMessageBox>
#include <QPushButton>
AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget *parent) :
QDialog(parent),
ui(new Ui::AskPassphraseDialog),
mode(mode),
model(0)
{
ui->setupUi(this);
ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE);
ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE);
ui->passEdit3->setMaxLength(MAX_PASSPHRASE_SIZE);
switch(mode)
{
case Encrypt: // Ask passphrase x2
ui->passLabel1->hide();
ui->passEdit1->hide();
ui->warningLabel->setText(tr("Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>."));
setWindowTitle(tr("Encrypt wallet"));
break;
case Unlock: // Ask passphrase
ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet."));
ui->passLabel2->hide();
ui->passEdit2->hide();
ui->passLabel3->hide();
ui->passEdit3->hide();
setWindowTitle(tr("Unlock wallet"));
break;
case Decrypt: // Ask passphrase
ui->warningLabel->setText(tr("This operation needs your wallet passphrase to decrypt the wallet."));
ui->passLabel2->hide();
ui->passEdit2->hide();
ui->passLabel3->hide();
ui->passEdit3->hide();
setWindowTitle(tr("Decrypt wallet"));
break;
case ChangePass: // Ask old passphrase + new passphrase x2
setWindowTitle(tr("Change passphrase"));
ui->warningLabel->setText(tr("Enter the old and new passphrase to the wallet."));
break;
}
resize(minimumSize()); // Get rid of extra space in dialog
textChanged();
connect(ui->passEdit1, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
connect(ui->passEdit2, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
connect(ui->passEdit3, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
}
AskPassphraseDialog::~AskPassphraseDialog()
{
// Attempt to overwrite text so that they do not linger around in memory
ui->passEdit1->setText(QString(" ").repeated(ui->passEdit1->text().size()));
ui->passEdit2->setText(QString(" ").repeated(ui->passEdit2->text().size()));
ui->passEdit3->setText(QString(" ").repeated(ui->passEdit3->text().size()));
delete ui;
}
void AskPassphraseDialog::setModel(WalletModel *model)
{
this->model = model;
}
void AskPassphraseDialog::accept()
{
std::string oldpass, newpass1, newpass2;
// TODO: mlock memory / munlock on return so they will not be swapped out, really need "mlockedstring" wrapper class to do this safely
oldpass.reserve(MAX_PASSPHRASE_SIZE);
newpass1.reserve(MAX_PASSPHRASE_SIZE);
newpass2.reserve(MAX_PASSPHRASE_SIZE);
oldpass.assign(ui->passEdit1->text().toStdString());
newpass1.assign(ui->passEdit2->text().toStdString());
newpass2.assign(ui->passEdit3->text().toStdString());
switch(mode)
{
case Encrypt: {
if(newpass1.empty() || newpass2.empty())
{
// Cannot encrypt with empty passphrase
break;
}
QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm wallet encryption"),
tr("WARNING: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!\nAre you sure you wish to encrypt your wallet?"),
QMessageBox::Yes|QMessageBox::Cancel,
QMessageBox::Cancel);
if(retval == QMessageBox::Yes)
{
if(newpass1 == newpass2)
{
if(model->setWalletEncrypted(true, newpass1))
{
QMessageBox::warning(this, tr("Wallet encrypted"),
tr("Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer."));
}
else
{
QMessageBox::critical(this, tr("Wallet encryption failed"),
tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted."));
}
QDialog::accept(); // Success
}
else
{
QMessageBox::critical(this, tr("Wallet encryption failed"),
tr("The supplied passphrases do not match."));
}
}
else
{
QDialog::reject(); // Cancelled
}
} break;
case Unlock:
if(!model->setWalletLocked(false, oldpass))
{
QMessageBox::critical(this, tr("Wallet unlock failed"),
tr("The passphrase entered for the wallet decryption was incorrect."));
}
else
{
QDialog::accept(); // Success
}
break;
case Decrypt:
if(!model->setWalletEncrypted(false, oldpass))
{
QMessageBox::critical(this, tr("Wallet decryption failed"),
tr("The passphrase entered for the wallet decryption was incorrect."));
}
else
{
QDialog::accept(); // Success
}
break;
case ChangePass:
if(newpass1 == newpass2)
{
if(model->changePassphrase(oldpass, newpass1))
{
QMessageBox::information(this, tr("Wallet encrypted"),
tr("Wallet passphrase was succesfully changed."));
QDialog::accept(); // Success
}
else
{
QMessageBox::critical(this, tr("Wallet encryption failed"),
tr("The passphrase entered for the wallet decryption was incorrect."));
}
}
else
{
QMessageBox::critical(this, tr("Wallet encryption failed"),
tr("The supplied passphrases do not match."));
}
break;
}
}
void AskPassphraseDialog::textChanged()
{
// Validate input, set Ok button to enabled when accepable
bool acceptable = false;
switch(mode)
{
case Encrypt: // New passphrase x2
acceptable = !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty();
break;
case Unlock: // Old passphrase x1
case Decrypt:
acceptable = !ui->passEdit1->text().isEmpty();
break;
case ChangePass: // Old passphrase x1, new passphrase x2
acceptable = !ui->passEdit1->text().isEmpty() && !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty();
break;
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(acceptable);
}

View File

@@ -0,0 +1,40 @@
#ifndef ASKPASSPHRASEDIALOG_H
#define ASKPASSPHRASEDIALOG_H
#include <QDialog>
namespace Ui {
class AskPassphraseDialog;
}
class WalletModel;
class AskPassphraseDialog : public QDialog
{
Q_OBJECT
public:
enum Mode {
Encrypt, // Ask passphrase x2
Unlock, // Ask passphrase
ChangePass, // Ask old passphrase + new passphrase x2
Decrypt // Ask passphrase
};
explicit AskPassphraseDialog(Mode mode, QWidget *parent = 0);
~AskPassphraseDialog();
void accept();
void setModel(WalletModel *model);
private:
Ui::AskPassphraseDialog *ui;
Mode mode;
WalletModel *model;
private slots:
void textChanged();
};
#endif // ASKPASSPHRASEDIALOG_H

170
src/qt/bitcoin.cpp Normal file
View File

@@ -0,0 +1,170 @@
/*
* W.J. van der Laan 2011
*/
#include "bitcoingui.h"
#include "clientmodel.h"
#include "walletmodel.h"
#include "optionsmodel.h"
#include "headers.h"
#include "init.h"
#include <QApplication>
#include <QMessageBox>
#include <QThread>
#include <QTextCodec>
#include <QLocale>
#include <QTranslator>
#include <QSplashScreen>
// Need a global reference for the notifications to find the GUI
BitcoinGUI *guiref;
QSplashScreen *splashref;
int MyMessageBox(const std::string& message, const std::string& caption, int style, wxWindow* parent, int x, int y)
{
// Message from main thread
if(guiref)
{
guiref->error(QString::fromStdString(caption),
QString::fromStdString(message));
}
else
{
QMessageBox::critical(0, QString::fromStdString(caption),
QString::fromStdString(message),
QMessageBox::Ok, QMessageBox::Ok);
}
return 4;
}
int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style, wxWindow* parent, int x, int y)
{
// Message from network thread
if(guiref)
{
QMetaObject::invokeMethod(guiref, "error", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(caption)),
Q_ARG(QString, QString::fromStdString(message)));
}
else
{
printf("%s: %s\n", caption.c_str(), message.c_str());
fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str());
}
return 4;
}
bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindow* parent)
{
if(!guiref)
return false;
if(nFeeRequired < MIN_TX_FEE || nFeeRequired <= nTransactionFee || fDaemon)
return true;
bool payFee = false;
// Call slot on GUI thread.
// If called from another thread, use a blocking QueuedConnection.
Qt::ConnectionType connectionType = Qt::DirectConnection;
if(QThread::currentThread() != QCoreApplication::instance()->thread())
{
connectionType = Qt::BlockingQueuedConnection;
}
QMetaObject::invokeMethod(guiref, "askFee", connectionType,
Q_ARG(qint64, nFeeRequired),
Q_ARG(bool*, &payFee));
return payFee;
}
void CalledSetStatusBar(const std::string& strText, int nField)
{
// Only used for built-in mining, which is disabled, simple ignore
}
void UIThreadCall(boost::function0<void> fn)
{
// Only used for built-in mining, which is disabled, simple ignore
}
void MainFrameRepaint()
{
}
void InitMessage(const std::string &message)
{
if(splashref)
{
splashref->showMessage(QString::fromStdString(message), Qt::AlignBottom|Qt::AlignHCenter, QColor(255,255,200));
QApplication::instance()->processEvents();
}
}
/*
Translate string to current locale using Qt.
*/
std::string _(const char* psz)
{
return QCoreApplication::translate("bitcoin-core", psz).toStdString();
}
int main(int argc, char *argv[])
{
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
Q_INIT_RESOURCE(bitcoin);
QApplication app(argc, argv);
// Load language file for system locale
QString locale = QLocale::system().name();
QTranslator translator;
translator.load("bitcoin_"+locale);
app.installTranslator(&translator);
QSplashScreen splash(QPixmap(":/images/splash"), 0);
splash.show();
splash.setAutoFillBackground(true);
splashref = &splash;
app.processEvents();
app.setQuitOnLastWindowClosed(false);
try
{
if(AppInit2(argc, argv))
{
{
// Put this in a block, so that BitcoinGUI is cleaned up properly before
// calling Shutdown().
BitcoinGUI window;
splash.finish(&window);
OptionsModel optionsModel(pwalletMain);
ClientModel clientModel(&optionsModel);
WalletModel walletModel(pwalletMain, &optionsModel);
guiref = &window;
window.setClientModel(&clientModel);
window.setWalletModel(&walletModel);
window.show();
app.exec();
guiref = 0;
}
Shutdown(NULL);
}
else
{
return 1;
}
} catch (std::exception& e) {
PrintException(&e, "Runaway exception");
} catch (...) {
PrintException(NULL, "Runaway exception");
}
return 0;
}

48
src/qt/bitcoin.qrc Normal file
View File

@@ -0,0 +1,48 @@
<RCC>
<qresource prefix="/icons" lang="edit">
<file alias="bitcoin">res/icons/bitcoin.png</file>
<file alias="address-book">res/icons/address-book.png</file>
<file alias="quit">res/icons/quit.png</file>
<file alias="send">res/icons/send.png</file>
<file alias="toolbar">res/icons/toolbar.png</file>
<file alias="connect_0">res/icons/connect0_16.png</file>
<file alias="connect_1">res/icons/connect1_16.png</file>
<file alias="connect_2">res/icons/connect2_16.png</file>
<file alias="connect_3">res/icons/connect3_16.png</file>
<file alias="connect_4">res/icons/connect4_16.png</file>
<file alias="transaction_0">res/icons/transaction0.png</file>
<file alias="transaction_confirmed">res/icons/transaction2.png</file>
<file alias="transaction_1">res/icons/clock1.png</file>
<file alias="transaction_2">res/icons/clock2.png</file>
<file alias="transaction_3">res/icons/clock3.png</file>
<file alias="transaction_4">res/icons/clock4.png</file>
<file alias="transaction_5">res/icons/clock5.png</file>
<file alias="options">res/icons/configure.png</file>
<file alias="receiving_addresses">res/icons/receive.png</file>
<file alias="editpaste">res/icons/editpaste.png</file>
<file alias="editcopy">res/icons/editcopy.png</file>
<file alias="add">res/icons/add.png</file>
<file alias="bitcoin_testnet">res/icons/bitcoin_testnet.png</file>
<file alias="toolbar_testnet">res/icons/toolbar_testnet.png</file>
<file alias="edit">res/icons/edit.png</file>
<file alias="history">res/icons/history.png</file>
<file alias="overview">res/icons/overview.png</file>
<file alias="export">res/icons/export.png</file>
<file alias="synced">res/icons/synced.png</file>
<file alias="remove">res/icons/remove.png</file>
<file alias="tx_mined">res/icons/tx_mined.png</file>
<file alias="tx_input">res/icons/tx_input.png</file>
<file alias="tx_output">res/icons/tx_output.png</file>
<file alias="tx_inout">res/icons/tx_inout.png</file>
<file alias="lock_closed">res/icons/lock_closed.png</file>
<file alias="lock_open">res/icons/lock_open.png</file>
<file alias="key">res/icons/key.png</file>
</qresource>
<qresource prefix="/images">
<file alias="about">res/images/about.png</file>
<file alias="splash">res/images/splash2.jpg</file>
</qresource>
<qresource prefix="/movies">
<file alias="update_spinner">res/movies/update_spinner.mng</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,67 @@
#include "bitcoinaddressvalidator.h"
/* Base58 characters are:
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
This is:
- All numbers except for '0'
- All uppercase letters except for 'I' and 'O'
- All lowercase letters except for 'l'
User friendly Base58 input can map
- 'l' and 'I' to '1'
- '0' and 'O' to 'o'
*/
BitcoinAddressValidator::BitcoinAddressValidator(QObject *parent) :
QValidator(parent)
{
}
QValidator::State BitcoinAddressValidator::validate(QString &input, int &pos) const
{
// Correction
for(int idx=0; idx<input.size(); ++idx)
{
switch(input.at(idx).unicode())
{
case 'l':
case 'I':
input[idx] = QChar('1');
break;
case '0':
case 'O':
input[idx] = QChar('o');
break;
default:
break;
}
}
// Validation
QValidator::State state = QValidator::Acceptable;
for(int idx=0; idx<input.size(); ++idx)
{
int ch = input.at(idx).unicode();
if(((ch >= '0' && ch<='9') ||
(ch >= 'a' && ch<='z') ||
(ch >= 'A' && ch<='Z')) &&
ch != 'l' && ch != 'I' && ch != '0' && ch != 'O')
{
// Alphanumeric and not a 'forbidden' character
}
else
{
state = QValidator::Invalid;
}
}
// Empty address is "intermediate" input
if(input.isEmpty())
{
state = QValidator::Intermediate;
}
return state;
}

View File

@@ -0,0 +1,24 @@
#ifndef BITCOINADDRESSVALIDATOR_H
#define BITCOINADDRESSVALIDATOR_H
#include <QRegExpValidator>
/* Base48 entry widget validator.
Corrects near-miss characters and refuses characters that are no part of base48.
*/
class BitcoinAddressValidator : public QValidator
{
Q_OBJECT
public:
explicit BitcoinAddressValidator(QObject *parent = 0);
State validate(QString &input, int &pos) const;
static const int MaxAddressLength = 34;
signals:
public slots:
};
#endif // BITCOINADDRESSVALIDATOR_H

View File

@@ -0,0 +1,178 @@
#include "bitcoinamountfield.h"
#include "qvalidatedlineedit.h"
#include "qvaluecombobox.h"
#include "bitcoinunits.h"
#include <QLabel>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QComboBox>
BitcoinAmountField::BitcoinAmountField(QWidget *parent):
QWidget(parent), amount(0), decimals(0), currentUnit(-1)
{
amount = new QValidatedLineEdit(this);
amount->setValidator(new QRegExpValidator(QRegExp("[0-9]*"), this));
amount->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
amount->installEventFilter(this);
amount->setMaximumWidth(75);
decimals = new QValidatedLineEdit(this);
decimals->setValidator(new QRegExpValidator(QRegExp("[0-9]+"), this));
decimals->setAlignment(Qt::AlignLeft|Qt::AlignVCenter);
decimals->setMaximumWidth(75);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setSpacing(0);
layout->addWidget(amount);
layout->addWidget(new QLabel(QString("<b>.</b>")));
layout->addWidget(decimals);
unit = new QValueComboBox(this);
unit->setModel(new BitcoinUnits(this));
layout->addWidget(unit);
layout->addStretch(1);
layout->setContentsMargins(0,0,0,0);
setLayout(layout);
setFocusPolicy(Qt::TabFocus);
setFocusProxy(amount);
// If one if the widgets changes, the combined content changes as well
connect(amount, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged()));
connect(decimals, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged()));
connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
// Set default based on configuration
unitChanged(unit->currentIndex());
}
void BitcoinAmountField::setText(const QString &text)
{
const QStringList parts = text.split(QString("."));
if(parts.size() == 2)
{
amount->setText(parts[0]);
decimals->setText(parts[1]);
}
else
{
amount->setText(QString());
decimals->setText(QString());
}
}
void BitcoinAmountField::clear()
{
amount->clear();
decimals->clear();
unit->setCurrentIndex(0);
}
bool BitcoinAmountField::validate()
{
bool valid = true;
if(decimals->text().isEmpty())
{
decimals->setValid(false);
valid = false;
}
if(!BitcoinUnits::parse(currentUnit, text(), 0))
{
setValid(false);
valid = false;
}
return valid;
}
void BitcoinAmountField::setValid(bool valid)
{
amount->setValid(valid);
decimals->setValid(valid);
}
QString BitcoinAmountField::text() const
{
if(decimals->text().isEmpty() && amount->text().isEmpty())
{
return QString();
}
return amount->text() + QString(".") + decimals->text();
}
// Intercept '.' and ',' keys, if pressed focus a specified widget
bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
{
Q_UNUSED(object);
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Period || keyEvent->key() == Qt::Key_Comma)
{
decimals->setFocus();
decimals->selectAll();
}
}
return false;
}
QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
{
QWidget::setTabOrder(prev, amount);
QWidget::setTabOrder(amount, decimals);
return decimals;
}
qint64 BitcoinAmountField::value(bool *valid_out) const
{
qint64 val_out = 0;
bool valid = BitcoinUnits::parse(currentUnit, text(), &val_out);
if(valid_out)
{
*valid_out = valid;
}
return val_out;
}
void BitcoinAmountField::setValue(qint64 value)
{
setText(BitcoinUnits::format(currentUnit, value));
}
void BitcoinAmountField::unitChanged(int idx)
{
// Use description tooltip for current unit for the combobox
unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
// Determine new unit ID
int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
// Parse current value and convert to new unit
bool valid = false;
qint64 currentValue = value(&valid);
currentUnit = newUnit;
// Set max length after retrieving the value, to prevent truncation
amount->setMaxLength(BitcoinUnits::amountDigits(currentUnit));
decimals->setMaxLength(BitcoinUnits::decimals(currentUnit));
if(valid)
{
// If value was valid, re-place it in the widget with the new unit
setValue(currentValue);
}
else
{
// If current value is invalid, just clear field
setText("");
}
setValid(true);
}
void BitcoinAmountField::setDisplayUnit(int newUnit)
{
unit->setValue(newUnit);
}

View File

@@ -0,0 +1,59 @@
#ifndef BITCOINFIELD_H
#define BITCOINFIELD_H
#include <QWidget>
QT_BEGIN_NAMESPACE
class QValidatedLineEdit;
class QValueComboBox;
QT_END_NAMESPACE
// Coin amount entry widget with separate parts for whole
// coins and decimals.
class BitcoinAmountField: public QWidget
{
Q_OBJECT
Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY textChanged USER true);
public:
explicit BitcoinAmountField(QWidget *parent = 0);
qint64 value(bool *valid=0) const;
void setValue(qint64 value);
// Mark current valid as invalid in UI
void setValid(bool valid);
bool validate();
// Change current unit
void setDisplayUnit(int unit);
// Make field empty and ready for new input
void clear();
// Qt messes up the tab chain by default in some cases (issue http://bugreports.qt.nokia.com/browse/QTBUG-10907)
// Hence we have to set it up manually
QWidget *setupTabChain(QWidget *prev);
signals:
void textChanged();
protected:
// Intercept '.' and ',' keys, if pressed focus a specified widget
bool eventFilter(QObject *object, QEvent *event);
private:
QValidatedLineEdit *amount;
QValidatedLineEdit *decimals;
QValueComboBox *unit;
int currentUnit;
void setText(const QString &text);
QString text() const;
private slots:
void unitChanged(int idx);
};
#endif // BITCOINFIELD_H

613
src/qt/bitcoingui.cpp Normal file
View File

@@ -0,0 +1,613 @@
/*
* Qt4 bitcoin GUI.
*
* W.J. van der Laan 2011
*/
#include "bitcoingui.h"
#include "transactiontablemodel.h"
#include "addressbookpage.h"
#include "sendcoinsdialog.h"
#include "optionsdialog.h"
#include "aboutdialog.h"
#include "clientmodel.h"
#include "walletmodel.h"
#include "editaddressdialog.h"
#include "optionsmodel.h"
#include "transactiondescdialog.h"
#include "addresstablemodel.h"
#include "transactionview.h"
#include "overviewpage.h"
#include "bitcoinunits.h"
#include "guiconstants.h"
#include "askpassphrasedialog.h"
#include "notificator.h"
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QIcon>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QLocale>
#include <QMessageBox>
#include <QProgressBar>
#include <QStackedWidget>
#include <QDateTime>
#include <QMovie>
#include <QDragEnterEvent>
#include <QUrl>
#include <iostream>
BitcoinGUI::BitcoinGUI(QWidget *parent):
QMainWindow(parent),
clientModel(0),
walletModel(0),
encryptWalletAction(0),
changePassphraseAction(0),
trayIcon(0),
notificator(0)
{
resize(850, 550);
setWindowTitle(tr("Bitcoin Wallet"));
setWindowIcon(QIcon(":icons/bitcoin"));
// Accept D&D of URIs
setAcceptDrops(true);
createActions();
// Menus
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(sendCoinsAction);
file->addAction(receiveCoinsAction);
file->addSeparator();
file->addAction(quitAction);
QMenu *settings = menuBar()->addMenu(tr("&Settings"));
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addSeparator();
settings->addAction(optionsAction);
QMenu *help = menuBar()->addMenu(tr("&Help"));
help->addAction(aboutAction);
// Toolbars
QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar->addAction(overviewAction);
toolbar->addAction(sendCoinsAction);
toolbar->addAction(receiveCoinsAction);
toolbar->addAction(historyAction);
toolbar->addAction(addressBookAction);
QToolBar *toolbar2 = addToolBar(tr("Actions toolbar"));
toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar2->addAction(exportAction);
// Create tabs
overviewPage = new OverviewPage();
transactionsPage = new QWidget(this);
QVBoxLayout *vbox = new QVBoxLayout();
transactionView = new TransactionView(this);
vbox->addWidget(transactionView);
transactionsPage->setLayout(vbox);
addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
sendCoinsPage = new SendCoinsDialog(this);
centralWidget = new QStackedWidget(this);
centralWidget->addWidget(overviewPage);
centralWidget->addWidget(transactionsPage);
centralWidget->addWidget(addressBookPage);
centralWidget->addWidget(receiveCoinsPage);
centralWidget->addWidget(sendCoinsPage);
setCentralWidget(centralWidget);
// Create status bar
statusBar();
// Status bar notification icons
QFrame *frameBlocks = new QFrame();
//frameBlocks->setFrameStyle(QFrame::Panel | QFrame::Sunken);
frameBlocks->setContentsMargins(0,0,0,0);
frameBlocks->setMinimumWidth(56);
frameBlocks->setMaximumWidth(56);
QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
frameBlocksLayout->setContentsMargins(3,0,3,0);
frameBlocksLayout->setSpacing(3);
labelEncryptionIcon = new QLabel();
labelConnectionsIcon = new QLabel();
labelBlocksIcon = new QLabel();
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(labelEncryptionIcon);
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(labelConnectionsIcon);
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(labelBlocksIcon);
frameBlocksLayout->addStretch();
// Progress bar for blocks download
progressBarLabel = new QLabel(tr("Synchronizing with network..."));
progressBarLabel->setVisible(false);
progressBar = new QProgressBar();
progressBar->setToolTip(tr("Block chain synchronization in progress"));
progressBar->setVisible(false);
statusBar()->addWidget(progressBarLabel);
statusBar()->addWidget(progressBar);
statusBar()->addPermanentWidget(frameBlocks);
createTrayIcon();
syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this);
// Clicking on a transaction on the overview page simply sends you to transaction history page
connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
// Doubleclicking on a transaction on the transaction history page shows details
connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
gotoOverviewPage();
}
void BitcoinGUI::createActions()
{
QActionGroup *tabGroup = new QActionGroup(this);
overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
overviewAction->setToolTip(tr("Show general overview of wallet"));
overviewAction->setCheckable(true);
tabGroup->addAction(overviewAction);
historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
historyAction->setToolTip(tr("Browse transaction history"));
historyAction->setCheckable(true);
tabGroup->addAction(historyAction);
addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
addressBookAction->setToolTip(tr("Edit the list of stored addresses and labels"));
addressBookAction->setCheckable(true);
tabGroup->addAction(addressBookAction);
receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
receiveCoinsAction->setToolTip(tr("Show the list of addresses for receiving payments"));
receiveCoinsAction->setCheckable(true);
tabGroup->addAction(receiveCoinsAction);
sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
sendCoinsAction->setToolTip(tr("Send coins to a bitcoin address"));
sendCoinsAction->setCheckable(true);
tabGroup->addAction(sendCoinsAction);
connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
quitAction = new QAction(QIcon(":/icons/quit"), tr("&Exit"), this);
quitAction->setToolTip(tr("Quit application"));
aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About"), this);
aboutAction->setToolTip(tr("Show information about Bitcoin"));
optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
optionsAction->setToolTip(tr("Modify configuration options for bitcoin"));
openBitcoinAction = new QAction(QIcon(":/icons/bitcoin"), tr("Open &Bitcoin"), this);
openBitcoinAction->setToolTip(tr("Show the Bitcoin window"));
exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
exportAction->setToolTip(tr("Export the current view to a file"));
encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet"), this);
encryptWalletAction->setToolTip(tr("Encrypt or decrypt wallet"));
encryptWalletAction->setCheckable(true);
changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase"), this);
changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption"));
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
connect(openBitcoinAction, SIGNAL(triggered()), this, SLOT(showNormal()));
connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
}
void BitcoinGUI::setClientModel(ClientModel *clientModel)
{
this->clientModel = clientModel;
if(clientModel->isTestNet())
{
QString title_testnet = windowTitle() + QString(" ") + tr("[testnet]");
setWindowTitle(title_testnet);
setWindowIcon(QIcon(":icons/bitcoin_testnet"));
if(trayIcon)
{
trayIcon->setToolTip(title_testnet);
trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
}
}
// Keep up to date with client
setNumConnections(clientModel->getNumConnections());
connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
setNumBlocks(clientModel->getNumBlocks());
connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int)));
// Report errors from network/worker thread
connect(clientModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
}
void BitcoinGUI::setWalletModel(WalletModel *walletModel)
{
this->walletModel = walletModel;
// Report errors from wallet thread
connect(walletModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
// Put transaction list in tabs
transactionView->setModel(walletModel);
overviewPage->setModel(walletModel);
addressBookPage->setModel(walletModel->getAddressTableModel());
receiveCoinsPage->setModel(walletModel->getAddressTableModel());
sendCoinsPage->setModel(walletModel);
setEncryptionStatus(walletModel->getEncryptionStatus());
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
// Balloon popup for new transaction
connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(incomingTransaction(QModelIndex,int,int)));
// Ask for passphrase if needed
connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
}
void BitcoinGUI::createTrayIcon()
{
QMenu *trayIconMenu = new QMenu(this);
trayIconMenu->addAction(openBitcoinAction);
trayIconMenu->addAction(optionsAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
trayIcon = new QSystemTrayIcon(this);
trayIcon->setContextMenu(trayIconMenu);
trayIcon->setToolTip("Bitcoin client");
trayIcon->setIcon(QIcon(":/icons/toolbar"));
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
trayIcon->show();
notificator = new Notificator(tr("bitcoin-qt"), trayIcon);
}
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
if(reason == QSystemTrayIcon::Trigger)
{
// Click on system tray icon triggers "open bitcoin"
openBitcoinAction->trigger();
}
}
void BitcoinGUI::optionsClicked()
{
OptionsDialog dlg;
dlg.setModel(clientModel->getOptionsModel());
dlg.exec();
}
void BitcoinGUI::aboutClicked()
{
AboutDialog dlg;
dlg.setModel(clientModel);
dlg.exec();
}
void BitcoinGUI::setNumConnections(int count)
{
QString icon;
switch(count)
{
case 0: icon = ":/icons/connect_0"; break;
case 1: case 2: case 3: icon = ":/icons/connect_1"; break;
case 4: case 5: case 6: icon = ":/icons/connect_2"; break;
case 7: case 8: case 9: icon = ":/icons/connect_3"; break;
default: icon = ":/icons/connect_4"; break;
}
labelConnectionsIcon->setPixmap(QIcon(icon).pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count));
}
void BitcoinGUI::setNumBlocks(int count)
{
int initTotal = clientModel->getNumBlocksAtStartup();
int total = clientModel->getNumBlocksOfPeers();
QString tooltip;
if(count < total)
{
progressBarLabel->setVisible(true);
progressBar->setVisible(true);
progressBar->setMaximum(total - initTotal);
progressBar->setValue(count - initTotal);
tooltip = tr("Downloaded %1 of %2 blocks of transaction history.").arg(count).arg(total);
}
else
{
progressBarLabel->setVisible(false);
progressBar->setVisible(false);
tooltip = tr("Downloaded %1 blocks of transaction history.").arg(count);
}
QDateTime now = QDateTime::currentDateTime();
QDateTime lastBlockDate = clientModel->getLastBlockDate();
int secs = lastBlockDate.secsTo(now);
QString text;
// Represent time from last generated block in human readable text
if(secs < 60)
{
text = tr("%n second(s) ago","",secs);
}
else if(secs < 60*60)
{
text = tr("%n minute(s) ago","",secs/60);
}
else if(secs < 24*60*60)
{
text = tr("%n hour(s) ago","",secs/(60*60));
}
else
{
text = tr("%n day(s) ago","",secs/(60*60*24));
}
// Set icon state: spinning if catching up, tick otherwise
if(secs < 30*60)
{
tooltip = tr("Up to date") + QString("\n") + tooltip;
labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
}
else
{
tooltip = tr("Catching up...") + QString("\n") + tooltip;
labelBlocksIcon->setMovie(syncIconMovie);
syncIconMovie->start();
}
tooltip += QString("\n");
tooltip += tr("Last received block was generated %1.").arg(text);
labelBlocksIcon->setToolTip(tooltip);
progressBarLabel->setToolTip(tooltip);
progressBar->setToolTip(tooltip);
}
void BitcoinGUI::error(const QString &title, const QString &message)
{
// Report errors from network/worker thread
notificator->notify(Notificator::Critical, title, message);
}
void BitcoinGUI::changeEvent(QEvent *e)
{
if (e->type() == QEvent::WindowStateChange)
{
if(clientModel->getOptionsModel()->getMinimizeToTray())
{
if (isMinimized())
{
hide();
e->ignore();
}
else
{
show();
e->accept();
}
}
}
QMainWindow::changeEvent(e);
}
void BitcoinGUI::closeEvent(QCloseEvent *event)
{
if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
!clientModel->getOptionsModel()->getMinimizeOnClose())
{
qApp->quit();
}
QMainWindow::closeEvent(event);
}
void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
{
QString strMessage =
tr("This transaction is over the size limit. You can still send it for a fee of %1, "
"which goes to the nodes that process your transaction and helps to support the network. "
"Do you want to pay the fee?").arg(
BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nFeeRequired));
QMessageBox::StandardButton retval = QMessageBox::question(
this, tr("Sending..."), strMessage,
QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes);
*payFee = (retval == QMessageBox::Yes);
}
void BitcoinGUI::incomingTransaction(const QModelIndex & parent, int start, int end)
{
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
.data(Qt::EditRole).toULongLong();
if(!clientModel->inInitialBlockDownload())
{
// On new transaction, make an info balloon
// Unless the initial block download is in progress, to prevent balloon-spam
QString date = ttm->index(start, TransactionTableModel::Date, parent)
.data().toString();
QString type = ttm->index(start, TransactionTableModel::Type, parent)
.data().toString();
QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
.data().toString();
QIcon icon = qvariant_cast<QIcon>(ttm->index(start,
TransactionTableModel::ToAddress, parent)
.data(Qt::DecorationRole));
notificator->notify(Notificator::Information,
(amount)<0 ? tr("Sent transaction") :
tr("Incoming transaction"),
tr("Date: %1\n"
"Amount: %2\n"
"Type: %3\n"
"Address: %4\n")
.arg(date)
.arg(BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), amount, true))
.arg(type)
.arg(address), icon);
}
}
void BitcoinGUI::gotoOverviewPage()
{
overviewAction->setChecked(true);
centralWidget->setCurrentWidget(overviewPage);
exportAction->setEnabled(false);
disconnect(exportAction, SIGNAL(triggered()), 0, 0);
}
void BitcoinGUI::gotoHistoryPage()
{
historyAction->setChecked(true);
centralWidget->setCurrentWidget(transactionsPage);
exportAction->setEnabled(true);
disconnect(exportAction, SIGNAL(triggered()), 0, 0);
connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
}
void BitcoinGUI::gotoAddressBookPage()
{
addressBookAction->setChecked(true);
centralWidget->setCurrentWidget(addressBookPage);
exportAction->setEnabled(true);
disconnect(exportAction, SIGNAL(triggered()), 0, 0);
connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
}
void BitcoinGUI::gotoReceiveCoinsPage()
{
receiveCoinsAction->setChecked(true);
centralWidget->setCurrentWidget(receiveCoinsPage);
exportAction->setEnabled(true);
disconnect(exportAction, SIGNAL(triggered()), 0, 0);
connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
}
void BitcoinGUI::gotoSendCoinsPage()
{
sendCoinsAction->setChecked(true);
if(centralWidget->currentWidget() != sendCoinsPage)
{
// Clear the current contents if we arrived from another tab
sendCoinsPage->clear();
}
centralWidget->setCurrentWidget(sendCoinsPage);
exportAction->setEnabled(false);
disconnect(exportAction, SIGNAL(triggered()), 0, 0);
}
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
{
// Accept only URLs
if(event->mimeData()->hasUrls())
event->acceptProposedAction();
}
void BitcoinGUI::dropEvent(QDropEvent *event)
{
if(event->mimeData()->hasUrls())
{
gotoSendCoinsPage();
QList<QUrl> urls = event->mimeData()->urls();
foreach(const QUrl &url, urls)
{
sendCoinsPage->handleURL(&url);
}
}
event->acceptProposedAction();
}
void BitcoinGUI::setEncryptionStatus(int status)
{
switch(status)
{
case WalletModel::Unencrypted:
labelEncryptionIcon->hide();
encryptWalletAction->setChecked(false);
changePassphraseAction->setEnabled(false);
encryptWalletAction->setEnabled(true);
break;
case WalletModel::Unlocked:
labelEncryptionIcon->show();
labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_open").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
encryptWalletAction->setChecked(true);
changePassphraseAction->setEnabled(true);
encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
break;
case WalletModel::Locked:
labelEncryptionIcon->show();
labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_closed").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
encryptWalletAction->setChecked(true);
changePassphraseAction->setEnabled(true);
encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
break;
}
}
void BitcoinGUI::encryptWallet(bool status)
{
AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
AskPassphraseDialog::Decrypt, this);
dlg.setModel(walletModel);
dlg.exec();
setEncryptionStatus(walletModel->getEncryptionStatus());
}
void BitcoinGUI::changePassphrase()
{
AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
dlg.setModel(walletModel);
dlg.exec();
}
void BitcoinGUI::unlockWallet()
{
// Unlock wallet when requested by wallet model
if(walletModel->getEncryptionStatus() == WalletModel::Locked)
{
AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
dlg.setModel(walletModel);
dlg.exec();
}
}

120
src/qt/bitcoingui.h Normal file
View File

@@ -0,0 +1,120 @@
#ifndef BITCOINGUI_H
#define BITCOINGUI_H
#include <QMainWindow>
#include <QSystemTrayIcon>
class TransactionTableModel;
class ClientModel;
class WalletModel;
class TransactionView;
class OverviewPage;
class AddressBookPage;
class SendCoinsDialog;
class Notificator;
QT_BEGIN_NAMESPACE
class QLabel;
class QLineEdit;
class QTableView;
class QAbstractItemModel;
class QModelIndex;
class QProgressBar;
class QStackedWidget;
class QUrl;
QT_END_NAMESPACE
class BitcoinGUI : public QMainWindow
{
Q_OBJECT
public:
explicit BitcoinGUI(QWidget *parent = 0);
void setClientModel(ClientModel *clientModel);
void setWalletModel(WalletModel *walletModel);
/* Transaction table tab indices */
enum {
AllTransactions = 0,
SentReceived = 1,
Sent = 2,
Received = 3
} TabIndex;
protected:
void changeEvent(QEvent *e);
void closeEvent(QCloseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
private:
ClientModel *clientModel;
WalletModel *walletModel;
QStackedWidget *centralWidget;
OverviewPage *overviewPage;
QWidget *transactionsPage;
AddressBookPage *addressBookPage;
AddressBookPage *receiveCoinsPage;
SendCoinsDialog *sendCoinsPage;
QLabel *labelEncryptionIcon;
QLabel *labelConnectionsIcon;
QLabel *labelBlocksIcon;
QLabel *progressBarLabel;
QProgressBar *progressBar;
QAction *overviewAction;
QAction *historyAction;
QAction *quitAction;
QAction *sendCoinsAction;
QAction *addressBookAction;
QAction *aboutAction;
QAction *receiveCoinsAction;
QAction *optionsAction;
QAction *openBitcoinAction;
QAction *exportAction;
QAction *encryptWalletAction;
QAction *changePassphraseAction;
QSystemTrayIcon *trayIcon;
Notificator *notificator;
TransactionView *transactionView;
QMovie *syncIconMovie;
void createActions();
QWidget *createTabs();
void createTrayIcon();
public slots:
void setNumConnections(int count);
void setNumBlocks(int count);
void setEncryptionStatus(int status);
void error(const QString &title, const QString &message);
/* It is currently not possible to pass a return value to another thread through
BlockingQueuedConnection, so use an indirected pointer.
http://bugreports.qt.nokia.com/browse/QTBUG-10440
*/
void askFee(qint64 nFeeRequired, bool *payFee);
private slots:
// UI pages
void gotoOverviewPage();
void gotoHistoryPage();
void gotoAddressBookPage();
void gotoReceiveCoinsPage();
void gotoSendCoinsPage();
// Misc actions
void optionsClicked();
void aboutClicked();
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
void incomingTransaction(const QModelIndex & parent, int start, int end);
void encryptWallet(bool status);
void changePassphrase();
void unlockWallet();
};
#endif

217
src/qt/bitcoinstrings.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include <QtGlobal>
// Automatically generated by extract_strings.py
static const char *bitcoin_strings[] = {QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin version"),
QT_TRANSLATE_NOOP("bitcoin-core", "Usage:"),
QT_TRANSLATE_NOOP("bitcoin-core", "Send command to -server or bitcoind\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "List commands\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Get help for a command\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Options:\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Specify configuration file (default: bitcoin.conf)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Specify pid file (default: bitcoind.pid)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Generate coins\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Don't generate coins\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Start minimized\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Specify data directory\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Specify connection timeout (in milliseconds)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Connect through socks4 proxy\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Allow DNS lookups for addnode and connect\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Add a node to connect to\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Connect only to the specified node\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Don't accept connections from outside\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Don't attempt to use UPnP to map the listening port\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Attempt to use UPnP to map the listening port\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Fee per KB to add to transactions you send\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Accept command line and JSON-RPC commands\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Run in the background as a daemon and accept commands\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Use the test network\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Username for JSON-RPC connections\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Password for JSON-RPC connections\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Listen for JSON-RPC connections on <port> (default: 8332)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Allow JSON-RPC connections from specified IP address\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Send commands to node running on <ip> (default: 127.0.0.1)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Set key pool size to <n> (default: 100)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Rescan the block chain for missing wallet transactions\n"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"\n"
"SSL options: (see the Bitcoin Wiki for SSL setup instructions)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Use OpenSSL (https) for JSON-RPC connections\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Server certificate file (default: server.cert)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Server private key (default: server.pem)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:"
"@STRENGTH)\n"),
QT_TRANSLATE_NOOP("bitcoin-core", "This help message\n"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Cannot obtain a lock on data directory %s. Bitcoin is probably already "
"running."),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading addresses..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error loading addr.dat \n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error loading blkindex.dat \n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading wallet..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat: Wallet corrupted \n"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error loading wallet.dat: Wallet requires newer version of Bitcoin \n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat \n"),
QT_TRANSLATE_NOOP("bitcoin-core", "Rescanning..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Done loading"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=<amount>"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Warning: -paytxfee is set very high. This is the transaction fee you will "
"pay if you send a transaction."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: CreateThread(StartNode) failed"),
QT_TRANSLATE_NOOP("bitcoin-core", "Warning: Disk space is low "),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Unable to bind to port %d on this computer. Bitcoin is probably already "
"running."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"This transaction is over the size limit. You can still send it for a fee of "
"%s, which goes to the nodes that process your transaction and helps to "
"support the network. Do you want to pay the fee?"),
QT_TRANSLATE_NOOP("bitcoin-core", "Enter the current passphrase to the wallet."),
QT_TRANSLATE_NOOP("bitcoin-core", "Passphrase"),
QT_TRANSLATE_NOOP("bitcoin-core", "Please supply the current wallet decryption passphrase."),
QT_TRANSLATE_NOOP("bitcoin-core", "The passphrase entered for the wallet decryption was incorrect."),
QT_TRANSLATE_NOOP("bitcoin-core", "Status"),
QT_TRANSLATE_NOOP("bitcoin-core", "Date"),
QT_TRANSLATE_NOOP("bitcoin-core", "Description"),
QT_TRANSLATE_NOOP("bitcoin-core", "Debit"),
QT_TRANSLATE_NOOP("bitcoin-core", "Credit"),
QT_TRANSLATE_NOOP("bitcoin-core", "Open for %d blocks"),
QT_TRANSLATE_NOOP("bitcoin-core", "Open until %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "%d/offline?"),
QT_TRANSLATE_NOOP("bitcoin-core", "%d/unconfirmed"),
QT_TRANSLATE_NOOP("bitcoin-core", "%d confirmations"),
QT_TRANSLATE_NOOP("bitcoin-core", "Generated"),
QT_TRANSLATE_NOOP("bitcoin-core", "Generated (%s matures in %d more blocks)"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Generated - Warning: This block was not received by any other nodes and will "
"probably not be accepted!"),
QT_TRANSLATE_NOOP("bitcoin-core", "Generated (not accepted)"),
QT_TRANSLATE_NOOP("bitcoin-core", "From: "),
QT_TRANSLATE_NOOP("bitcoin-core", "Received with: "),
QT_TRANSLATE_NOOP("bitcoin-core", "Payment to yourself"),
QT_TRANSLATE_NOOP("bitcoin-core", "To: "),
QT_TRANSLATE_NOOP("bitcoin-core", " Generating"),
QT_TRANSLATE_NOOP("bitcoin-core", "(not connected)"),
QT_TRANSLATE_NOOP("bitcoin-core", " %d connections %d blocks %d transactions"),
QT_TRANSLATE_NOOP("bitcoin-core", "Wallet already encrypted."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Enter the new passphrase to the wallet.\n"
"Please use a passphrase of 10 or more random characters, or eight or more "
"words."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: The supplied passphrase was too short."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"WARNING: If you encrypt your wallet and lose your passphrase, you will LOSE "
"ALL OF YOUR BITCOINS!\n"
"Are you sure you wish to encrypt your wallet?"),
QT_TRANSLATE_NOOP("bitcoin-core", "Please re-enter your new wallet passphrase."),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: the supplied passphrases didn't match."),
QT_TRANSLATE_NOOP("bitcoin-core", "Wallet encryption failed."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Wallet Encrypted.\n"
"Remember that encrypting your wallet cannot fully protect your bitcoins from "
"being stolen by malware infecting your computer."),
QT_TRANSLATE_NOOP("bitcoin-core", "Wallet is unencrypted, please encrypt it first."),
QT_TRANSLATE_NOOP("bitcoin-core", "Enter the new passphrase for the wallet."),
QT_TRANSLATE_NOOP("bitcoin-core", "Re-enter the new passphrase for the wallet."),
QT_TRANSLATE_NOOP("bitcoin-core", "Wallet Passphrase Changed."),
QT_TRANSLATE_NOOP("bitcoin-core", "New Receiving Address"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"You should use a new address for each payment you receive.\n"
"\n"
"Label"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Status:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", ", has not been successfully broadcast yet"),
QT_TRANSLATE_NOOP("bitcoin-core", ", broadcast through %d node"),
QT_TRANSLATE_NOOP("bitcoin-core", ", broadcast through %d nodes"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Date:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Source:</b> Generated<br>"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>From:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "unknown"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>To:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", " (yours, label: "),
QT_TRANSLATE_NOOP("bitcoin-core", " (yours)"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Credit:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "(%s matures in %d more blocks)"),
QT_TRANSLATE_NOOP("bitcoin-core", "(not accepted)"),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Debit:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Transaction fee:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "<b>Net amount:</b> "),
QT_TRANSLATE_NOOP("bitcoin-core", "Message:"),
QT_TRANSLATE_NOOP("bitcoin-core", "Comment:"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Generated coins must wait 120 blocks before they can be spent. When you "
"generated this block, it was broadcast to the network to be added to the "
"block chain. If it fails to get into the chain, it will change to \"not "
"accepted\" and not be spendable. This may occasionally happen if another "
"node generates a block within a few seconds of yours."),
QT_TRANSLATE_NOOP("bitcoin-core", "Cannot write autostart/bitcoin.desktop file"),
QT_TRANSLATE_NOOP("bitcoin-core", "Main"),
QT_TRANSLATE_NOOP("bitcoin-core", "&Start Bitcoin on window system startup"),
QT_TRANSLATE_NOOP("bitcoin-core", "&Minimize on close"),
QT_TRANSLATE_NOOP("bitcoin-core", "version %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error in amount "),
QT_TRANSLATE_NOOP("bitcoin-core", "Send Coins"),
QT_TRANSLATE_NOOP("bitcoin-core", "Amount exceeds your balance "),
QT_TRANSLATE_NOOP("bitcoin-core", "Total exceeds your balance when the "),
QT_TRANSLATE_NOOP("bitcoin-core", " transaction fee is included "),
QT_TRANSLATE_NOOP("bitcoin-core", "Payment sent "),
QT_TRANSLATE_NOOP("bitcoin-core", "Sending..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid address "),
QT_TRANSLATE_NOOP("bitcoin-core", "Sending %s to %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "CANCELLED"),
QT_TRANSLATE_NOOP("bitcoin-core", "Cancelled"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transfer cancelled "),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: "),
QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"),
QT_TRANSLATE_NOOP("bitcoin-core", "Connecting..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to connect"),
QT_TRANSLATE_NOOP("bitcoin-core", "Requesting public key..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Received public key..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Recipient is not accepting transactions sent by IP address"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transfer was not accepted"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid response received"),
QT_TRANSLATE_NOOP("bitcoin-core", "Creating transaction..."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"This transaction requires a transaction fee of at least %s because of its "
"amount, complexity, or use of recently received funds"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction creation failed"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction aborted"),
QT_TRANSLATE_NOOP("bitcoin-core", "Lost connection, transaction cancelled"),
QT_TRANSLATE_NOOP("bitcoin-core", "Sending payment..."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"The transaction was rejected. This might happen if some of the coins in "
"your wallet were already spent, such as if you used a copy of wallet.dat and "
"coins were spent in the copy but not marked as spent here."),
QT_TRANSLATE_NOOP("bitcoin-core", "Waiting for confirmation..."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"The payment was sent, but the recipient was unable to verify it.\n"
"The transaction is recorded and will credit to the recipient,\n"
"but the comment information will be blank."),
QT_TRANSLATE_NOOP("bitcoin-core", "Payment was sent, but an invalid response was received"),
QT_TRANSLATE_NOOP("bitcoin-core", "Payment completed"),
QT_TRANSLATE_NOOP("bitcoin-core", "Name"),
QT_TRANSLATE_NOOP("bitcoin-core", "Address"),
QT_TRANSLATE_NOOP("bitcoin-core", "Label"),
QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin Address"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"This is one of your own addresses for receiving payments and cannot be "
"entered in the address book. "),
QT_TRANSLATE_NOOP("bitcoin-core", "Edit Address"),
QT_TRANSLATE_NOOP("bitcoin-core", "Edit Address Label"),
QT_TRANSLATE_NOOP("bitcoin-core", "Add Address"),
QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin"),
QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin - Generating"),
QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin - (not connected)"),
QT_TRANSLATE_NOOP("bitcoin-core", "&Open Bitcoin"),
QT_TRANSLATE_NOOP("bitcoin-core", "&Send Bitcoins"),
QT_TRANSLATE_NOOP("bitcoin-core", "O&ptions..."),
QT_TRANSLATE_NOOP("bitcoin-core", "E&xit"),
QT_TRANSLATE_NOOP("bitcoin-core", "Program has crashed and will terminate. "),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Warning: Please check that your computer's date and time are correct. If "
"your clock is wrong Bitcoin will not work properly."),
QT_TRANSLATE_NOOP("bitcoin-core", "beta"),
};

181
src/qt/bitcoinunits.cpp Normal file
View File

@@ -0,0 +1,181 @@
#include "bitcoinunits.h"
#include <QStringList>
BitcoinUnits::BitcoinUnits(QObject *parent):
QAbstractListModel(parent),
unitlist(availableUnits())
{
}
QList<BitcoinUnits::Unit> BitcoinUnits::availableUnits()
{
QList<BitcoinUnits::Unit> unitlist;
unitlist.append(BTC);
unitlist.append(mBTC);
unitlist.append(uBTC);
return unitlist;
}
bool BitcoinUnits::valid(int unit)
{
switch(unit)
{
case BTC:
case mBTC:
case uBTC:
return true;
default:
return false;
}
}
QString BitcoinUnits::name(int unit)
{
switch(unit)
{
case BTC: return QString("BTC");
case mBTC: return QString("mBTC");
case uBTC: return QString::fromUtf8("μBTC");
default: return QString("???");
}
}
QString BitcoinUnits::description(int unit)
{
switch(unit)
{
case BTC: return QString("Bitcoins");
case mBTC: return QString("Milli-Bitcoins (1 / 1,000)");
case uBTC: return QString("Micro-Bitcoins (1 / 1,000,000)");
default: return QString("???");
}
}
qint64 BitcoinUnits::factor(int unit)
{
switch(unit)
{
case BTC: return 100000000;
case mBTC: return 100000;
case uBTC: return 100;
default: return 100000000;
}
}
int BitcoinUnits::amountDigits(int unit)
{
switch(unit)
{
case BTC: return 8; // 21,000,000 (# digits, without commas)
case mBTC: return 11; // 21,000,000,000
case uBTC: return 14; // 21,000,000,000,000
default: return 0;
}
}
int BitcoinUnits::decimals(int unit)
{
switch(unit)
{
case BTC: return 8;
case mBTC: return 5;
case uBTC: return 2;
default: return 0;
}
}
QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
{
// Note: not using straight sprintf here because we do NOT want
// localized number formatting.
if(!valid(unit))
return QString(); // Refuse to format invalid unit
qint64 coin = factor(unit);
int num_decimals = decimals(unit);
qint64 n_abs = (n > 0 ? n : -n);
qint64 quotient = n_abs / coin;
qint64 remainder = n_abs % coin;
QString quotient_str = QString::number(quotient);
QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
// Right-trim excess 0's after the decimal point
int nTrim = 0;
for (int i = remainder_str.size()-1; i>=2 && (remainder_str.at(i) == '0'); --i)
++nTrim;
remainder_str.chop(nTrim);
if (n < 0)
quotient_str.insert(0, '-');
else if (fPlus && n > 0)
quotient_str.insert(0, '+');
return quotient_str + QString(".") + remainder_str;
}
QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign)
{
return format(unit, amount, plussign) + QString(" ") + name(unit);
}
bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out)
{
if(!valid(unit) || value.isEmpty())
return false; // Refuse to parse invalid unit or empty string
int num_decimals = decimals(unit);
QStringList parts = value.split(".");
if(parts.size() > 2)
{
return false; // More than one dot
}
QString whole = parts[0];
QString decimals;
if(parts.size() > 1)
{
decimals = parts[1];
}
if(decimals.size() > num_decimals)
{
return false; // Exceeds max precision
}
bool ok = false;
QString str = whole + decimals.leftJustified(num_decimals, '0');
if(str.size() > 18)
{
return false; // Longer numbers will exceed 63 bits
}
qint64 retvalue = str.toLongLong(&ok);
if(val_out)
{
*val_out = retvalue;
}
return ok;
}
int BitcoinUnits::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return unitlist.size();
}
QVariant BitcoinUnits::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row >= 0 && row < unitlist.size())
{
Unit unit = unitlist.at(row);
switch(role)
{
case Qt::EditRole:
case Qt::DisplayRole:
return QVariant(name(unit));
case Qt::ToolTipRole:
return QVariant(description(unit));
case UnitRole:
return QVariant(static_cast<int>(unit));
}
}
return QVariant();
}

57
src/qt/bitcoinunits.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef BITCOINUNITS_H
#define BITCOINUNITS_H
#include <QString>
#include <QAbstractListModel>
// Bitcoin unit definitions, encapsulates parsing and formatting
// and serves as list model for dropdown selection boxes.
class BitcoinUnits: public QAbstractListModel
{
public:
explicit BitcoinUnits(QObject *parent);
enum Unit
{
// Source: https://en.bitcoin.it/wiki/Units
// Please add only sensible ones
BTC,
mBTC,
uBTC
};
/// Static API
// Get list of units, for dropdown box
static QList<Unit> availableUnits();
// Is unit ID valid?
static bool valid(int unit);
// Short name
static QString name(int unit);
// Longer description
static QString description(int unit);
// Number of satoshis / unit
static qint64 factor(int unit);
// Number of amount digits (to represent max number of coins)
static int amountDigits(int unit);
// Number of decimals left
static int decimals(int unit);
// Format as string
static QString format(int unit, qint64 amount, bool plussign=false);
// Format as string (with unit)
static QString formatWithUnit(int unit, qint64 amount, bool plussign=false);
// Parse string to coin amount
static bool parse(int unit, const QString &value, qint64 *val_out);
/// AbstractListModel implementation
enum {
// Unit identifier
UnitRole = Qt::UserRole
} RoleIndex;
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QList<BitcoinUnits::Unit> unitlist;
};
typedef BitcoinUnits::Unit BitcoinUnit;
#endif // BITCOINUNITS_H

83
src/qt/clientmodel.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "clientmodel.h"
#include "guiconstants.h"
#include "optionsmodel.h"
#include "addresstablemodel.h"
#include "transactiontablemodel.h"
#include "headers.h"
#include <QTimer>
#include <QDateTime>
ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) :
QObject(parent), optionsModel(optionsModel),
cachedNumConnections(0), cachedNumBlocks(0)
{
// Until signal notifications is built into the bitcoin core,
// simply update everything after polling using a timer.
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(MODEL_UPDATE_DELAY);
numBlocksAtStartup = -1;
}
int ClientModel::getNumConnections() const
{
return vNodes.size();
}
int ClientModel::getNumBlocks() const
{
return nBestHeight;
}
int ClientModel::getNumBlocksAtStartup()
{
if (numBlocksAtStartup == -1) numBlocksAtStartup = getNumBlocks();
return numBlocksAtStartup;
}
QDateTime ClientModel::getLastBlockDate() const
{
return QDateTime::fromTime_t(pindexBest->GetBlockTime());
}
void ClientModel::update()
{
int newNumConnections = getNumConnections();
int newNumBlocks = getNumBlocks();
if(cachedNumConnections != newNumConnections)
emit numConnectionsChanged(newNumConnections);
if(cachedNumBlocks != newNumBlocks)
emit numBlocksChanged(newNumBlocks);
cachedNumConnections = newNumConnections;
cachedNumBlocks = newNumBlocks;
}
bool ClientModel::isTestNet() const
{
return fTestNet;
}
bool ClientModel::inInitialBlockDownload() const
{
return IsInitialBlockDownload();
}
int ClientModel::getNumBlocksOfPeers() const
{
return GetNumBlocksOfPeers();
}
OptionsModel *ClientModel::getOptionsModel()
{
return optionsModel;
}
QString ClientModel::formatFullVersion() const
{
return QString::fromStdString(FormatFullVersion());
}

60
src/qt/clientmodel.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef CLIENTMODEL_H
#define CLIENTMODEL_H
#include <QObject>
class OptionsModel;
class AddressTableModel;
class TransactionTableModel;
class CWallet;
QT_BEGIN_NAMESPACE
class QDateTime;
QT_END_NAMESPACE
// Model for Bitcoin network client
class ClientModel : public QObject
{
Q_OBJECT
public:
explicit ClientModel(OptionsModel *optionsModel, QObject *parent = 0);
OptionsModel *getOptionsModel();
int getNumConnections() const;
int getNumBlocks() const;
int getNumBlocksAtStartup();
QDateTime getLastBlockDate() const;
// Return true if client connected to testnet
bool isTestNet() const;
// Return true if core is doing initial block download
bool inInitialBlockDownload() const;
// Return conservative estimate of total number of blocks, or 0 if unknown
int getNumBlocksOfPeers() const;
QString formatFullVersion() const;
private:
OptionsModel *optionsModel;
int cachedNumConnections;
int cachedNumBlocks;
int numBlocksAtStartup;
signals:
void numConnectionsChanged(int count);
void numBlocksChanged(int count);
// Asynchronous error notification
void error(const QString &title, const QString &message);
public slots:
private slots:
void update();
};
#endif // CLIENTMODEL_H

83
src/qt/csvmodelwriter.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "csvmodelwriter.h"
#include <QAbstractItemModel>
#include <QFile>
#include <QTextStream>
CSVModelWriter::CSVModelWriter(const QString &filename, QObject *parent) :
QObject(parent),
filename(filename)
{
}
void CSVModelWriter::setModel(const QAbstractItemModel *model)
{
this->model = model;
}
void CSVModelWriter::addColumn(const QString &title, int column, int role)
{
Column col;
col.title = title;
col.column = column;
col.role = role;
columns.append(col);
}
static void writeValue(QTextStream &f, const QString &value)
{
// TODO: quoting if " or \n in string
f << "\"" << value << "\"";
}
static void writeSep(QTextStream &f)
{
f << ",";
}
static void writeNewline(QTextStream &f)
{
f << "\n";
}
bool CSVModelWriter::write()
{
QFile file(filename);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QTextStream out(&file);
int numRows = model->rowCount();
// Header row
for(int i=0; i<columns.size(); ++i)
{
if(i!=0)
{
writeSep(out);
}
writeValue(out, columns[i].title);
}
writeNewline(out);
// Data rows
for(int j=0; j<numRows; ++j)
{
for(int i=0; i<columns.size(); ++i)
{
if(i!=0)
{
writeSep(out);
}
QVariant data = model->index(j, columns[i].column).data(columns[i].role);
writeValue(out, data.toString());
}
writeNewline(out);
}
file.close();
return file.error() == QFile::NoError;
}

43
src/qt/csvmodelwriter.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef CSVMODELWRITER_H
#define CSVMODELWRITER_H
#include <QObject>
#include <QList>
QT_BEGIN_NAMESPACE
class QAbstractItemModel;
QT_END_NAMESPACE
// Export TableModel to CSV file
class CSVModelWriter : public QObject
{
Q_OBJECT
public:
explicit CSVModelWriter(const QString &filename, QObject *parent = 0);
void setModel(const QAbstractItemModel *model);
void addColumn(const QString &title, int column, int role=Qt::EditRole);
// Perform write operation
// Returns true on success, false otherwise
bool write();
private:
QString filename;
const QAbstractItemModel *model;
struct Column
{
QString title;
int column;
int role;
};
QList<Column> columns;
signals:
public slots:
};
#endif // CSVMODELWRITER_H

View File

@@ -0,0 +1,115 @@
#include "editaddressdialog.h"
#include "ui_editaddressdialog.h"
#include "addresstablemodel.h"
#include "guiutil.h"
#include <QDataWidgetMapper>
#include <QMessageBox>
EditAddressDialog::EditAddressDialog(Mode mode, QWidget *parent) :
QDialog(parent),
ui(new Ui::EditAddressDialog), mapper(0), mode(mode), model(0)
{
ui->setupUi(this);
GUIUtil::setupAddressWidget(ui->addressEdit, this);
switch(mode)
{
case NewReceivingAddress:
setWindowTitle(tr("New receiving address"));
ui->addressEdit->setEnabled(false);
break;
case NewSendingAddress:
setWindowTitle(tr("New sending address"));
break;
case EditReceivingAddress:
setWindowTitle(tr("Edit receiving address"));
ui->addressEdit->setDisabled(true);
break;
case EditSendingAddress:
setWindowTitle(tr("Edit sending address"));
break;
}
mapper = new QDataWidgetMapper(this);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
}
EditAddressDialog::~EditAddressDialog()
{
delete ui;
}
void EditAddressDialog::setModel(AddressTableModel *model)
{
this->model = model;
mapper->setModel(model);
mapper->addMapping(ui->labelEdit, AddressTableModel::Label);
mapper->addMapping(ui->addressEdit, AddressTableModel::Address);
}
void EditAddressDialog::loadRow(int row)
{
mapper->setCurrentIndex(row);
}
bool EditAddressDialog::saveCurrentRow()
{
switch(mode)
{
case NewReceivingAddress:
case NewSendingAddress:
address = model->addRow(
mode == NewSendingAddress ? AddressTableModel::Send : AddressTableModel::Receive,
ui->labelEdit->text(),
ui->addressEdit->text());
break;
case EditReceivingAddress:
case EditSendingAddress:
if(mapper->submit())
{
address = ui->addressEdit->text();
}
break;
}
return !address.isEmpty();
}
void EditAddressDialog::accept()
{
if(!saveCurrentRow())
{
switch(model->getEditStatus())
{
case AddressTableModel::DUPLICATE_ADDRESS:
QMessageBox::warning(this, windowTitle(),
tr("The entered address \"%1\" is already in the address book.").arg(ui->addressEdit->text()),
QMessageBox::Ok, QMessageBox::Ok);
break;
case AddressTableModel::INVALID_ADDRESS:
QMessageBox::warning(this, windowTitle(),
tr("The entered address \"%1\" is not a valid bitcoin address.").arg(ui->addressEdit->text()),
QMessageBox::Ok, QMessageBox::Ok);
return;
case AddressTableModel::WALLET_UNLOCK_FAILURE:
QMessageBox::critical(this, windowTitle(),
tr("Could not unlock wallet."),
QMessageBox::Ok, QMessageBox::Ok);
return;
case AddressTableModel::KEY_GENERATION_FAILURE:
QMessageBox::critical(this, windowTitle(),
tr("New key generation failed."),
QMessageBox::Ok, QMessageBox::Ok);
return;
}
return;
}
QDialog::accept();
}
QString EditAddressDialog::getAddress() const
{
return address;
}

View File

@@ -0,0 +1,47 @@
#ifndef EDITADDRESSDIALOG_H
#define EDITADDRESSDIALOG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
class QDataWidgetMapper;
QT_END_NAMESPACE
namespace Ui {
class EditAddressDialog;
}
class AddressTableModel;
class EditAddressDialog : public QDialog
{
Q_OBJECT
public:
enum Mode {
NewReceivingAddress,
NewSendingAddress,
EditReceivingAddress,
EditSendingAddress
};
explicit EditAddressDialog(Mode mode, QWidget *parent = 0);
~EditAddressDialog();
void setModel(AddressTableModel *model);
void loadRow(int row);
void accept();
QString getAddress() const;
private:
bool saveCurrentRow();
Ui::EditAddressDialog *ui;
QDataWidgetMapper *mapper;
Mode mode;
AddressTableModel *model;
QString address;
};
#endif // EDITADDRESSDIALOG_H

162
src/qt/forms/aboutdialog.ui Normal file
View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>319</height>
</rect>
</property>
<property name="windowTitle">
<string>About Bitcoin</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../bitcoin.qrc">:/images/about</pixmap>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;b&gt;Bitcoin&lt;/b&gt; version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string notr="true">0.3.666-beta</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Copyright © 2009-2011 Bitcoin Developers
This is experimental software.
Distributed under the MIT/X11 software license, see the accompanying file license.txt or http://www.opensource.org/licenses/mit-license.php.
This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../bitcoin.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>360</x>
<y>308</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>428</x>
<y>308</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddressBookPage</class>
<widget class="QWidget" name="AddressBookPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>627</width>
<height>347</height>
</rect>
</property>
<property name="windowTitle">
<string>Address Book</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelExplanation">
<property name="text">
<string>These are your Bitcoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you.</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView">
<property name="toolTip">
<string>Double-click to edit address or label</string>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="newAddressButton">
<property name="toolTip">
<string>Create a new address</string>
</property>
<property name="text">
<string>&amp;New Address...</string>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/add</normaloff>:/icons/add</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copyToClipboard">
<property name="toolTip">
<string>Copy the currently selected address to the system clipboard</string>
</property>
<property name="text">
<string>&amp;Copy to Clipboard</string>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/editcopy</normaloff>:/icons/editcopy</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="toolTip">
<string>Delete the currently selected address from the list. Only sending addresses can be deleted.</string>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../bitcoin.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AskPassphraseDialog</class>
<widget class="QDialog" name="AskPassphraseDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>228</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>550</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="warningLabel">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="passLabel1">
<property name="text">
<string>Enter passphrase</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="passEdit1">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="passLabel2">
<property name="text">
<string>New passphrase</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="passEdit2">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="passLabel3">
<property name="text">
<string>Repeat new passphrase</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="passEdit3">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AskPassphraseDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AskPassphraseDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditAddressDialog</class>
<widget class="QDialog" name="EditAddressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>457</width>
<height>126</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Address</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Label</string>
</property>
<property name="buddy">
<cstring>labelEdit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="labelEdit">
<property name="toolTip">
<string>The label associated with this address book entry</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Address</string>
</property>
<property name="buddy">
<cstring>addressEdit</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="addressEdit">
<property name="toolTip">
<string>The address associated with this address book entry. This can only be modified for sending addresses.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditAddressDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditAddressDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OverviewPage</class>
<widget class="QWidget" name="OverviewPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>552</width>
<height>342</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>12</number>
</property>
<property name="verticalSpacing">
<number>12</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Balance:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelBalance">
<property name="text">
<string>123.456 BTC</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Number of transactions:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="labelNumTransactions">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Unconfirmed:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="labelUnconfirmed">
<property name="text">
<string>0 BTC</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Wallet&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;b&gt;Recent transactions&lt;/b&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="listTransactions">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SendCoinsDialog</class>
<widget class="QDialog" name="SendCoinsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>686</width>
<height>217</height>
</rect>
</property>
<property name="windowTitle">
<string>Send Coins</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>666</width>
<height>162</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="entries">
<property name="spacing">
<number>6</number>
</property>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QPushButton" name="addButton">
<property name="toolTip">
<string>Send to multiple recipients at once</string>
</property>
<property name="text">
<string>&amp;Add recipient...</string>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/add</normaloff>:/icons/add</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="sendButton">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Confirm the send action</string>
</property>
<property name="text">
<string>&amp;Send</string>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/send</normaloff>:/icons/send</iconset>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../bitcoin.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SendCoinsEntry</class>
<widget class="QFrame" name="SendCoinsEntry">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>729</width>
<height>136</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>12</number>
</property>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>A&amp;mount:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payAmount</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Pay &amp;To:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payTo</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="BitcoinAmountField" name="payAmount"/>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QValidatedLineEdit" name="addAsLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Enter a label for this address to add it to your address book</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Label:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>addAsLabel</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QValidatedLineEdit" name="payTo">
<property name="toolTip">
<string>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</string>
</property>
<property name="maxLength">
<number>34</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addressBookButton">
<property name="toolTip">
<string>Choose adress from address book</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteButton">
<property name="toolTip">
<string>Paste address from clipboard</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset>
</property>
<property name="shortcut">
<string>Alt+P</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="toolTip">
<string>Remove this recipient</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BitcoinAmountField</class>
<extends>QLineEdit</extends>
<header>bitcoinamountfield.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QValidatedLineEdit</class>
<extends>QLineEdit</extends>
<header>qvalidatedlineedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../bitcoin.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TransactionDescDialog</class>
<widget class="QDialog" name="TransactionDescDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Transaction details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="detailText">
<property name="toolTip">
<string>This pane shows a detailed description of the transaction</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TransactionDescDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TransactionDescDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

23
src/qt/guiconstants.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef GUICONSTANTS_H
#define GUICONSTANTS_H
/* Milliseconds between model updates */
static const int MODEL_UPDATE_DELAY = 500;
/* Maximum passphrase length */
static const int MAX_PASSPHRASE_SIZE = 1024;
/* Size of icons in status bar */
static const int STATUSBAR_ICONSIZE = 16;
/* Invalid field background style */
#define STYLE_INVALID "background:#FF8080"
/* Transaction list -- unconfirmed transaction */
#define COLOR_UNCONFIRMED QColor(128, 128, 128)
/* Transaction list -- negative amount */
#define COLOR_NEGATIVE QColor(255, 0, 0)
/* Transaction list -- bare address (without label) */
#define COLOR_BAREADDRESS QColor(140, 140, 140)
#endif // GUICONSTANTS_H

74
src/qt/guiutil.cpp Normal file
View File

@@ -0,0 +1,74 @@
#include "guiutil.h"
#include "bitcoinaddressvalidator.h"
#include "walletmodel.h"
#include "bitcoinunits.h"
#include "headers.h"
#include <QString>
#include <QDateTime>
#include <QDoubleValidator>
#include <QFont>
#include <QLineEdit>
#include <QUrl>
QString GUIUtil::dateTimeStr(qint64 nTime)
{
return dateTimeStr(QDateTime::fromTime_t((qint32)nTime));
}
QString GUIUtil::dateTimeStr(const QDateTime &date)
{
return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
}
QFont GUIUtil::bitcoinAddressFont()
{
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
return font;
}
void GUIUtil::setupAddressWidget(QLineEdit *widget, QWidget *parent)
{
widget->setMaxLength(BitcoinAddressValidator::MaxAddressLength);
widget->setValidator(new BitcoinAddressValidator(parent));
widget->setFont(bitcoinAddressFont());
}
void GUIUtil::setupAmountWidget(QLineEdit *widget, QWidget *parent)
{
QDoubleValidator *amountValidator = new QDoubleValidator(parent);
amountValidator->setDecimals(8);
amountValidator->setBottom(0.0);
widget->setValidator(amountValidator);
widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
}
bool GUIUtil::parseBitcoinURL(const QUrl *url, SendCoinsRecipient *out)
{
if(url->scheme() != QString("bitcoin"))
return false;
SendCoinsRecipient rv;
rv.address = url->path();
rv.label = url->queryItemValue("label");
QString amount = url->queryItemValue("amount");
if(amount.isEmpty())
{
rv.amount = 0;
}
else // Amount is non-empty
{
if(!BitcoinUnits::parse(BitcoinUnits::BTC, amount, &rv.amount))
{
return false;
}
}
if(out)
{
*out = rv;
}
return true;
}

34
src/qt/guiutil.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef GUIUTIL_H
#define GUIUTIL_H
#include <QString>
QT_BEGIN_NAMESPACE
class QFont;
class QLineEdit;
class QWidget;
class QDateTime;
class QUrl;
QT_END_NAMESPACE
class SendCoinsRecipient;
class GUIUtil
{
public:
// Create human-readable string from date
static QString dateTimeStr(qint64 nTime);
static QString dateTimeStr(const QDateTime &datetime);
// Render bitcoin addresses in monospace font
static QFont bitcoinAddressFont();
// Set up widgets for address and amounts
static void setupAddressWidget(QLineEdit *widget, QWidget *parent);
static void setupAmountWidget(QLineEdit *widget, QWidget *parent);
// Parse "bitcoin:" URL into recipient object, return true on succesful parsing
// See Bitcoin URL definition discussion here: https://bitcointalk.org/index.php?topic=33490.0
static bool parseBitcoinURL(const QUrl *url, SendCoinsRecipient *out);
};
#endif // GUIUTIL_H

1155
src/qt/locale/bitcoin_de.ts Normal file

File diff suppressed because it is too large Load Diff

1441
src/qt/locale/bitcoin_nl.ts Normal file

File diff suppressed because it is too large Load Diff

2173
src/qt/locale/bitcoin_ru.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
#include "monitoreddatamapper.h"
#include <QWidget>
#include <QMetaObject>
#include <QMetaProperty>
MonitoredDataMapper::MonitoredDataMapper(QObject *parent) :
QDataWidgetMapper(parent)
{
}
void MonitoredDataMapper::addMapping(QWidget *widget, int section)
{
QDataWidgetMapper::addMapping(widget, section);
addChangeMonitor(widget);
}
void MonitoredDataMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName)
{
QDataWidgetMapper::addMapping(widget, section, propertyName);
addChangeMonitor(widget);
}
void MonitoredDataMapper::addChangeMonitor(QWidget *widget)
{
// Watch user property of widget for changes, and connect
// the signal to our viewModified signal.
QMetaProperty prop = widget->metaObject()->userProperty();
int signal = prop.notifySignalIndex();
int method = this->metaObject()->indexOfMethod("viewModified()");
if(signal != -1 && method != -1)
{
QMetaObject::connect(widget, signal, this, method);
}
}

View File

@@ -0,0 +1,32 @@
#ifndef MONITOREDDATAMAPPER_H
#define MONITOREDDATAMAPPER_H
#include <QDataWidgetMapper>
QT_BEGIN_NAMESPACE
class QWidget;
QT_END_NAMESPACE
/* Data <-> Widget mapper that watches for changes,
to be able to notify when 'dirty' (for example, to
enable a commit/apply button).
*/
class MonitoredDataMapper : public QDataWidgetMapper
{
Q_OBJECT
public:
explicit MonitoredDataMapper(QObject *parent=0);
void addMapping(QWidget *widget, int section);
void addMapping(QWidget *widget, int section, const QByteArray &propertyName);
private:
void addChangeMonitor(QWidget *widget);
signals:
void viewModified();
};
#endif // MONITOREDDATAMAPPER_H

224
src/qt/notificator.cpp Normal file
View File

@@ -0,0 +1,224 @@
#include "notificator.h"
#include <QMetaType>
#include <QVariant>
#include <QIcon>
#include <QApplication>
#include <QStyle>
#include <QByteArray>
#include <QSystemTrayIcon>
#include <QMessageBox>
#ifdef QT_DBUS
#include <QtDBus/QtDBus>
#include <stdint.h>
#endif
// https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least 128
const int FREEDESKTOP_NOTIFICATION_ICON_SIZE = 128;
Notificator::Notificator(const QString &programName, QSystemTrayIcon *trayicon, QWidget *parent):
QObject(parent),
parent(parent),
programName(programName),
mode(None),
trayIcon(trayicon)
#ifdef QT_DBUS
,interface(0)
#endif
{
if(trayicon && trayicon->supportsMessages())
{
mode = QSystemTray;
}
#ifdef QT_DBUS
interface = new QDBusInterface("org.freedesktop.Notifications",
"/org/freedesktop/Notifications", "org.freedesktop.Notifications");
if(interface->isValid())
{
mode = Freedesktop;
}
#endif
}
Notificator::~Notificator()
{
#ifdef QT_DBUS
delete interface;
#endif
}
#ifdef QT_DBUS
// Loosely based on http://www.qtcentre.org/archive/index.php/t-25879.html
class FreedesktopImage
{
public:
FreedesktopImage() {}
FreedesktopImage(const QImage &img);
static int metaType();
// Image to variant that can be marshaled over DBus
static QVariant toVariant(const QImage &img);
private:
int width, height, stride;
bool hasAlpha;
int channels;
int bitsPerSample;
QByteArray image;
friend QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i);
friend const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i);
};
Q_DECLARE_METATYPE(FreedesktopImage);
// Image configuration settings
const int CHANNELS = 4;
const int BYTES_PER_PIXEL = 4;
const int BITS_PER_SAMPLE = 8;
FreedesktopImage::FreedesktopImage(const QImage &img):
width(img.width()),
height(img.height()),
stride(img.width() * BYTES_PER_PIXEL),
hasAlpha(true),
channels(CHANNELS),
bitsPerSample(BITS_PER_SAMPLE)
{
// Convert 00xAARRGGBB to RGBA bytewise (endian-independent) format
QImage tmp = img.convertToFormat(QImage::Format_ARGB32);
const uint32_t *data = reinterpret_cast<const uint32_t*>(tmp.constBits());
unsigned int num_pixels = width * height;
image.resize(num_pixels * BYTES_PER_PIXEL);
for(unsigned int ptr = 0; ptr < num_pixels; ++ptr)
{
image[ptr*BYTES_PER_PIXEL+0] = data[ptr] >> 16; // R
image[ptr*BYTES_PER_PIXEL+1] = data[ptr] >> 8; // G
image[ptr*BYTES_PER_PIXEL+2] = data[ptr]; // B
image[ptr*BYTES_PER_PIXEL+3] = data[ptr] >> 24; // A
}
}
QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i)
{
a.beginStructure();
a << i.width << i.height << i.stride << i.hasAlpha << i.bitsPerSample << i.channels << i.image;
a.endStructure();
return a;
}
const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i)
{
a.beginStructure();
a >> i.width >> i.height >> i.stride >> i.hasAlpha >> i.bitsPerSample >> i.channels >> i.image;
a.endStructure();
return a;
}
int FreedesktopImage::metaType()
{
return qDBusRegisterMetaType<FreedesktopImage>();
}
QVariant FreedesktopImage::toVariant(const QImage &img)
{
FreedesktopImage fimg(img);
return QVariant(FreedesktopImage::metaType(), &fimg);
}
void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
{
Q_UNUSED(cls);
// Arguments for DBus call:
QList<QVariant> args;
// Program Name:
args.append(programName);
// Unique ID of this notification type:
args.append(0U);
// Application Icon, empty string
args.append(QString());
// Summary
args.append(title);
// Body
args.append(text);
// Actions (none, actions are deprecated)
QStringList actions;
args.append(actions);
// Hints
QVariantMap hints;
// If no icon specified, set icon based on class
QIcon tmpicon;
if(icon.isNull())
{
QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion;
switch(cls)
{
case Information: sicon = QStyle::SP_MessageBoxInformation; break;
case Warning: sicon = QStyle::SP_MessageBoxWarning; break;
case Critical: sicon = QStyle::SP_MessageBoxCritical; break;
default: break;
}
tmpicon = QApplication::style()->standardIcon(sicon);
}
else
{
tmpicon = icon;
}
hints["icon_data"] = FreedesktopImage::toVariant(tmpicon.pixmap(FREEDESKTOP_NOTIFICATION_ICON_SIZE).toImage());
args.append(hints);
// Timeout (in msec)
args.append(millisTimeout);
// "Fire and forget"
interface->callWithArgumentList(QDBus::NoBlock, "Notify", args);
}
#endif
void Notificator::notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
{
Q_UNUSED(icon);
QSystemTrayIcon::MessageIcon sicon = QSystemTrayIcon::NoIcon;
switch(cls) // Set icon based on class
{
case Information: sicon = QSystemTrayIcon::Information; break;
case Warning: sicon = QSystemTrayIcon::Warning; break;
case Critical: sicon = QSystemTrayIcon::Critical; break;
}
trayIcon->showMessage(title, text, sicon, millisTimeout);
}
void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
{
switch(mode)
{
#ifdef QT_DBUS
case Freedesktop:
notifyDBus(cls, title, text, icon, millisTimeout);
break;
#endif
case QSystemTray:
notifySystray(cls, title, text, icon, millisTimeout);
break;
default:
if(cls == Critical)
{
// Fall back to old fashioned popup dialog if critical and no other notification available
QMessageBox::critical(parent, title, text, QMessageBox::Ok, QMessageBox::Ok);
}
break;
}
}

63
src/qt/notificator.h Normal file
View File

@@ -0,0 +1,63 @@
#ifndef NOTIFICATOR_H
#define NOTIFICATOR_H
#include <QObject>
#include <QIcon>
QT_BEGIN_NAMESPACE
class QSystemTrayIcon;
#ifdef QT_DBUS
class QDBusInterface;
#endif
QT_END_NAMESPACE
// Cross-platform desktop notification client
class Notificator: public QObject
{
Q_OBJECT
public:
// Create a new notificator
// Ownership of trayIcon is not transferred to this object
Notificator(const QString &programName=QString(), QSystemTrayIcon *trayIcon=0, QWidget *parent=0);
~Notificator();
// Message class
enum Class
{
Information,
Warning,
Critical,
};
public slots:
/* Show notification message.
*
* cls: general message class
* title: title shown with message
* text: message content
* icon: optional icon to show with message
* millisTimeout: notification timeout in milliseconds (default 10 seconds)
*/
void notify(Class cls, const QString &title, const QString &text,
const QIcon &icon = QIcon(), int millisTimeout = 10000);
private:
QWidget *parent;
enum Mode {
None,
Freedesktop, // Use DBus org.freedesktop.Notifications
QSystemTray, // Use QSystemTray::showMessage
};
QString programName;
Mode mode;
QSystemTrayIcon *trayIcon;
#ifdef QT_DBUS
QDBusInterface *interface;
void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout);
#endif
void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout);
};
#endif // NOTIFICATOR_H

277
src/qt/optionsdialog.cpp Normal file
View File

@@ -0,0 +1,277 @@
#include "optionsdialog.h"
#include "optionsmodel.h"
#include "bitcoinamountfield.h"
#include "monitoreddatamapper.h"
#include "guiutil.h"
#include "bitcoinunits.h"
#include "qvaluecombobox.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QStackedWidget>
#include <QCheckBox>
#include <QLabel>
#include <QLineEdit>
#include <QIntValidator>
#include <QDoubleValidator>
#include <QRegExpValidator>
#include <QDialogButtonBox>
/* First page of options */
class MainOptionsPage : public QWidget
{
public:
explicit MainOptionsPage(QWidget *parent=0);
void setMapper(MonitoredDataMapper *mapper);
private:
QCheckBox *bitcoin_at_startup;
QCheckBox *minimize_to_tray;
QCheckBox *map_port_upnp;
QCheckBox *minimize_on_close;
QCheckBox *connect_socks4;
QLineEdit *proxy_ip;
QLineEdit *proxy_port;
BitcoinAmountField *fee_edit;
signals:
public slots:
};
class DisplayOptionsPage : public QWidget
{
public:
explicit DisplayOptionsPage(QWidget *parent=0);
void setMapper(MonitoredDataMapper *mapper);
private:
QValueComboBox *unit;
QCheckBox *display_addresses;
signals:
public slots:
};
OptionsDialog::OptionsDialog(QWidget *parent):
QDialog(parent), contents_widget(0), pages_widget(0),
model(0), main_page(0), display_page(0)
{
contents_widget = new QListWidget();
contents_widget->setMaximumWidth(128);
pages_widget = new QStackedWidget();
pages_widget->setMinimumWidth(300);
QListWidgetItem *item_main = new QListWidgetItem(tr("Main"));
contents_widget->addItem(item_main);
main_page = new MainOptionsPage(this);
pages_widget->addWidget(main_page);
QListWidgetItem *item_display = new QListWidgetItem(tr("Display"));
contents_widget->addItem(item_display);
display_page = new DisplayOptionsPage(this);
pages_widget->addWidget(display_page);
contents_widget->setCurrentRow(0);
QHBoxLayout *main_layout = new QHBoxLayout();
main_layout->addWidget(contents_widget);
main_layout->addWidget(pages_widget, 1);
QVBoxLayout *layout = new QVBoxLayout();
layout->addLayout(main_layout);
QDialogButtonBox *buttonbox = new QDialogButtonBox();
buttonbox->setStandardButtons(QDialogButtonBox::Apply|QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
apply_button = buttonbox->button(QDialogButtonBox::Apply);
layout->addWidget(buttonbox);
setLayout(layout);
setWindowTitle(tr("Options"));
/* Widget-to-option mapper */
mapper = new MonitoredDataMapper(this);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
mapper->setOrientation(Qt::Vertical);
/* enable apply button when data modified */
connect(mapper, SIGNAL(viewModified()), this, SLOT(enableApply()));
/* disable apply button when new data loaded */
connect(mapper, SIGNAL(currentIndexChanged(int)), this, SLOT(disableApply()));
/* Event bindings */
connect(contents_widget, SIGNAL(currentRowChanged(int)), this, SLOT(changePage(int)));
connect(buttonbox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(okClicked()));
connect(buttonbox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(buttonbox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(applyClicked()));
}
void OptionsDialog::setModel(OptionsModel *model)
{
this->model = model;
mapper->setModel(model);
main_page->setMapper(mapper);
display_page->setMapper(mapper);
mapper->toFirst();
}
void OptionsDialog::changePage(int index)
{
pages_widget->setCurrentIndex(index);
}
void OptionsDialog::okClicked()
{
mapper->submit();
accept();
}
void OptionsDialog::cancelClicked()
{
reject();
}
void OptionsDialog::applyClicked()
{
mapper->submit();
apply_button->setEnabled(false);
}
void OptionsDialog::enableApply()
{
apply_button->setEnabled(true);
}
void OptionsDialog::disableApply()
{
apply_button->setEnabled(false);
}
MainOptionsPage::MainOptionsPage(QWidget *parent):
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout();
bitcoin_at_startup = new QCheckBox(tr("&Start Bitcoin on window system startup"));
bitcoin_at_startup->setToolTip(tr("Automatically start Bitcoin after the computer is turned on"));
layout->addWidget(bitcoin_at_startup);
minimize_to_tray = new QCheckBox(tr("&Minimize to the tray instead of the taskbar"));
minimize_to_tray->setToolTip(tr("Show only a tray icon after minimizing the window"));
layout->addWidget(minimize_to_tray);
map_port_upnp = new QCheckBox(tr("Map port using &UPnP"));
map_port_upnp->setToolTip(tr("Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled."));
layout->addWidget(map_port_upnp);
minimize_on_close = new QCheckBox(tr("M&inimize on close"));
minimize_on_close->setToolTip(tr("Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu."));
layout->addWidget(minimize_on_close);
connect_socks4 = new QCheckBox(tr("&Connect through SOCKS4 proxy:"));
connect_socks4->setToolTip(tr("Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor)"));
layout->addWidget(connect_socks4);
QHBoxLayout *proxy_hbox = new QHBoxLayout();
proxy_hbox->addSpacing(18);
QLabel *proxy_ip_label = new QLabel(tr("Proxy &IP: "));
proxy_hbox->addWidget(proxy_ip_label);
proxy_ip = new QLineEdit();
proxy_ip->setMaximumWidth(140);
proxy_ip->setEnabled(false);
proxy_ip->setValidator(new QRegExpValidator(QRegExp("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"), this));
proxy_ip->setToolTip(tr("IP address of the proxy (e.g. 127.0.0.1)"));
proxy_ip_label->setBuddy(proxy_ip);
proxy_hbox->addWidget(proxy_ip);
QLabel *proxy_port_label = new QLabel(tr("&Port: "));
proxy_hbox->addWidget(proxy_port_label);
proxy_port = new QLineEdit();
proxy_port->setMaximumWidth(55);
proxy_port->setValidator(new QIntValidator(0, 65535, this));
proxy_port->setEnabled(false);
proxy_port->setToolTip(tr("Port of the proxy (e.g. 1234)"));
proxy_port_label->setBuddy(proxy_port);
proxy_hbox->addWidget(proxy_port);
proxy_hbox->addStretch(1);
layout->addLayout(proxy_hbox);
QLabel *fee_help = new QLabel(tr("Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended."));
fee_help->setWordWrap(true);
layout->addWidget(fee_help);
QHBoxLayout *fee_hbox = new QHBoxLayout();
fee_hbox->addSpacing(18);
QLabel *fee_label = new QLabel(tr("Pay transaction &fee"));
fee_hbox->addWidget(fee_label);
fee_edit = new BitcoinAmountField();
fee_edit->setToolTip(tr("Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended."));
fee_label->setBuddy(fee_edit);
fee_hbox->addWidget(fee_edit);
fee_hbox->addStretch(1);
layout->addLayout(fee_hbox);
layout->addStretch(1); // Extra space at bottom
setLayout(layout);
connect(connect_socks4, SIGNAL(toggled(bool)), proxy_ip, SLOT(setEnabled(bool)));
connect(connect_socks4, SIGNAL(toggled(bool)), proxy_port, SLOT(setEnabled(bool)));
#ifndef USE_UPNP
map_port_upnp->setDisabled(true);
#endif
}
void MainOptionsPage::setMapper(MonitoredDataMapper *mapper)
{
// Map model to widgets
mapper->addMapping(bitcoin_at_startup, OptionsModel::StartAtStartup);
mapper->addMapping(minimize_to_tray, OptionsModel::MinimizeToTray);
mapper->addMapping(map_port_upnp, OptionsModel::MapPortUPnP);
mapper->addMapping(minimize_on_close, OptionsModel::MinimizeOnClose);
mapper->addMapping(connect_socks4, OptionsModel::ConnectSOCKS4);
mapper->addMapping(proxy_ip, OptionsModel::ProxyIP);
mapper->addMapping(proxy_port, OptionsModel::ProxyPort);
mapper->addMapping(fee_edit, OptionsModel::Fee);
}
DisplayOptionsPage::DisplayOptionsPage(QWidget *parent):
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout();
QHBoxLayout *unit_hbox = new QHBoxLayout();
unit_hbox->addSpacing(18);
QLabel *unit_label = new QLabel(tr("&Unit to show amounts in: "));
unit_hbox->addWidget(unit_label);
unit = new QValueComboBox(this);
unit->setModel(new BitcoinUnits(this));
unit->setToolTip(tr("Choose the default subdivision unit to show in the interface, and when sending coins"));
unit_label->setBuddy(unit);
unit_hbox->addWidget(unit);
layout->addLayout(unit_hbox);
display_addresses = new QCheckBox(tr("Display addresses in transaction list"), this);
layout->addWidget(display_addresses);
layout->addStretch();
setLayout(layout);
}
void DisplayOptionsPage::setMapper(MonitoredDataMapper *mapper)
{
mapper->addMapping(unit, OptionsModel::DisplayUnit);
mapper->addMapping(display_addresses, OptionsModel::DisplayAddresses);
}

50
src/qt/optionsdialog.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef OPTIONSDIALOG_H
#define OPTIONSDIALOG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
class QStackedWidget;
class QListWidget;
class QListWidgetItem;
class QPushButton;
QT_END_NAMESPACE
class OptionsModel;
class MainOptionsPage;
class DisplayOptionsPage;
class MonitoredDataMapper;
class OptionsDialog : public QDialog
{
Q_OBJECT
public:
explicit OptionsDialog(QWidget *parent=0);
void setModel(OptionsModel *model);
signals:
public slots:
void changePage(int index);
private slots:
void okClicked();
void cancelClicked();
void applyClicked();
void enableApply();
void disableApply();
private:
QListWidget *contents_widget;
QStackedWidget *pages_widget;
OptionsModel *model;
MonitoredDataMapper *mapper;
QPushButton *apply_button;
// Pages
MainOptionsPage *main_page;
DisplayOptionsPage *display_page;
void setupMainPage();
};
#endif // OPTIONSDIALOG_H

162
src/qt/optionsmodel.cpp Normal file
View File

@@ -0,0 +1,162 @@
#include "optionsmodel.h"
#include "bitcoinunits.h"
#include "headers.h"
OptionsModel::OptionsModel(CWallet *wallet, QObject *parent) :
QAbstractListModel(parent),
wallet(wallet),
nDisplayUnit(BitcoinUnits::BTC),
bDisplayAddresses(false)
{
// Read our specific settings from the wallet db
CWalletDB walletdb(wallet->strWalletFile);
walletdb.ReadSetting("nDisplayUnit", nDisplayUnit);
walletdb.ReadSetting("bDisplayAddresses", bDisplayAddresses);
}
int OptionsModel::rowCount(const QModelIndex & parent) const
{
return OptionIDRowCount;
}
QVariant OptionsModel::data(const QModelIndex & index, int role) const
{
if(role == Qt::EditRole)
{
switch(index.row())
{
case StartAtStartup:
return QVariant();
case MinimizeToTray:
return QVariant(fMinimizeToTray);
case MapPortUPnP:
return QVariant(fUseUPnP);
case MinimizeOnClose:
return QVariant(fMinimizeOnClose);
case ConnectSOCKS4:
return QVariant(fUseProxy);
case ProxyIP:
return QVariant(QString::fromStdString(addrProxy.ToStringIP()));
case ProxyPort:
return QVariant(QString::fromStdString(addrProxy.ToStringPort()));
case Fee:
return QVariant(nTransactionFee);
case DisplayUnit:
return QVariant(nDisplayUnit);
case DisplayAddresses:
return QVariant(bDisplayAddresses);
default:
return QVariant();
}
}
return QVariant();
}
bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
bool successful = true; /* set to false on parse error */
if(role == Qt::EditRole)
{
CWalletDB walletdb(wallet->strWalletFile);
switch(index.row())
{
case StartAtStartup:
successful = false; /*TODO*/
break;
case MinimizeToTray:
fMinimizeToTray = value.toBool();
walletdb.WriteSetting("fMinimizeToTray", fMinimizeToTray);
break;
case MapPortUPnP:
fUseUPnP = value.toBool();
walletdb.WriteSetting("fUseUPnP", fUseUPnP);
#ifdef USE_UPNP
MapPort(fUseUPnP);
#endif
break;
case MinimizeOnClose:
fMinimizeOnClose = value.toBool();
walletdb.WriteSetting("fMinimizeOnClose", fMinimizeOnClose);
break;
case ConnectSOCKS4:
fUseProxy = value.toBool();
walletdb.WriteSetting("fUseProxy", fUseProxy);
break;
case ProxyIP:
{
// Use CAddress to parse and check IP
CAddress addr(value.toString().toStdString() + ":1");
if (addr.ip != INADDR_NONE)
{
addrProxy.ip = addr.ip;
walletdb.WriteSetting("addrProxy", addrProxy);
}
else
{
successful = false;
}
}
break;
case ProxyPort:
{
int nPort = atoi(value.toString().toAscii().data());
if (nPort > 0 && nPort < USHRT_MAX)
{
addrProxy.port = htons(nPort);
walletdb.WriteSetting("addrProxy", addrProxy);
}
else
{
successful = false;
}
}
break;
case Fee: {
nTransactionFee = value.toLongLong();
walletdb.WriteSetting("nTransactionFee", nTransactionFee);
}
break;
case DisplayUnit: {
int unit = value.toInt();
nDisplayUnit = unit;
walletdb.WriteSetting("nDisplayUnit", nDisplayUnit);
emit displayUnitChanged(unit);
}
case DisplayAddresses: {
bDisplayAddresses = value.toBool();
walletdb.WriteSetting("bDisplayAddresses", bDisplayAddresses);
}
default:
break;
}
}
emit dataChanged(index, index);
return successful;
}
qint64 OptionsModel::getTransactionFee()
{
return nTransactionFee;
}
bool OptionsModel::getMinimizeToTray()
{
return fMinimizeToTray;
}
bool OptionsModel::getMinimizeOnClose()
{
return fMinimizeOnClose;
}
int OptionsModel::getDisplayUnit()
{
return nDisplayUnit;
}
bool OptionsModel::getDisplayAddresses()
{
return bDisplayAddresses;
}

56
src/qt/optionsmodel.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef OPTIONSMODEL_H
#define OPTIONSMODEL_H
#include <QAbstractListModel>
class CWallet;
/* Interface from QT to configuration data structure for bitcoin client.
To QT, the options are presented as a list with the different options
laid out vertically.
This can be changed to a tree once the settings become sufficiently
complex.
*/
class OptionsModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit OptionsModel(CWallet *wallet, QObject *parent = 0);
enum OptionID {
StartAtStartup, // bool
MinimizeToTray, // bool
MapPortUPnP, // bool
MinimizeOnClose, // bool
ConnectSOCKS4, // bool
ProxyIP, // QString
ProxyPort, // QString
Fee, // qint64
DisplayUnit, // BitcoinUnits::Unit
DisplayAddresses, // bool
OptionIDRowCount
};
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
/* Explicit getters */
qint64 getTransactionFee();
bool getMinimizeToTray();
bool getMinimizeOnClose();
int getDisplayUnit();
bool getDisplayAddresses();
private:
// Wallet stores persistent options
CWallet *wallet;
int nDisplayUnit;
bool bDisplayAddresses;
signals:
void displayUnitChanged(int unit);
public slots:
};
#endif // OPTIONSMODEL_H

173
src/qt/overviewpage.cpp Normal file
View File

@@ -0,0 +1,173 @@
#include "overviewpage.h"
#include "ui_overviewpage.h"
#include "walletmodel.h"
#include "bitcoinunits.h"
#include "optionsmodel.h"
#include "transactiontablemodel.h"
#include "transactionfilterproxy.h"
#include "guiutil.h"
#include "guiconstants.h"
#include <QAbstractItemDelegate>
#include <QPainter>
#define DECORATION_SIZE 64
#define NUM_ITEMS 3
class TxViewDelegate : public QAbstractItemDelegate
{
//Q_OBJECT
public:
TxViewDelegate(): QAbstractItemDelegate(), unit(BitcoinUnits::BTC)
{
}
inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index ) const
{
painter->save();
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
QRect mainRect = option.rect;
QRect decorationRect(mainRect.topLeft(), QSize(DECORATION_SIZE, DECORATION_SIZE));
int xspace = DECORATION_SIZE + 8;
int ypad = 6;
int halfheight = (mainRect.height() - 2*ypad)/2;
QRect amountRect(mainRect.left() + xspace, mainRect.top()+ypad, mainRect.width() - xspace, halfheight);
QRect addressRect(mainRect.left() + xspace, mainRect.top()+ypad+halfheight, mainRect.width() - xspace, halfheight);
icon.paint(painter, decorationRect);
QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime();
QString address = index.data(Qt::DisplayRole).toString();
qint64 amount = index.data(TransactionTableModel::AmountRole).toLongLong();
bool confirmed = index.data(TransactionTableModel::ConfirmedRole).toBool();
QVariant value = index.data(Qt::ForegroundRole);
QColor foreground = option.palette.color(QPalette::Text);
if(qVariantCanConvert<QColor>(value))
{
foreground = qvariant_cast<QColor>(value);
}
painter->setPen(foreground);
painter->drawText(addressRect, Qt::AlignLeft|Qt::AlignVCenter, address);
if(amount < 0)
{
foreground = COLOR_NEGATIVE;
}
else if(!confirmed)
{
foreground = COLOR_UNCONFIRMED;
}
else
{
foreground = option.palette.color(QPalette::Text);
}
painter->setPen(foreground);
QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true);
if(!confirmed)
{
amountText = QString("[") + amountText + QString("]");
}
painter->drawText(amountRect, Qt::AlignRight|Qt::AlignVCenter, amountText);
painter->setPen(option.palette.color(QPalette::Text));
painter->drawText(amountRect, Qt::AlignLeft|Qt::AlignVCenter, GUIUtil::dateTimeStr(date));
painter->restore();
}
inline QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return QSize(DECORATION_SIZE, DECORATION_SIZE);
}
int unit;
};
OverviewPage::OverviewPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::OverviewPage),
currentBalance(-1),
currentUnconfirmedBalance(-1),
txdelegate(new TxViewDelegate())
{
ui->setupUi(this);
// Balance: <balance>
ui->labelBalance->setFont(QFont("Monospace", -1, QFont::Bold));
ui->labelBalance->setToolTip(tr("Your current balance"));
ui->labelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);
// Unconfirmed balance: <balance>
ui->labelUnconfirmed->setFont(QFont("Monospace", -1, QFont::Bold));
ui->labelUnconfirmed->setToolTip(tr("Total of transactions that have yet to be confirmed, and do not yet count toward the current balance"));
ui->labelUnconfirmed->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);
ui->labelNumTransactions->setToolTip(tr("Total number of transactions in wallet"));
// Recent transactions
ui->listTransactions->setStyleSheet("background:transparent");
ui->listTransactions->setItemDelegate(txdelegate);
ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
ui->listTransactions->setSelectionMode(QAbstractItemView::NoSelection);
ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
connect(ui->listTransactions, SIGNAL(clicked(QModelIndex)), this, SIGNAL(transactionClicked(QModelIndex)));
}
OverviewPage::~OverviewPage()
{
delete ui;
}
void OverviewPage::setBalance(qint64 balance, qint64 unconfirmedBalance)
{
int unit = model->getOptionsModel()->getDisplayUnit();
currentBalance = balance;
currentUnconfirmedBalance = unconfirmedBalance;
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance));
ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance));
}
void OverviewPage::setNumTransactions(int count)
{
ui->labelNumTransactions->setText(QLocale::system().toString(count));
}
void OverviewPage::setModel(WalletModel *model)
{
this->model = model;
// Set up transaction list
TransactionFilterProxy *filter = new TransactionFilterProxy();
filter->setSourceModel(model->getTransactionTableModel());
filter->setLimit(NUM_ITEMS);
filter->setDynamicSortFilter(true);
filter->setSortRole(Qt::EditRole);
filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
ui->listTransactions->setModel(filter);
ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress);
// Keep up to date with wallet
setBalance(model->getBalance(), model->getUnconfirmedBalance());
connect(model, SIGNAL(balanceChanged(qint64, qint64)), this, SLOT(setBalance(qint64, qint64)));
setNumTransactions(model->getNumTransactions());
connect(model, SIGNAL(numTransactionsChanged(int)), this, SLOT(setNumTransactions(int)));
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(displayUnitChanged()));
}
void OverviewPage::displayUnitChanged()
{
if(currentBalance != -1)
setBalance(currentBalance, currentUnconfirmedBalance);
txdelegate->unit = model->getOptionsModel()->getDisplayUnit();
ui->listTransactions->update();
}

45
src/qt/overviewpage.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef OVERVIEWPAGE_H
#define OVERVIEWPAGE_H
#include <QWidget>
QT_BEGIN_NAMESPACE
class QModelIndex;
QT_END_NAMESPACE
namespace Ui {
class OverviewPage;
}
class WalletModel;
class TxViewDelegate;
class OverviewPage : public QWidget
{
Q_OBJECT
public:
explicit OverviewPage(QWidget *parent = 0);
~OverviewPage();
void setModel(WalletModel *model);
public slots:
void setBalance(qint64 balance, qint64 unconfirmedBalance);
void setNumTransactions(int count);
signals:
void transactionClicked(const QModelIndex &index);
private:
Ui::OverviewPage *ui;
WalletModel *model;
qint64 currentBalance;
qint64 currentUnconfirmedBalance;
TxViewDelegate *txdelegate;
private slots:
void displayUnitChanged();
};
#endif // OVERVIEWPAGE_H

View File

@@ -0,0 +1,45 @@
#include "qvalidatedlineedit.h"
#include "guiconstants.h"
QValidatedLineEdit::QValidatedLineEdit(QWidget *parent) :
QLineEdit(parent), valid(true)
{
connect(this, SIGNAL(textChanged(QString)), this, SLOT(markValid()));
}
void QValidatedLineEdit::setValid(bool valid)
{
if(valid == this->valid)
{
return;
}
if(valid)
{
setStyleSheet("");
}
else
{
setStyleSheet(STYLE_INVALID);
}
this->valid = valid;
}
void QValidatedLineEdit::focusInEvent(QFocusEvent *evt)
{
// Clear invalid flag on focus
setValid(true);
QLineEdit::focusInEvent(evt);
}
void QValidatedLineEdit::markValid()
{
setValid(true);
}
void QValidatedLineEdit::clear()
{
setValid(true);
QLineEdit::clear();
}

View File

@@ -0,0 +1,28 @@
#ifndef QVALIDATEDLINEEDIT_H
#define QVALIDATEDLINEEDIT_H
#include <QLineEdit>
// Line edit that can be marked as "invalid". When marked as invalid,
// it will get a red background until it is focused.
class QValidatedLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit QValidatedLineEdit(QWidget *parent = 0);
void clear();
protected:
void focusInEvent(QFocusEvent *evt);
private:
bool valid;
public slots:
void setValid(bool valid);
private slots:
void markValid();
};
#endif // QVALIDATEDLINEEDIT_H

27
src/qt/qvaluecombobox.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "qvaluecombobox.h"
QValueComboBox::QValueComboBox(QWidget *parent) :
QComboBox(parent), role(Qt::UserRole)
{
connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(handleSelectionChanged(int)));
}
int QValueComboBox::value() const
{
return itemData(currentIndex(), role).toInt();
}
void QValueComboBox::setValue(int value)
{
setCurrentIndex(findData(value, role));
}
void QValueComboBox::setRole(int role)
{
this->role = role;
}
void QValueComboBox::handleSelectionChanged(int idx)
{
emit valueChanged();
}

33
src/qt/qvaluecombobox.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef QVALUECOMBOBOX_H
#define QVALUECOMBOBOX_H
#include <QComboBox>
// QComboBox that can be used with QDataWidgetMapper to select
// ordinal values from a model.
class QValueComboBox : public QComboBox
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged USER true);
public:
explicit QValueComboBox(QWidget *parent = 0);
int value() const;
void setValue(int value);
// Model role to use as value
void setRole(int role);
signals:
void valueChanged();
public slots:
private:
int role;
private slots:
void handleSelectionChanged(int idx);
};
#endif // QVALUECOMBOBOX_H

1
src/qt/res/bitcoin-qt.rc Normal file
View File

@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "icons/bitcoin.ico"

BIN
src/qt/res/icons/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
src/qt/res/icons/clock1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

BIN
src/qt/res/icons/clock2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

BIN
src/qt/res/icons/clock3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

BIN
src/qt/res/icons/clock4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

BIN
src/qt/res/icons/clock5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

BIN
src/qt/res/icons/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/qt/res/icons/export.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

BIN
src/qt/res/icons/key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
src/qt/res/icons/quit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/qt/res/icons/remove.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/qt/res/icons/send.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/qt/res/icons/synced.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More