diff options
Diffstat (limited to 'src')
128 files changed, 12412 insertions, 22 deletions
diff --git a/src/rpc.cpp b/src/bitcoinrpc.cpp index a4deece6b2..c8248fa4fe 100644 --- a/src/rpc.cpp +++ b/src/bitcoinrpc.cpp @@ -49,13 +49,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) { diff --git a/src/rpc.h b/src/bitcoinrpc.h index 48a7b8a8a6..48a7b8a8a6 100644 --- a/src/rpc.h +++ b/src/bitcoinrpc.h diff --git a/src/headers.h b/src/headers.h index d1844eb24e..02dba30ae2 100644 --- a/src/headers.h +++ b/src/headers.h @@ -98,8 +98,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" diff --git a/src/init.cpp b/src/init.cpp index acfcc44d10..eeb22bbaf1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -3,7 +3,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" @@ -79,7 +79,7 @@ void HandleSIGTERM(int) // // Start // -#ifndef GUI +#if !defined(QT_GUI) && !defined(GUI) int main(int argc, char* argv[]) { bool fRet = false; @@ -239,10 +239,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"); @@ -372,18 +371,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; @@ -414,12 +416,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 @@ -552,7 +556,7 @@ bool AppInit2(int argc, char* argv[]) SetStartOnSystemStartup(true); #endif -#ifndef GUI +#if !defined(QT_GUI) && !defined(GUI) while (1) Sleep(5000); #endif diff --git a/src/main.cpp b/src/main.cpp index f129fb6506..99600771c7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ map<COutPoint, CInPoint> mapNextTx; map<uint256, CBlockIndex*> mapBlockIndex; uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); -const int nTotalBlocksEstimate = 134444; // Conservative estimate of total nr of blocks on main chain +int nTotalBlocksEstimate = 134444; // Conservative estimate of total nr of blocks on main chain const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download" CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; @@ -1875,6 +1875,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 > nTotalBlocksEstimate) + { + nTotalBlocksEstimate = pfrom->nStartingHeight; + } } diff --git a/src/qt/aboutdialog.cpp b/src/qt/aboutdialog.cpp new file mode 100644 index 0000000000..13d263b75c --- /dev/null +++ b/src/qt/aboutdialog.cpp @@ -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(); +} diff --git a/src/qt/aboutdialog.h b/src/qt/aboutdialog.h new file mode 100644 index 0000000000..d2caa3eedf --- /dev/null +++ b/src/qt/aboutdialog.h @@ -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 diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp new file mode 100644 index 0000000000..ee64cc2c80 --- /dev/null +++ b/src/qt/addressbookpage.cpp @@ -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); + } +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h new file mode 100644 index 0000000000..53c7728c8c --- /dev/null +++ b/src/qt/addressbookpage.h @@ -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 diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp new file mode 100644 index 0000000000..bd314ba0f0 --- /dev/null +++ b/src/qt/addresstablemodel.cpp @@ -0,0 +1,335 @@ +#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_KeyStore) + CRITICAL_BLOCK(wallet->cs_mapAddressBook) + { + 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_mapAddressBook) + { + // 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_mapAddressBook) + { + if(wallet->mapAddressBook.count(strAddress)) + { + editStatus = DUPLICATE_ADDRESS; + return QString(); + } + } + } + else if(type == Receive) + { + // Generate a new address to associate with given label + strAddress = CBitcoinAddress(wallet->GetOrReuseKeyFromPool()).ToString(); + } + else + { + return QString(); + } + // Add entry and update list + CRITICAL_BLOCK(wallet->cs_mapAddressBook) + 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_mapAddressBook) + { + 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_mapAddressBook) + { + 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(); + } +} + diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h new file mode 100644 index 0000000000..296fa58054 --- /dev/null +++ b/src/qt/addresstablemodel.h @@ -0,0 +1,81 @@ +#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 = 0, + INVALID_ADDRESS = 1, + DUPLICATE_ADDRESS = 2 + }; + + 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 diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp new file mode 100644 index 0000000000..cdd69e31a5 --- /dev/null +++ b/src/qt/bitcoin.cpp @@ -0,0 +1,185 @@ +/* + * W.J. van der Laan 2011 + */ +#include "bitcoingui.h" +#include "clientmodel.h" +#include "walletmodel.h" +#include "optionsmodel.h" +#include "qtwin.h" + +#include "headers.h" +#include "init.h" + +#include <QApplication> +#include <QMessageBox> +#include <QThread> +#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[]) +{ + 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"), Qt::WindowStaysOnTopHint); + 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); + + if (QtWin::isCompositionEnabled()) + { +#ifdef Q_WS_WIN32 + // Windows-specific customization + window.setAttribute(Qt::WA_TranslucentBackground); + window.setAttribute(Qt::WA_NoSystemBackground, false); + QPalette pal = window.palette(); + QColor bg = pal.window().color(); + bg.setAlpha(0); + pal.setColor(QPalette::Window, bg); + window.setPalette(pal); + window.ensurePolished(); + window.setAttribute(Qt::WA_StyledBackground, false); +#endif + QtWin::extendFrameIntoClientArea(&window); + window.setContentsMargins(0, 0, 0, 0); + } + + 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; +} diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc new file mode 100644 index 0000000000..629349c65d --- /dev/null +++ b/src/qt/bitcoin.qrc @@ -0,0 +1,45 @@ +<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> + </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> diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp new file mode 100644 index 0000000000..373877808f --- /dev/null +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -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; +} diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h new file mode 100644 index 0000000000..73f6ea1f61 --- /dev/null +++ b/src/qt/bitcoinaddressvalidator.h @@ -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 diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp new file mode 100644 index 0000000000..ea38cc86dd --- /dev/null +++ b/src/qt/bitcoinamountfield.cpp @@ -0,0 +1,177 @@ +#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) + { + setValue(currentValue); + } + else + { + // If current value is invalid, just clear field + setText(""); + } + setValid(true); +} + +void BitcoinAmountField::setDisplayUnit(int newUnit) +{ + unit->setValue(newUnit); +} diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h new file mode 100644 index 0000000000..cc92159fed --- /dev/null +++ b/src/qt/bitcoinamountfield.h @@ -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 diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp new file mode 100644 index 0000000000..dd94652e3a --- /dev/null +++ b/src/qt/bitcoingui.cpp @@ -0,0 +1,533 @@ +/* + * 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 <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), + trayIcon(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("&File"); + file->addAction(sendCoinsAction); + file->addAction(receiveCoinsAction); + file->addSeparator(); + file->addAction(quitAction); + + QMenu *settings = menuBar()->addMenu("&Settings"); + settings->addAction(optionsAction); + + QMenu *help = menuBar()->addMenu("&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); + labelConnectionsIcon = new QLabel(); + labelBlocksIcon = new QLabel(); + 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")); + + 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(show())); +} + +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); + + // Balloon popup for new transaction + connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(incomingTransaction(QModelIndex,int,int))); +} + +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(); +} + +void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) +{ + if(reason == QSystemTrayIcon::Trigger) + { + // Doubleclick 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(16,16)); + labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count)); +} + +void BitcoinGUI::setNumBlocks(int count) +{ + int total = clientModel->getTotalBlocksEstimate(); + QString tooltip; + + if(count < total) + { + progressBarLabel->setVisible(true); + progressBar->setVisible(true); + progressBar->setMaximum(total); + progressBar->setValue(count); + 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(16,16)); + } + 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 + if(trayIcon->supportsMessages()) + { + // Show as "balloon" message if possible + trayIcon->showMessage(title, message, QSystemTrayIcon::Critical); + } + else + { + // Fall back to old fashioned popup dialog if not + QMessageBox::critical(this, title, + message, + QMessageBox::Ok, QMessageBox::Ok); + } +} + +void BitcoinGUI::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::WindowStateChange) + { + if(clientModel->getOptionsModel()->getMinimizeToTray()) + { + if (isMinimized()) + { + hide(); + e->ignore(); + } + else + { + 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) +{ + if(start == end) + return; + 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(); + + trayIcon->showMessage((amount)<0 ? tr("Sent transaction") : + tr("Incoming transaction"), + tr("Date: ") + date + "\n" + + tr("Amount: ") + BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), amount, true) + "\n" + + tr("Type: ") + type + "\n" + + tr("Address: ") + address + "\n", + QSystemTrayIcon::Information); + } +} + +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(); +} + diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h new file mode 100644 index 0000000000..377da72611 --- /dev/null +++ b/src/qt/bitcoingui.h @@ -0,0 +1,110 @@ +#ifndef BITCOINGUI_H +#define BITCOINGUI_H + +#include <QMainWindow> +#include <QSystemTrayIcon> + +class TransactionTableModel; +class ClientModel; +class WalletModel; +class TransactionView; +class OverviewPage; +class AddressBookPage; +class SendCoinsDialog; + +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 *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; + + QSystemTrayIcon *trayIcon; + TransactionView *transactionView; + + QMovie *syncIconMovie; + + void createActions(); + QWidget *createTabs(); + void createTrayIcon(); + +public slots: + void setNumConnections(int count); + void setNumBlocks(int count); + 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); +}; + +#endif diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp new file mode 100644 index 0000000000..2fa8de052b --- /dev/null +++ b/src/qt/bitcoinstrings.cpp @@ -0,0 +1,86 @@ +#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", "Error loading addr.dat \n"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading blkindex.dat \n"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat \n"), +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", "Warning: Disk space is low "), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: 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", "Error: Transaction creation failed "), +QT_TRANSLATE_NOOP("bitcoin-core", "Sending..."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: 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", "Invalid amount"), +QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid bitcoin address"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unable to bind to port %d on this computer. Bitcoin is probably already " +"running."), +QT_TRANSLATE_NOOP("bitcoin-core", "To use the %s option"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Warning: %s, you must set rpcpassword=<password>\n" +"in the configuration file: %s\n" +"If the file does not exist, create it with owner-readable-only file " +"permissions.\n"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"You must set rpcpassword=<password> in the configuration file:\n" +"%s\n" +"If the file does not exist, create it with owner-readable-only file " +"permissions."), +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"), +};
\ No newline at end of file diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp new file mode 100644 index 0000000000..9a9a4890dc --- /dev/null +++ b/src/qt/bitcoinunits.cpp @@ -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(); +} diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h new file mode 100644 index 0000000000..a7bebbc3e4 --- /dev/null +++ b/src/qt/bitcoinunits.h @@ -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 diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp new file mode 100644 index 0000000000..5cf02eac7d --- /dev/null +++ b/src/qt/clientmodel.cpp @@ -0,0 +1,75 @@ +#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); +} + +int ClientModel::getNumConnections() const +{ + return vNodes.size(); +} + +int ClientModel::getNumBlocks() const +{ + return nBestHeight; +} + +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::getTotalBlocksEstimate() const +{ + return GetTotalBlocksEstimate(); +} + +OptionsModel *ClientModel::getOptionsModel() +{ + return optionsModel; +} + +QString ClientModel::formatFullVersion() const +{ + return QString::fromStdString(FormatFullVersion()); +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h new file mode 100644 index 0000000000..15387056c8 --- /dev/null +++ b/src/qt/clientmodel.h @@ -0,0 +1,57 @@ +#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; + + 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 getTotalBlocksEstimate() const; + + QString formatFullVersion() const; + +private: + OptionsModel *optionsModel; + + int cachedNumConnections; + int cachedNumBlocks; + +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 diff --git a/src/qt/csvmodelwriter.cpp b/src/qt/csvmodelwriter.cpp new file mode 100644 index 0000000000..62c0b949aa --- /dev/null +++ b/src/qt/csvmodelwriter.cpp @@ -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; +} + diff --git a/src/qt/csvmodelwriter.h b/src/qt/csvmodelwriter.h new file mode 100644 index 0000000000..7367f3a6a9 --- /dev/null +++ b/src/qt/csvmodelwriter.h @@ -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 diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp new file mode 100644 index 0000000000..2b3d9bf0f0 --- /dev/null +++ b/src/qt/editaddressdialog.cpp @@ -0,0 +1,105 @@ +#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; + } + + return; + } + QDialog::accept(); +} + +QString EditAddressDialog::getAddress() const +{ + return address; +} diff --git a/src/qt/editaddressdialog.h b/src/qt/editaddressdialog.h new file mode 100644 index 0000000000..81086a45a8 --- /dev/null +++ b/src/qt/editaddressdialog.h @@ -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 diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui new file mode 100644 index 0000000000..cf7997326c --- /dev/null +++ b/src/qt/forms/aboutdialog.ui @@ -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><b>Bitcoin</b> 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> diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui new file mode 100644 index 0000000000..fb098c8280 --- /dev/null +++ b/src/qt/forms/addressbookpage.ui @@ -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>&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>&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>&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> diff --git a/src/qt/forms/editaddressdialog.ui b/src/qt/forms/editaddressdialog.ui new file mode 100644 index 0000000000..b4a4c1b1e9 --- /dev/null +++ b/src/qt/forms/editaddressdialog.ui @@ -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>&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>&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> diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui new file mode 100644 index 0000000000..cc67fae533 --- /dev/null +++ b/src/qt/forms/overviewpage.ui @@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Wallet</span></p></body></html></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><b>Recent transactions</b></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> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui new file mode 100644 index 0000000000..5b30d99e56 --- /dev/null +++ b/src/qt/forms/sendcoinsdialog.ui @@ -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>&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>&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> diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui new file mode 100644 index 0000000000..13593c2c1e --- /dev/null +++ b/src/qt/forms/sendcoinsentry.ui @@ -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&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 &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>&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> diff --git a/src/qt/forms/transactiondescdialog.ui b/src/qt/forms/transactiondescdialog.ui new file mode 100644 index 0000000000..2f70a38214 --- /dev/null +++ b/src/qt/forms/transactiondescdialog.ui @@ -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> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h new file mode 100644 index 0000000000..7fbf7fcd3a --- /dev/null +++ b/src/qt/guiconstants.h @@ -0,0 +1,17 @@ +#ifndef GUICONSTANTS_H +#define GUICONSTANTS_H + +/* milliseconds between model updates */ +static const int MODEL_UPDATE_DELAY = 500; + +/* 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 diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp new file mode 100644 index 0000000000..158b84a285 --- /dev/null +++ b/src/qt/guiutil.cpp @@ -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; +} diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h new file mode 100644 index 0000000000..bc4ddb8aae --- /dev/null +++ b/src/qt/guiutil.h @@ -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 diff --git a/src/qt/locale/bitcoin_de.ts b/src/qt/locale/bitcoin_de.ts new file mode 100644 index 0000000000..87185155ac --- /dev/null +++ b/src/qt/locale/bitcoin_de.ts @@ -0,0 +1,1155 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="de_DE"> +<defaultcodec>UTF-8</defaultcodec> +<context> + <name>AboutDialog</name> + <message> + <location filename="../forms/aboutdialog.ui" line="14"/> + <source>About Bitcoin</source> + <translation>Über Bitcoin</translation> + </message> + <message> + <location filename="../forms/aboutdialog.ui" line="53"/> + <source><b>Bitcoin</b> version</source> + <translation><b>Bitcoin</b> Version</translation> + </message> + <message> + <location filename="../forms/aboutdialog.ui" line="85"/> + <source>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.</source> + <translation>Copyright © 2009-2011 Bitcoin Developers + +Dies ist experimentelle Software. + +Veröffentlicht unter der MIT/X11 Software-Lizenz. Sie können diese in der beiligenden Datei license.txt oder unter http://www.opensource.org/licenses/mit-license.php nachlesen. + +Dieses Produkt enthält Software, welche vom OpenSSL Projekt zur Verwendung im OpenSSL Toolkit (http://www.openssl.org/) entwickelt wurde, kryptographische Software von Eric Young (eay@cryptsoft.com) und UPnP Software von Thomas-Bernard.</translation> + </message> +</context> +<context> + <name>AddressBookPage</name> + <message> + <location filename="../forms/addressbookpage.ui" line="14"/> + <source>Address Book</source> + <translation>Adressbuch</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="20"/> + <source>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.</source> + <translation>Dies sind ihre Bitcoin-Adressen zum empfangen von Zahlungen. Sie können jedem Sender eine andere Adresse mitteilen, sodass sie ihre empfangenen Zahlungen zurückverfolgen können.</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="33"/> + <source>Double-click to edit address or label</source> + <translation>Doppelklick zum Ändern der Adresse oder Beschreibung</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="57"/> + <source>Create a new address</source> + <translation>Neue Adresse erstellen</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="60"/> + <source>&New Address...</source> + <translation>&Neue Adresse...</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="71"/> + <source>Copy the currently selected address to the system clipboard</source> + <translation>Ausgewählte Adresse in die Zwischenablage kopieren</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="74"/> + <source>&Copy to Clipboard</source> + <translation>&Kopieren</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="85"/> + <source>Delete the currently selected address from the list. Only sending addresses can be deleted.</source> + <translation>Ausgewählte Adresse aus der Liste entfernen. Sie können nur die Adressen von Empfängern löschen.</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="88"/> + <source>&Delete</source> + <translation>&Löschen</translation> + </message> +</context> +<context> + <name>AddressTableModel</name> + <message> + <location filename="../addresstablemodel.cpp" line="78"/> + <source>Label</source> + <translation></translation> + </message> + <message> + <location filename="../addresstablemodel.cpp" line="78"/> + <source>Address</source> + <translation></translation> + </message> + <message> + <location filename="../addresstablemodel.cpp" line="114"/> + <source>(no label)</source> + <translation></translation> + </message> +</context> +<context> + <name>BitcoinGUI</name> + <message> + <location filename="../bitcoingui.cpp" line="50"/> + <source>Bitcoin Wallet</source> + <translation></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="112"/> + <source>Number of connections to other clients</source> + <translation>Anzahl der Verbindungen zu anderen Clients</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="117"/> + <source>Number of blocks in the block chain</source> + <translation>Anzahl der Blocks in der Block-Chain</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="120"/> + <source>Synchronizing with network...</source> + <translation>Mit Netzwerk synchronisieren...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="123"/> + <source>Block chain synchronization in progress</source> + <translation>Synchronisierung der Block-Chain...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="140"/> + <source>&Overview</source> + <translation>Übersicht</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="144"/> + <source>&Transactions</source> + <translation>Transaktionen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="148"/> + <source>&Address Book</source> + <translation>Adressbuch</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="149"/> + <source>Edit the list of stored addresses and labels</source> + <translation>Adressliste bearbeiten</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="153"/> + <source>&Receive coins</source> + <translation>Bitcoins empfangen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="154"/> + <source>Show the list of addresses for receiving payments</source> + <translation>Liste der Adressen für Überweisungen anzeigen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="158"/> + <source>&Send coins</source> + <translation>Bitcoins senden</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="159"/> + <source>Send coins to a bitcoin address</source> + <translation>Bitcoins an eine Bitcoin-Adresse senden</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="169"/> + <source>&Exit</source> + <translation>Beenden</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="170"/> + <source>Quit application</source> + <translation>Anwendung beenden</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="171"/> + <source>&About</source> + <translation>Über</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="172"/> + <source>Show information about Bitcoin</source> + <translation>Informationen über Bitcoin</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="173"/> + <source>&Options...</source> + <translation>Einstellungen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="174"/> + <source>Modify configuration options for bitcoin</source> + <translation>Einstellungen ändern</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="175"/> + <source>Open &Bitcoin</source> + <translation>Bitcoin öffnen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="176"/> + <source>Show the Bitcoin window</source> + <translation>Bitcoin-Fenster öffnen</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="177"/> + <source>&Export...</source> + <translation>Exportieren...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="178"/> + <source>Export data in current view to a file</source> + <translation>Angezeigte Daten als Datei exportieren</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="193"/> + <source>Bitcoin Wallet [testnet]</source> + <translation>Bitcoin Wallet [testnet]</translation> + </message> + <message numerus="yes"> + <location filename="../bitcoingui.cpp" line="298"/> + <source>%n connection(s)</source> + <translation> + <numerusform>%n Verbindung(en)</numerusform> + <numerusform></numerusform> + </translation> + </message> + <message numerus="yes"> + <location filename="../bitcoingui.cpp" line="317"/> + <source>%n block(s)</source> + <translation> + <numerusform>%n Blöcke</numerusform> + <numerusform></numerusform> + </translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="375"/> + <source>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?</source> + <translation>Die Größe dieser Transaktion übersteigt das Limit. + Sie können die Coins jedoch senden, wenn sie einen zusätzlichen Betrag von %1 zahlen, +welcher an die Teilnehmer des Bitcoin-Netzwerkes ausgeschüttet wird und dieses unterstützt. +Möchten Sie die zusätzliche Gebühr zahlen?</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="379"/> + <source>Sending...</source> + <translation>Senden...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="407"/> + <source>Incoming transaction</source> + <translation>Eingehende Transaktion</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="408"/> + <source>Date: </source> + <translation>Datum:</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="409"/> + <source>Amount: </source> + <translation>Betrag:</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="410"/> + <source>Type: </source> + <translation>Beschreibung:</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="411"/> + <source>Address: </source> + <translation>Adresse:</translation> + </message> +</context> +<context> + <name>EditAddressDialog</name> + <message> + <location filename="../forms/editaddressdialog.ui" line="14"/> + <source>Edit Address</source> + <translation>Adresse Bearbeiten</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="25"/> + <source>&Label</source> + <translation>Beschreibung</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="35"/> + <source>The label associated with this address book entry</source> + <translation>Name/Beschreibung für den Adressbucheintrag</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="42"/> + <source>&Address</source> + <translation>Adresse</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="52"/> + <source>The address associated with this address book entry. This can only be modified for sending addresses.</source> + <translation>Adresse für den Adressbucheintrag</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="20"/> + <source>New receiving address</source> + <translation>Neue Empfangsadresse</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="24"/> + <source>New sending address</source> + <translation>Neue Zahlungsadresse</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="27"/> + <source>Edit receiving address</source> + <translation>Empfangsadresse bearbeiten</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="31"/> + <source>Edit sending address</source> + <translation>Zahlungsadresse bearbeiten</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="89"/> + <source>The entered address "%1" is not a valid bitcoin address.</source> + <translation>Die eingegebene Adresse ("%1") ist keine gültige Bitcoin-Adresse.</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="97"/> + <source>The entered address "%1" is already in the address book.</source> + <translation>Die eingegebene Adresse ("%1") befindet sich bereits in Ihrem Adressbuch.</translation> + </message> +</context> +<context> + <name>MainOptionsPage</name> + <message> + <location filename="../optionsdialog.cpp" line="142"/> + <source>&Start Bitcoin on window system startup</source> + <translation>Bitcoin beim Start des Systems ausführen</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="143"/> + <source>Automatically start Bitcoin after the computer is turned on</source> + <translation>Bitcoin automatisch beim Systemstart ausführen</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="146"/> + <source>&Minimize to the tray instead of the taskbar</source> + <translation>In den Infobereich statt der Taskleiste minimieren</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="147"/> + <source>Show only a tray icon after minimizing the window</source> + <translation>Nur ein Icon im Infobereich anzeigen nach dem minimieren des Fensters</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="150"/> + <source>Map port using &UPnP</source> + <translation>Portweiterleitung via UPnP</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="151"/> + <source>Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source> + <translation>Öffnet den Bitcoin Client-Port automatisch auf dem Router. Dies funktioniert nur, wenn Ihr Router UPnP unterstützt und dies aktiviert ist.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="154"/> + <source>M&inimize on close</source> + <translation>Beim Schließen minimieren</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="155"/> + <source>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.</source> + <translation>Minimiert die Anwendung, wenn das Fenster geschlossen wird. Wenn dies aktiviert ist, müssen Sie das Programm über "Beenden" im Menü schließen.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="158"/> + <source>&Connect through SOCKS4 proxy:</source> + <translation>Über SOCKS4-Proxy verbinden:</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="159"/> + <source>Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor)</source> + <translation>Über SOCKS4-Proxy zum Bitcoin-Netzwerk verbinden (bspw. für eine Verbindung über Tor)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="164"/> + <source>Proxy &IP: </source> + <translation>Proxy-IP:</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="170"/> + <source>IP address of the proxy (e.g. 127.0.0.1)</source> + <translation>IP-Adresse des Proxys (z.B. 127.0.0.1)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="173"/> + <source>&Port: </source> + <translation>Port:</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="179"/> + <source>Port of the proxy (e.g. 1234)</source> + <translation>Port des Proxy-Servers (z.B. 1234)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="185"/> + <source>Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended.</source> + <translation>Zusätzliche Transaktionsgebühr per KB, welche sicherstellt dass Ihre Transaktionen schnell bearbeitet werden. Die meisten Transaktionen sind 1KB groß. Eine Gebühr von 0.01 wird empfohlen.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="191"/> + <source>Pay transaction &fee</source> + <translation>Transaktionsgebühr bezahlen</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="194"/> + <source>Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended.</source> + <translation>Zusätzliche Transaktionsgebühr per KB, welche sicherstellt dass Ihre Transaktionen schnell bearbeitet werden. Die meisten Transaktionen sind 1KB groß. Eine Gebühr von 0.01 wird empfohlen.</translation> + </message> +</context> +<context> + <name>OptionsDialog</name> + <message> + <location filename="../optionsdialog.cpp" line="54"/> + <source>Main</source> + <translation>Haupt</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="74"/> + <source>Options</source> + <translation>Einstellungen</translation> + </message> +</context> +<context> + <name>OverviewPage</name> + <message> + <location filename="../forms/overviewpage.ui" line="14"/> + <source>Form</source> + <translation></translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="38"/> + <source>Balance:</source> + <translation>Kontostand:</translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="45"/> + <source>123.456 BTC</source> + <translation></translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="52"/> + <source>Number of transactions:</source> + <translation>Anzahl der Transaktionen:</translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="59"/> + <source>0</source> + <translation></translation> + </message> + <message> + <location filename="../overviewpage.cpp" line="14"/> + <source>Your current balance</source> + <translation>Kontostand</translation> + </message> +</context> +<context> + <name>SendCoinsDialog</name> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="14"/> + <location filename="../sendcoinsdialog.cpp" line="56"/> + <location filename="../sendcoinsdialog.cpp" line="78"/> + <location filename="../sendcoinsdialog.cpp" line="84"/> + <location filename="../sendcoinsdialog.cpp" line="90"/> + <location filename="../sendcoinsdialog.cpp" line="96"/> + <source>Send Coins</source> + <translation>Bitcoins überweisen</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="27"/> + <source>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> + <translation>Adresse für die Überweisung (z.B. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="37"/> + <source>Look up adress in address book</source> + <translation>Adressbuch</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="47"/> + <source>Alt+A</source> + <translation>Alt+A</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="60"/> + <source>Paste address from system clipboard</source> + <translation>Adresse aus der Zwischenanlage einfügen</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="70"/> + <source>Alt+P</source> + <translation>Alt+P</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="82"/> + <source>A&mount:</source> + <translation>Betrag:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="95"/> + <source>Pay &To:</source> + <translation>Empfänger:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="113"/> + <source>Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> + <translation>Bitcoin-Adresse eingeben (z.B. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="131"/> + <location filename="../sendcoinsdialog.cpp" line="23"/> + <source>Enter a label for this address to add it to your address book</source> + <translation>Beschreibung der Adresse im Adressbuch</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="140"/> + <source>&Label:</source> + <translation>Beschreibung:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="183"/> + <source>Confirm the send action</source> + <translation>Überweisung bestätigen</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="186"/> + <source>&Send</source> + <translation>Überweisen</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="206"/> + <source>Abort the send action</source> + <translation>Überweisung abbrechen</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="57"/> + <source>Must fill in an amount to pay.</source> + <translation>Sie müssen einen Betrag angeben.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="65"/> + <source>Confirm send coins</source> + <translation>Überweisung bestätigen</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="66"/> + <source>Are you sure you want to send %1 BTC to %2 (%3)?</source> + <translation>Sind Sie sich sicher, %1 BTC an %2 (%3) zahlen zu wollen?</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="79"/> + <source>The recepient address is not valid, please recheck.</source> + <translation>Die Adresse des Empfängers ist nicht gültig, bitte überprüfen Sie diese.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="85"/> + <source>The amount to pay must be larger than 0.</source> + <translation>Der Transaktionsbetrag muss mehr als 0 betragen.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="91"/> + <source>Amount exceeds your balance</source> + <translation>Der Betrag übersteigt ihren Kontostand</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="97"/> + <source>Total exceeds your balance when the %1 transaction fee is included</source> + <translation>Betrag übersteigt ihren Kontostand aufgrund der Transaktionsgebühren in Höhe von %1</translation> + </message> +</context> +<context> + <name>TransactionDescDialog</name> + <message> + <location filename="../forms/transactiondescdialog.ui" line="14"/> + <source>Transaction details</source> + <translation>Transaktionsübersicht</translation> + </message> + <message> + <location filename="../forms/transactiondescdialog.ui" line="20"/> + <source>This pane shows a detailed description of the transaction</source> + <translation>Dieses Fenster zeigt ihnen eine detaillierte Übersicht der Transaktion an</translation> + </message> +</context> +<context> + <name>TransactionTableModel</name> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Date</source> + <translation>Datum</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Type</source> + <translation>Beschreibung</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Address</source> + <translation>Adresse</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Amount</source> + <translation>Betrag</translation> + </message> + <message numerus="yes"> + <location filename="../transactiontablemodel.cpp" line="272"/> + <source>Open for %n block(s)</source> + <translation> + <numerusform>Offen für %n Blöcke</numerusform> + <numerusform></numerusform> + </translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="275"/> + <source>Open until %1</source> + <translation>Offen bis %1</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="278"/> + <source>Offline (%1 confirmations)</source> + <translation>Offline (%1 Bestätigungen)</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="281"/> + <source>Unconfirmed (%1/%2 confirmations)</source> + <translation>Unbestätigt (%1/%2 Bestätigungen)</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="284"/> + <source>Confirmed (%1 confirmations)</source> + <translation>Bestätigt (%1 Bestätigungen)</translation> + </message> + <message numerus="yes"> + <location filename="../transactiontablemodel.cpp" line="293"/> + <source>Mined balance will be available in %n more blocks</source> + <translation> + <numerusform>Der geminte Betrag wird nach %n Blöcken verfügbar sein</numerusform> + <numerusform></numerusform> + </translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="299"/> + <source>This block was not received by any other nodes and will probably not be accepted!</source> + <translation>Dieser Block wurde vom Netzwerk nicht angenommen und wird wahrscheinlich nicht bestätigt werden!</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="302"/> + <source>Generated but not accepted</source> + <translation>Generiert, jedoch nicht angenommen</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="348"/> + <source>Received with</source> + <translation>Empfangen durch</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="351"/> + <source>Received from IP</source> + <translation>Empfangen durch IP</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="354"/> + <source>Sent to</source> + <translation>An</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="357"/> + <source>Sent to IP</source> + <translation>An IP</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="360"/> + <source>Payment to yourself</source> + <translation>Überweisung an Sie selbst</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="363"/> + <source>Mined</source> + <translation>Gemined</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="578"/> + <source>Transaction status. Hover over this field to show number of confirmations.</source> + <translation>Transaktionsstatus. Fahren Sie mit der Maus über dieses Feld um die Anzahl der Bestätigungen zu sehen.</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="580"/> + <source>Date and time that the transaction was received.</source> + <translation>Datum und Uhrzeit als die Transaktion empfangen wurde.</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="582"/> + <source>Type of transaction.</source> + <translation>Art der Transaktion.</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="584"/> + <source>Destination address of transaction.</source> + <translation>Empfangsadresse der Transaktion</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="586"/> + <source>Amount removed from or added to balance.</source> + <translation>Betrag vom Kontostand entfernt oder hinzugefügt</translation> + </message> +</context> +<context> + <name>TransactionView</name> + <message> + <location filename="../transactionview.cpp" line="39"/> + <location filename="../transactionview.cpp" line="52"/> + <source>All</source> + <translation>Alle</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="40"/> + <source>Today</source> + <translation>Heute</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="41"/> + <source>This week</source> + <translation>Diese Woche</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="42"/> + <source>This month</source> + <translation>Diesen Monat</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="43"/> + <source>Last month</source> + <translation>Letzten Monat</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="44"/> + <source>This year</source> + <translation>Dieses Jahr</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="45"/> + <source>Range...</source> + <translation>Bereich...</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="53"/> + <source>Received with</source> + <translation>Empfangen durch</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="55"/> + <source>Sent to</source> + <translation>An</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="57"/> + <source>To yourself</source> + <translation>Zu Ihnen selbst</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="58"/> + <source>Mined</source> + <translation>Gemined</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="59"/> + <source>Other</source> + <translation>Andere</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="208"/> + <source>Export Transaction Data</source> + <translation>Transaktionen exportieren</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="210"/> + <source>Comma separated file (*.csv)</source> + <translation></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="230"/> + <source>Error exporting</source> + <translation>Fehler beim exportieren</translation> + </message> + <message> + <location filename="../transactionview.cpp" line="230"/> + <source>Could not write to file %1.</source> + <translation>Konnte Datei %1 nicht öffnen</translation> + </message> +</context> +<context> + <name>WalletModel</name> + <message> + <location filename="../walletmodel.cpp" line="95"/> + <source>Sending...</source> + <translation>Senden...</translation> + </message> +</context> +<context> + <name>bitcoin-core</name> + <message> + <location filename="../bitcoinstrings.cpp" line="3"/> + <source>Bitcoin version</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="4"/> + <source>Usage:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="5"/> + <source>Send command to -server or bitcoind +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="6"/> + <source>List commands +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="7"/> + <source>Get help for a command +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="8"/> + <source>Options: +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="9"/> + <source>Specify configuration file (default: bitcoin.conf) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="10"/> + <source>Specify pid file (default: bitcoind.pid) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="11"/> + <source>Generate coins +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="12"/> + <source>Don't generate coins +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="13"/> + <source>Start minimized +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="14"/> + <source>Specify data directory +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="15"/> + <source>Specify connection timeout (in milliseconds) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="16"/> + <source>Connect through socks4 proxy +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="17"/> + <source>Allow DNS lookups for addnode and connect +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="18"/> + <source>Add a node to connect to +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="19"/> + <source>Connect only to the specified node +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="20"/> + <source>Don't accept connections from outside +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="21"/> + <source>Don't attempt to use UPnP to map the listening port +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="22"/> + <source>Attempt to use UPnP to map the listening port +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="23"/> + <source>Fee per KB to add to transactions you send +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="24"/> + <source>Accept command line and JSON-RPC commands +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="25"/> + <source>Run in the background as a daemon and accept commands +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="26"/> + <source>Use the test network +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="27"/> + <source>Username for JSON-RPC connections +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="28"/> + <source>Password for JSON-RPC connections +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="29"/> + <source>Listen for JSON-RPC connections on <port> (default: 8332) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="30"/> + <source>Allow JSON-RPC connections from specified IP address +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="31"/> + <source>Send commands to node running on <ip> (default: 127.0.0.1) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="32"/> + <source>Set key pool size to <n> (default: 100) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="33"/> + <source>Rescan the block chain for missing wallet transactions +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="34"/> + <source> +SSL options: (see the Bitcoin Wiki for SSL setup instructions) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="37"/> + <source>Use OpenSSL (https) for JSON-RPC connections +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="38"/> + <source>Server certificate file (default: server.cert) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="39"/> + <source>Server private key (default: server.pem) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="40"/> + <source>Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="43"/> + <source>This help message +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="44"/> + <source>Cannot obtain a lock on data directory %s. Bitcoin is probably already running.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="47"/> + <source>Error loading addr.dat +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="48"/> + <source>Error loading blkindex.dat +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="49"/> + <source>Error loading wallet.dat +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="50"/> + <source>Invalid -proxy address</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="51"/> + <source>Invalid amount for -paytxfee=<amount></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="52"/> + <source>Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="55"/> + <source>Warning: Disk space is low </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="56"/> + <source>Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="59"/> + <source>Error: Transaction creation failed </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="60"/> + <source>Sending...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="61"/> + <source>Error: 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.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="65"/> + <source>Invalid amount</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="66"/> + <source>Insufficient funds</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="67"/> + <source>Invalid bitcoin address</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="68"/> + <source>Unable to bind to port %d on this computer. Bitcoin is probably already running.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="71"/> + <source>To use the %s option</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="72"/> + <source>Warning: %s, you must set rpcpassword=<password> +in the configuration file: %s +If the file does not exist, create it with owner-readable-only file permissions. +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="77"/> + <source>You must set rpcpassword=<password> in the configuration file: +%s +If the file does not exist, create it with owner-readable-only file permissions.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="82"/> + <source>Warning: Please check that your computer's date and time are correct. If your clock is wrong Bitcoin will not work properly.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="85"/> + <source>-beta</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/src/qt/locale/bitcoin_nl.ts b/src/qt/locale/bitcoin_nl.ts new file mode 100644 index 0000000000..afe0dc89e2 --- /dev/null +++ b/src/qt/locale/bitcoin_nl.ts @@ -0,0 +1,1441 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="nl_NL"> +<defaultcodec>UTF-8</defaultcodec> +<context> + <name>AboutDialog</name> + <message> + <location filename="../forms/aboutdialog.ui" line="14"/> + <source>About Bitcoin</source> + <translation>Over Bitcoin +</translation> + </message> + <message> + <location filename="../forms/aboutdialog.ui" line="53"/> + <source><b>Bitcoin</b> version</source> + <translation><b>Bitcoin</b> versie</translation> + </message> + <message> + <location filename="../forms/aboutdialog.ui" line="85"/> + <source>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.</source> + <translation>Copyright © 2009-2011 Bitcoin-ontwikkelaars + +Dit is experimentele software. + +Gedistributeerd onder de MIT/X11 software licentie, zie het bijgevoegde bestand +license.txt of kijk op http://www.opensource.org/licenses/mit-license.php. + +Dit product bevat software ontwikkeld door het OpenSSL project for gebruik +in de OpenSSL Toolkit (http://www.openssl.org/), en cryptografische +software geschreven door Eric Young (eay@cryptsoft.com)) en UPnP software geschreven +door Thomas Bernard. +</translation> + </message> +</context> +<context> + <name>AddressBookDialog</name> + <message> + <source>Address Book</source> + <translation type="obsolete">Adresboek</translation> + </message> + <message> + <source>Sending</source> + <translation type="obsolete">Versturen</translation> + </message> + <message> + <source>Receiving</source> + <translation type="obsolete">Ontvangen</translation> + </message> + <message> + <source>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. The highlighted address is displayed in the main window.</source> + <translation type="obsolete">Dit zijn je bitcoin-adressen voor het ontvangen van betalingen. Het is een goed idee iedere afzender een ander adres te geven zodat je bij kunt houden wie je een betaling stuurt. Het geselecteerde adres is zichtbaar in het hoofdscherm.</translation> + </message> + <message> + <source>Create a new address</source> + <translation type="obsolete">Voeg een nieuw adres toe</translation> + </message> + <message> + <source>&New Address...</source> + <translation type="obsolete">&Nieuw adres...</translation> + </message> + <message> + <source>Copy the currently selected address to the system clipboard</source> + <translation type="obsolete">Kopieer het geselecteerde adres naar het plakbord</translation> + </message> + <message> + <source>&Copy to Clipboard</source> + <translation type="obsolete">&Kopieer naar plakbord</translation> + </message> + <message> + <source>Edit the currently selected address</source> + <translation type="obsolete">Bewerk het geselecteerde adres</translation> + </message> + <message> + <source>&Edit...</source> + <translation type="obsolete">&Bewerken...</translation> + </message> + <message> + <source>Delete the currently selected address from the list. Only sending addresses can be deleted.</source> + <translation type="obsolete">Verwijder het geselecteerde adres. Alleen verstuur-adressen kunnen worden verwijderd.</translation> + </message> + <message> + <source>&Delete</source> + <translation type="obsolete">&Verwijderen</translation> + </message> +</context> +<context> + <name>AddressBookPage</name> + <message> + <location filename="../forms/addressbookpage.ui" line="14"/> + <source>Address Book</source> + <translation type="unfinished">Adresboek</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="20"/> + <source>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.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="33"/> + <source>Double-click to edit address or label</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="57"/> + <source>Create a new address</source> + <translation type="unfinished">Voeg een nieuw adres toe</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="60"/> + <source>&New Address...</source> + <translation type="unfinished">&Nieuw adres...</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="71"/> + <source>Copy the currently selected address to the system clipboard</source> + <translation type="unfinished">Kopieer het geselecteerde adres naar het plakbord</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="74"/> + <source>&Copy to Clipboard</source> + <translation type="unfinished">&Kopieer naar plakbord</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="85"/> + <source>Delete the currently selected address from the list. Only sending addresses can be deleted.</source> + <translation type="unfinished">Verwijder het geselecteerde adres. Alleen verstuur-adressen kunnen worden verwijderd.</translation> + </message> + <message> + <location filename="../forms/addressbookpage.ui" line="88"/> + <source>&Delete</source> + <translation type="unfinished">&Verwijderen</translation> + </message> +</context> +<context> + <name>AddressTableModel</name> + <message> + <location filename="../addresstablemodel.cpp" line="78"/> + <source>Label</source> + <translation>Label</translation> + </message> + <message> + <location filename="../addresstablemodel.cpp" line="78"/> + <source>Address</source> + <translation>Adres</translation> + </message> + <message> + <location filename="../addresstablemodel.cpp" line="114"/> + <source>(no label)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Default receiving address</source> + <translation type="obsolete">Standaard ontvangst-adres</translation> + </message> +</context> +<context> + <name>BitcoinGUI</name> + <message> + <source>Bitcoin</source> + <translation type="obsolete">Bitcoin</translation> + </message> + <message> + <source>Your Bitcoin address:</source> + <translation type="obsolete">Uw bitcoin-adres:</translation> + </message> + <message> + <source>Your current default receiving address</source> + <translation type="obsolete">Uw huidig standaard ontvangst-adres</translation> + </message> + <message> + <source>&New...</source> + <translation type="obsolete">&Nieuw...</translation> + </message> + <message> + <source>Create new receiving address</source> + <translation type="obsolete">Maak nieuw ontvangst-adres</translation> + </message> + <message> + <source>&Copy to clipboard</source> + <translation type="obsolete">&Kopieer naar plakbord</translation> + </message> + <message> + <source>Copy current receiving address to the system clipboard</source> + <translation type="obsolete">Kopieer huidig ontvangst-adres naar plakbord</translation> + </message> + <message> + <source>Balance:</source> + <translation type="obsolete">Saldo:</translation> + </message> + <message> + <source>Your current balance</source> + <translation type="obsolete">Uw huidige saldo</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="112"/> + <source>Number of connections to other clients</source> + <translation>Aantal verbindingen met andere clients</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="117"/> + <source>Number of blocks in the block chain</source> + <translation>Aantal blokken in de block-chain</translation> + </message> + <message> + <source>Number of transactions in your wallet</source> + <translation type="obsolete">Aantal transacties in je portefeuille</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="120"/> + <source>Synchronizing with network...</source> + <translation>Synchroniseren met netwerk...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="123"/> + <source>Block chain synchronization in progress</source> + <translation>Bezig met blokken-database-synchronisatie</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="169"/> + <source>&Exit</source> + <translation>A&fsluiten</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="170"/> + <source>Quit application</source> + <translation>De applicatie afsluiten</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="158"/> + <source>&Send coins</source> + <translation>&Verstuur coins</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="159"/> + <source>Send coins to a bitcoin address</source> + <translation>Coins versturen naar een bitcoin-adres</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="148"/> + <source>&Address Book</source> + <translation>&Adresboek</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="50"/> + <source>Bitcoin Wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="140"/> + <source>&Overview</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="144"/> + <source>&Transactions</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="149"/> + <source>Edit the list of stored addresses and labels</source> + <translation>Bewerk de lijst van adressen en labels</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="153"/> + <source>&Receive coins</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="154"/> + <source>Show the list of addresses for receiving payments</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="171"/> + <source>&About</source> + <translation>&Over Bitcoin</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="172"/> + <source>Show information about Bitcoin</source> + <translation>Laat informatie zien over Bitcoin</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="177"/> + <source>&Export...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="178"/> + <source>Export data in current view to a file</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="193"/> + <source>[testnet]</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="408"/> + <source>Date: </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="409"/> + <source>Amount: </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="410"/> + <source>Type: </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="411"/> + <source>Address: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Your &Receiving Addresses...</source> + <translation type="obsolete">&Uw ontvangstadressen...</translation> + </message> + <message> + <source>Show the list of receiving addresses and edit their labels</source> + <translation type="obsolete">Laat de lijst zien van ontvangst-adressen en bewerk de labels</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="173"/> + <source>&Options...</source> + <translation>&Opties...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="174"/> + <source>Modify configuration options for bitcoin</source> + <translation>Verander instellingen van Bitcoin</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="175"/> + <source>Open &Bitcoin</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="176"/> + <source>Show the Bitcoin window</source> + <translation>Laat het Bitcoin-venster zien</translation> + </message> + <message> + <source>All transactions</source> + <translation type="obsolete">Alle transacties</translation> + </message> + <message> + <source>Sent/Received</source> + <translation type="obsolete">Verzonden/Ontvangen</translation> + </message> + <message> + <source>Sent</source> + <translation type="obsolete">Verzonden</translation> + </message> + <message> + <source>Received</source> + <translation type="obsolete">Ontvangen</translation> + </message> + <message numerus="yes"> + <location filename="../bitcoingui.cpp" line="298"/> + <source>%n connection(s)</source> + <translation> + <numerusform>%n verbinding</numerusform> + <numerusform>%n verbindingen</numerusform> + </translation> + </message> + <message numerus="yes"> + <location filename="../bitcoingui.cpp" line="317"/> + <source>%n block(s)</source> + <translation> + <numerusform>%n blok</numerusform> + <numerusform>%n blokken</numerusform> + </translation> + </message> + <message numerus="yes"> + <source>%n transaction(s)</source> + <translation type="obsolete"> + <numerusform>%n transactie</numerusform> + <numerusform>%n transacties</numerusform> + </translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="375"/> + <source>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?</source> + <translation>Deze transactie overschrijdt de limiet. Om de transactie alsnog te verwerken kun je een fooi betalen van %1. Deze zal betaald worden aan de node die uw transactie verwerkt. Wil je doorgaan en deze fooi betalen?</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="379"/> + <source>Sending...</source> + <translation>Versturen...</translation> + </message> + <message> + <location filename="../bitcoingui.cpp" line="407"/> + <source>Incoming transaction</source> + <translation>Binnenkomende transactie</translation> + </message> +</context> +<context> + <name>ClientModel</name> + <message> + <source>Sending...</source> + <translation type="obsolete">Versturen...</translation> + </message> +</context> +<context> + <name>EditAddressDialog</name> + <message> + <location filename="../forms/editaddressdialog.ui" line="14"/> + <source>Edit Address</source> + <translation>Bewerk Adres</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="25"/> + <source>&Label</source> + <translation>&Label</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="42"/> + <source>&Address</source> + <translation>&Adres</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="35"/> + <source>The label associated with this address book entry</source> + <translation>Het label dat is geassocieerd met dit adres</translation> + </message> + <message> + <location filename="../forms/editaddressdialog.ui" line="52"/> + <source>The address associated with this address book entry. This can only be modified for sending addresses.</source> + <translation>Het adres dat is geassocieerd met de label. Dit kan alleen worden veranderd voor verzend-adressen.</translation> + </message> + <message> + <source>Set as default receiving address</source> + <translation type="obsolete">Stel in as standaard ontvangst-adres</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="20"/> + <source>New receiving address</source> + <translation>Nieuw ontvangst-adres</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="24"/> + <source>New sending address</source> + <translation>Nieuw verzend-adres</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="27"/> + <source>Edit receiving address</source> + <translation>Bewerk ontvangst-adres</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="31"/> + <source>Edit sending address</source> + <translation>Bewerk ontvangst-adres</translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="89"/> + <source>The entered address "%1" is not a valid bitcoin address.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../editaddressdialog.cpp" line="97"/> + <source>The entered address "%1" is already in the address book.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>The address %1 is already in the address book.</source> + <translation type="obsolete">Het adres %1 staat al in het adresboek.</translation> + </message> +</context> +<context> + <name>MainOptionsPage</name> + <message> + <location filename="../optionsdialog.cpp" line="142"/> + <source>&Start Bitcoin on window system startup</source> + <translation>&Start Bitcoin wanneer het systeem opstart</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="143"/> + <source>Automatically start Bitcoin after the computer is turned on</source> + <translation>Start Bitcoin automatisch wanneer het systeem start</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="146"/> + <source>&Minimize to the tray instead of the taskbar</source> + <translation>&Minimaliseer tot systeemvak in plaats van de taakbalk</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="147"/> + <source>Show only a tray icon after minimizing the window</source> + <translation>Laat alleen een systeemvak-icoon zien wanneer het venster geminimaliseerd is</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="150"/> + <source>Map port using &UPnP</source> + <translation>Portmapping via &UPnP</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="151"/> + <source>Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source> + <translation>Open de Bitcoin-poort automatisch op de router. Dit werkt alleen als de router UPnP ondersteunt.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="154"/> + <source>M&inimize on close</source> + <translation>&Minimaliseer bij sluiten van het venster</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="155"/> + <source>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.</source> + <translation>Minimiliseer het venster in de plaats van de applicatie af te sluiten als het venster gesloten wordt. Wanneer deze optie aan staan, kan de applicatie alleen worden afgesloten door Afsluiten te kiezen in het menu.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="158"/> + <source>&Connect through SOCKS4 proxy:</source> + <translation>&Verbind via socks4 proxy: </translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="159"/> + <source>Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor)</source> + <translation>Verbind met het Bitcoin-netwerk door een SOCKS4 proxy (bijvoorbeeld Tor)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="164"/> + <source>Proxy &IP: </source> + <translation>Proxy &IP:</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="170"/> + <source>IP address of the proxy (e.g. 127.0.0.1)</source> + <translation>IP adres van de proxy (bijv. 127.0.0.1)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="173"/> + <source>&Port: </source> + <translation>&Poort:</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="179"/> + <source>Port of the proxy (e.g. 1234)</source> + <translation>Poort waarop de proxy luistert (bijv. 1234)</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="185"/> + <source>Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended.</source> + <translation>Optionele transactiefooi per KB die helpt ervoor zorgen dat uw transacties snel verwerkt worden. De meeste transacties zijn 1KB. Fooi 0.01 is aangeraden.</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="191"/> + <source>Pay transaction &fee</source> + <translation>Transactie&fooi</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="194"/> + <source>Optional transaction fee per KB that helps make sure your transactions are processed quickly. Most transactions are 1KB. Fee 0.01 recommended.</source> + <translation>Optionele transactiefooi per KB die helpt ervoor zorgen dat uw transacties snel verwerkt worden. De meeste transacties zijn 1KB. Fooi 0.01 is aangeraden.</translation> + </message> +</context> +<context> + <name>OptionsDialog</name> + <message> + <location filename="../optionsdialog.cpp" line="54"/> + <source>Main</source> + <translation>Algemeen</translation> + </message> + <message> + <location filename="../optionsdialog.cpp" line="74"/> + <source>Options</source> + <translation>Opties</translation> + </message> +</context> +<context> + <name>OverviewPage</name> + <message> + <location filename="../forms/overviewpage.ui" line="14"/> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="38"/> + <source>Balance:</source> + <translation type="unfinished">Saldo:</translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="45"/> + <source>123.456 BTC</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="52"/> + <source>Number of transactions:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/overviewpage.ui" line="59"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../overviewpage.cpp" line="14"/> + <source>Your current balance</source> + <translation type="unfinished">Uw huidige saldo</translation> + </message> +</context> +<context> + <name>SendCoinsDialog</name> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="14"/> + <location filename="../sendcoinsdialog.cpp" line="56"/> + <location filename="../sendcoinsdialog.cpp" line="78"/> + <location filename="../sendcoinsdialog.cpp" line="84"/> + <location filename="../sendcoinsdialog.cpp" line="90"/> + <location filename="../sendcoinsdialog.cpp" line="96"/> + <source>Send Coins</source> + <translation>Verstuur coins</translation> + </message> + <message> + <source>&Amount:</source> + <translation type="obsolete">&Hoeveelheid:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="95"/> + <source>Pay &To:</source> + <translation>Betaal &aan:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="27"/> + <source>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> + <translation>Voer een bitcoin-adres (bijvoorbeeld: 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="47"/> + <source>Alt+A</source> + <translation>Alt+A</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="60"/> + <source>Paste address from system clipboard</source> + <translation>Plak adres uit systeemplakbord</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="131"/> + <location filename="../sendcoinsdialog.cpp" line="23"/> + <source>Enter a label for this address to add it to your address book</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="140"/> + <source>&Label:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>&Paste</source> + <translation type="obsolete">&Plakken</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="37"/> + <source>Look up adress in address book</source> + <translation>Opzoeken in adresboek</translation> + </message> + <message> + <source>Address &Book...</source> + <translation type="obsolete">Adres&boek....</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="70"/> + <source>Alt+P</source> + <translation>Alt+P</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="82"/> + <source>A&mount:</source> + <translation>&Hoeveelheid:</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="113"/> + <source>Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> + <translation>Voer een bitcoin-adres in (bijvoorbeeld: 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> + </message> + <message> + <source>Add specified destination address to address book</source> + <translation type="obsolete">Voeg het ingevoerde doel-adres toe aan het adresboek</translation> + </message> + <message> + <source>A&dd to address book as</source> + <translation type="obsolete">&Voeg toe aan adresboek als</translation> + </message> + <message> + <source>Label to add address as</source> + <translation type="obsolete">Label om toe te kennen aan adres in adresboek</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="183"/> + <source>Confirm the send action</source> + <translation>Bevestig de zend-transactie</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="186"/> + <source>&Send</source> + <translation>&Versturen</translation> + </message> + <message> + <location filename="../forms/sendcoinsdialog.ui" line="206"/> + <source>Abort the send action</source> + <translation>De transactie annuleren</translation> + </message> + <message> + <source>The amount to pay must be a valid number.</source> + <translation type="obsolete">Het ingevoerde bedrag is geen geldig getal.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="57"/> + <source>Must fill in an amount to pay.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="65"/> + <source>Confirm send coins</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="66"/> + <source>Are you sure you want to send %1 BTC to %2 (%3)?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="79"/> + <source>The recepient address is not valid, please recheck.</source> + <translation>Het verzendadres is niet geld.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="85"/> + <source>The amount to pay must be larger than 0.</source> + <translation>Het ingevoerde gedrag moet groter zijn dan 0.</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="91"/> + <source>Amount exceeds your balance</source> + <translation>Hoeveelheid overschrijdt uw huidige balans</translation> + </message> + <message> + <location filename="../sendcoinsdialog.cpp" line="97"/> + <source>Total exceeds your balance when the %1 transaction fee is included</source> + <translation>Totaal overschrijdt uw huidige balans wanneer de %1 transactiefooi is meegerekend</translation> + </message> +</context> +<context> + <name>TransactionDescDialog</name> + <message> + <location filename="../forms/transactiondescdialog.ui" line="14"/> + <source>Transaction details</source> + <translation>Transactiedetails</translation> + </message> + <message> + <location filename="../forms/transactiondescdialog.ui" line="20"/> + <source>This pane shows a detailed description of the transaction</source> + <translation>Dit venster laat een uitgebreide beschrijving van de transactie zien</translation> + </message> +</context> +<context> + <name>TransactionTableModel</name> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Date</source> + <translation>Datum</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Type</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Address</source> + <translation type="unfinished">Adres</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="211"/> + <source>Amount</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="278"/> + <source>Offline (%1 confirmations)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="281"/> + <source>Unconfirmed (%1/%2 confirmations)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="284"/> + <source>Confirmed (%1 confirmations)</source> + <translation type="unfinished"></translation> + </message> + <message numerus="yes"> + <location filename="../transactiontablemodel.cpp" line="293"/> + <source>Mined balance will be available in %n more blocks</source> + <translation type="unfinished"> + <numerusform></numerusform> + <numerusform></numerusform> + </translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="299"/> + <source>This block was not received by any other nodes and will probably not be accepted!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="302"/> + <source>Generated but not accepted</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="348"/> + <source>Received with</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="351"/> + <source>Received from IP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="354"/> + <source>Sent to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="357"/> + <source>Sent to IP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="363"/> + <source>Mined</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="582"/> + <source>Type of transaction.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="584"/> + <source>Destination address of transaction.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="586"/> + <source>Amount removed from or added to balance.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Description</source> + <translation type="obsolete">Beschrijving</translation> + </message> + <message> + <source>Debit</source> + <translation type="obsolete">Debet</translation> + </message> + <message> + <source>Credit</source> + <translation type="obsolete">Credit</translation> + </message> + <message numerus="yes"> + <location filename="../transactiontablemodel.cpp" line="272"/> + <source>Open for %n block(s)</source> + <translation> + <numerusform>Open gedurende %n blok</numerusform> + <numerusform>Open gedurende %n blokken</numerusform> + </translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="275"/> + <source>Open until %1</source> + <translation>Open tot %1</translation> + </message> + <message> + <source>Offline (%1)</source> + <translation type="obsolete">Offline (%1)</translation> + </message> + <message> + <source>Unconfirmed (%1/%2)</source> + <translation type="obsolete">Niet bevestigd (%1/%2)</translation> + </message> + <message> + <source>Confirmed (%1)</source> + <translation type="obsolete">Bevestigd (%1)</translation> + </message> + <message> + <source>Received with: </source> + <translation type="obsolete">Ontvangen met:</translation> + </message> + <message> + <source>Received from IP: </source> + <translation type="obsolete">Ontvangen van IP:</translation> + </message> + <message> + <source>Sent to: </source> + <translation type="obsolete">Verzonden aan:</translation> + </message> + <message> + <source>Sent to IP: </source> + <translation type="obsolete">Verzonden aan IP:</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="360"/> + <source>Payment to yourself</source> + <translation>Betaling aan uzelf</translation> + </message> + <message numerus="yes"> + <source>Generated (matures in %n more blocks)</source> + <translation type="obsolete"> + <numerusform>Gegenereerd (wordt volwassen na %n blok)</numerusform> + <numerusform>Gegenereerd (wordt volwassen na %n blokken)</numerusform> + </translation> + </message> + <message> + <source>Generated</source> + <translation type="obsolete">Gegenereerd</translation> + </message> + <message> + <source>Generated - Warning: This block was not received by any other nodes and will probably not be accepted!</source> + <translation type="obsolete">Gegenereerd - Waarschuwing: Dit blok is niet ontvangen door andere nodes en zal waarschijnlijk niet geaccepteerd worden!</translation> + </message> + <message> + <source>Generated (not accepted)</source> + <translation type="obsolete">Gegenereerd (niet geaccepteerd)</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="578"/> + <source>Transaction status. Hover over this field to show number of confirmations.</source> + <translation>Transactiestatus. Hou de muiscursor boven dit veld om het aantal bevestigingen te laten zien.</translation> + </message> + <message> + <location filename="../transactiontablemodel.cpp" line="580"/> + <source>Date and time that the transaction was received.</source> + <translation>Datum en tijd waarop deze transactie is ontvangen.</translation> + </message> + <message> + <source>Short description of the transaction.</source> + <translation type="obsolete">Korte beschrijving van de transactie.</translation> + </message> + <message> + <source>Amount removed from balance.</source> + <translation type="obsolete">Bedrag afgeschreven van saldo.</translation> + </message> + <message> + <source>Amount added to balance.</source> + <translation type="obsolete">Bedrag bijgeschreven bij saldo.</translation> + </message> +</context> +<context> + <name>TransactionView</name> + <message> + <location filename="../transactionview.cpp" line="39"/> + <location filename="../transactionview.cpp" line="52"/> + <source>All</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="40"/> + <source>Today</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="41"/> + <source>This week</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="42"/> + <source>This month</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="43"/> + <source>Last month</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="44"/> + <source>This year</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="45"/> + <source>Range...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="53"/> + <source>Received with</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="55"/> + <source>Sent to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="57"/> + <source>To yourself</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="58"/> + <source>Mined</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="59"/> + <source>Other</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="209"/> + <source>Export Transaction Data</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="211"/> + <source>Comma separated file (*.csv)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="231"/> + <source>Error exporting</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../transactionview.cpp" line="231"/> + <source>Could not write to file %1.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>WalletModel</name> + <message> + <location filename="../walletmodel.cpp" line="95"/> + <source>Sending...</source> + <translation type="unfinished">Versturen...</translation> + </message> +</context> +<context> + <name>bitcoin-core</name> + <message> + <location filename="../bitcoinstrings.cpp" line="3"/> + <source>Bitcoin version</source> + <translation>Bitcoin</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="4"/> + <source>Usage:</source> + <translation>Gebruik:</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="5"/> + <source>Send command to -server or bitcoind +</source> + <translation>Zend commando naar -server of bitcoind +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="6"/> + <source>List commands +</source> + <translation>List van commando's +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="7"/> + <source>Get help for a command +</source> + <translation>Toon hulp voor een commando +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="8"/> + <source>Options: +</source> + <translation>Opties: +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="9"/> + <source>Specify configuration file (default: bitcoin.conf) +</source> + <translation>Specifieer configuratiebestand (standaard: bitcoin.conf) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="10"/> + <source>Specify pid file (default: bitcoind.pid) +</source> + <translation>Specifieer pid-bestand (standaard: bitcoind.pid) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="11"/> + <source>Generate coins +</source> + <translation>Genereer coins +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="12"/> + <source>Don't generate coins +</source> + <translation>Genereer geen coins +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="13"/> + <source>Start minimized +</source> + <translation>Geminimaliseerd starten +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="14"/> + <source>Specify data directory +</source> + <translation>Stel datamap in +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="15"/> + <source>Specify connection timeout (in milliseconds) +</source> + <translation>Gelieve de time-out tijd te specifieren (in milliseconden) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="16"/> + <source>Connect through socks4 proxy +</source> + <translation>Verbind via socks4 proxy +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="17"/> + <source>Allow DNS lookups for addnode and connect +</source> + <translation>Sta DNS-opzoeking toe voor addnode en connect +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="18"/> + <source>Add a node to connect to +</source> + <translation>Voeg een node toe om mee te verbinden +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="19"/> + <source>Connect only to the specified node +</source> + <translation>Verbind alleen met deze node +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="20"/> + <source>Don't accept connections from outside +</source> + <translation>Sta geen verbindingen van buitenaf toe +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="21"/> + <source>Don't attempt to use UPnP to map the listening port +</source> + <translation>Probeer geen UPnP te gebruiken om de poort waarop geluisterd wordt te mappen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="22"/> + <source>Attempt to use UPnP to map the listening port +</source> + <translation>Probeer UPnP te gebruiken om de poort waarop geluisterd wordt te mappen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="23"/> + <source>Fee per KB to add to transactions you send +</source> + <translation>Fooi per KB om aan transacties die gezonden worden toe te voegen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="24"/> + <source>Accept command line and JSON-RPC commands +</source> + <translation>Aanvaard commandolijn en JSON-RPC commando's +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="25"/> + <source>Run in the background as a daemon and accept commands +</source> + <translation>Draai in de achtergrond als daemon en aanvaard commando's +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="26"/> + <source>Use the test network +</source> + <translation>Gebruik het test-netwerk +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="27"/> + <source>Username for JSON-RPC connections +</source> + <translation>Gebruikersnaam voor JSON-RPC verbindingen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="28"/> + <source>Password for JSON-RPC connections +</source> + <translation>Wachtwoord voor JSON-RPC verbindingen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="29"/> + <source>Listen for JSON-RPC connections on <port> (default: 8332) +</source> + <translation>Luister voor JSON-RPC verbindingen op <poort> (standaard: 8332) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="30"/> + <source>Allow JSON-RPC connections from specified IP address +</source> + <translation>Enkel JSON-RPC verbindingen van opgegeven IP adres toestaan +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="31"/> + <source>Send commands to node running on <ip> (default: 127.0.0.1) +</source> + <translation>Zend commando's naar proces dat op <ip> draait (standaard: 127.0.0.1) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="32"/> + <source>Set key pool size to <n> (default: 100) +</source> + <translation>Stel sleutelpoelgrootte in op <n> (standaard: 100) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="33"/> + <source>Rescan the block chain for missing wallet transactions +</source> + <translation>Doorzoek de blokken database op ontbrekende portefeuille-transacties +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="34"/> + <source> +SSL options: (see the Bitcoin Wiki for SSL setup instructions) +</source> + <translation> +SSL opties: (zie de Bitcoin wiki voor SSL instructies) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="37"/> + <source>Use OpenSSL (https) for JSON-RPC connections +</source> + <translation>Gebruik OpenSSL (https) voor JSON-RPC verbindingen +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="38"/> + <source>Server certificate file (default: server.cert) +</source> + <translation>Certificaat-bestand voor server (standaard: server.cert) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="39"/> + <source>Server private key (default: server.pem) +</source> + <translation>Geheime sleutel voor server (standaard: server.pem) +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="40"/> + <source>Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) +</source> + <translation>Aanvaardbare ciphers (standaard: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH)</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="43"/> + <source>This help message +</source> + <translation>Dit helpbericht +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="44"/> + <source>Cannot obtain a lock on data directory %s. Bitcoin is probably already running.</source> + <translation>Kan geen lock op de gegevensdirectory %s verkrijgen. Bitcoin draait vermoedelijk reeds.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="47"/> + <source>Error loading addr.dat +</source> + <translation>Fout bij laden van bestand addr.dat +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="48"/> + <source>Error loading blkindex.dat +</source> + <translation>Fout bij laden van bestand addr.dat +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="49"/> + <source>Error loading wallet.dat +</source> + <translation>Fout bij laden van bestand wallet.dat +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="50"/> + <source>Invalid -proxy address</source> + <translation>Foutief -proxy adres</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="51"/> + <source>Invalid amount for -paytxfee=<amount></source> + <translation>Ongeldig bedrag voor -paytxfee=<bedrag></translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="52"/> + <source>Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction.</source> + <translation>Waarschuwing: -paytxfee is zeer hoog ingesteld. Dit is de fooi die betaald wordt bij het zenden van een transactie.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="55"/> + <source>Warning: Disk space is low </source> + <translation>Waarschuwing: Weinig schijfruimte over </translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="56"/> + <source>Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds </source> + <translation>Fout: Deze transactie vergt een fooi van ten minste %s omwille van zijn bedrag, complexiteit, of gebruik van recent ontvangen coins </translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="59"/> + <source>Error: Transaction creation failed </source> + <translation>Fout: Aanmaken van transactie mislukt </translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="60"/> + <source>Sending...</source> + <translation>Versturen...</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="61"/> + <source>Error: 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.</source> + <translation>Fout: De transactie is afgekeurd. Dit kan gebeuren als bepaalde coins in je Portefeuille al zijn uitgegeven. Dit kan veroorzaakt worden doordat je een kopie van wallet.dat gebruikt hebt en enkel daar je uitgave geregistreerd is.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="65"/> + <source>Invalid amount</source> + <translation>Foutieve hoeveelheid</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="66"/> + <source>Insufficient funds</source> + <translation>Onvoldoende saldo</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="67"/> + <source>Invalid bitcoin address</source> + <translation>Foutief bitcoin-adres</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="68"/> + <source>Unable to bind to port %d on this computer. Bitcoin is probably already running.</source> + <translation>Kan niet binden met poort %d op deze computer. Bitcoin draait vermoedelijk reeds.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="71"/> + <source>To use the %s option</source> + <translation>Om de %s optie te gebruiken</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="72"/> + <source>Warning: %s, you must set rpcpassword=<password> +in the configuration file: %s +If the file does not exist, create it with owner-readable-only file permissions. +</source> + <translation>Waarschuwing: %s, rpcpassword=<password> moet ingesteld zijn +in het configuratie bestand: %s +Als het bestand nog niet bestaat, maak het dan aan met enkel-leesbaar-door-eigenaar rechten. +</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="77"/> + <source>You must set rpcpassword=<password> in the configuration file: +%s +If the file does not exist, create it with owner-readable-only file permissions.</source> + <translation>rpcpassword=<password> moet ingesteld in het configuratie bestand: +%s +Als het bestand nog niet bestaat, maak het dan aan met enkel-leesbaar-door-eigenaar rechten.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="82"/> + <source>Warning: Please check that your computer's date and time are correct. If your clock is wrong Bitcoin will not work properly.</source> + <translation>Waarschuwing: Controleer of uw computers datum en tijd correct ingesteld zijn. Als uw klok fout staat zal Bitcoin niet correct werken.</translation> + </message> + <message> + <location filename="../bitcoinstrings.cpp" line="85"/> + <source>-beta</source> + <translation>-beta</translation> + </message> +</context> +</TS> diff --git a/src/qt/monitoreddatamapper.cpp b/src/qt/monitoreddatamapper.cpp new file mode 100644 index 0000000000..88948d07bf --- /dev/null +++ b/src/qt/monitoreddatamapper.cpp @@ -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); + } +} diff --git a/src/qt/monitoreddatamapper.h b/src/qt/monitoreddatamapper.h new file mode 100644 index 0000000000..4dd2d1a86a --- /dev/null +++ b/src/qt/monitoreddatamapper.h @@ -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 diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp new file mode 100644 index 0000000000..0eeb6f86a5 --- /dev/null +++ b/src/qt/optionsdialog.cpp @@ -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); +} diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h new file mode 100644 index 0000000000..d5238a3664 --- /dev/null +++ b/src/qt/optionsdialog.h @@ -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 diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp new file mode 100644 index 0000000000..efc216dab8 --- /dev/null +++ b/src/qt/optionsmodel.cpp @@ -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; +} diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h new file mode 100644 index 0000000000..7f489c5014 --- /dev/null +++ b/src/qt/optionsmodel.h @@ -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 diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp new file mode 100644 index 0000000000..7bade9a755 --- /dev/null +++ b/src/qt/overviewpage.cpp @@ -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(); +} diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h new file mode 100644 index 0000000000..4b4cc922ca --- /dev/null +++ b/src/qt/overviewpage.h @@ -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 diff --git a/src/qt/qtwin.cpp b/src/qt/qtwin.cpp new file mode 100644 index 0000000000..bb029bba24 --- /dev/null +++ b/src/qt/qtwin.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#include "qtwin.h" +#include <QLibrary> +#include <QApplication> +#include <QWidget> +#include <QList> +#include <QPointer> + +#ifdef Q_WS_WIN + +#include <qt_windows.h> + +// Blur behind data structures +#define DWM_BB_ENABLE 0x00000001 // fEnable has been specified +#define DWM_BB_BLURREGION 0x00000002 // hRgnBlur has been specified +#define DWM_BB_TRANSITIONONMAXIMIZED 0x00000004 // fTransitionOnMaximized has been specified +#define WM_DWMCOMPOSITIONCHANGED 0x031E // Composition changed window message + +typedef struct _DWM_BLURBEHIND +{ + DWORD dwFlags; + BOOL fEnable; + HRGN hRgnBlur; + BOOL fTransitionOnMaximized; +} DWM_BLURBEHIND, *PDWM_BLURBEHIND; + +typedef struct _MARGINS +{ + int cxLeftWidth; + int cxRightWidth; + int cyTopHeight; + int cyBottomHeight; +} MARGINS, *PMARGINS; + +typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled); +typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS* pMarInset); +typedef HRESULT (WINAPI *PtrDwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind); +typedef HRESULT (WINAPI *PtrDwmGetColorizationColor)(DWORD *pcrColorization, BOOL *pfOpaqueBlend); + +static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled= 0; +static PtrDwmEnableBlurBehindWindow pDwmEnableBlurBehindWindow = 0; +static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0; +static PtrDwmGetColorizationColor pDwmGetColorizationColor = 0; + + +/* + * Internal helper class that notifies windows if the + * DWM compositing state changes and updates the widget + * flags correspondingly. + */ +class WindowNotifier : public QWidget +{ +public: + WindowNotifier() { winId(); } + void addWidget(QWidget *widget) { widgets.append(widget); } + void removeWidget(QWidget *widget) { widgets.removeAll(widget); } + bool winEvent(MSG *message, long *result); + +private: + QWidgetList widgets; +}; + +static bool resolveLibs() +{ + if (!pDwmIsCompositionEnabled) { + QLibrary dwmLib(QString::fromAscii("dwmapi")); + pDwmIsCompositionEnabled =(PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled"); + pDwmExtendFrameIntoClientArea = (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea"); + pDwmEnableBlurBehindWindow = (PtrDwmEnableBlurBehindWindow)dwmLib.resolve("DwmEnableBlurBehindWindow"); + pDwmGetColorizationColor = (PtrDwmGetColorizationColor)dwmLib.resolve("DwmGetColorizationColor"); + } + return pDwmIsCompositionEnabled != 0; +} + +#endif + +/*! + * Chekcs and returns true if Windows DWM composition + * is currently enabled on the system. + * + * To get live notification on the availability of + * this feature, you will currently have to + * reimplement winEvent() on your widget and listen + * for the WM_DWMCOMPOSITIONCHANGED event to occur. + * + */ +bool QtWin::isCompositionEnabled() +{ +#ifdef Q_WS_WIN + if (resolveLibs()) { + HRESULT hr = S_OK; + BOOL isEnabled = false; + hr = pDwmIsCompositionEnabled(&isEnabled); + if (SUCCEEDED(hr)) + return isEnabled; + } +#endif + return false; +} + +/*! + * Enables Blur behind on a Widget. + * + * \a enable tells if the blur should be enabled or not + */ +bool QtWin::enableBlurBehindWindow(QWidget *widget, bool enable) +{ + Q_ASSERT(widget); + bool result = false; +#ifdef Q_WS_WIN + if (resolveLibs()) { + DWM_BLURBEHIND bb = {0}; + HRESULT hr = S_OK; + bb.fEnable = enable; + bb.dwFlags = DWM_BB_ENABLE; + bb.hRgnBlur = NULL; + widget->setAttribute(Qt::WA_TranslucentBackground, enable); + widget->setAttribute(Qt::WA_NoSystemBackground, enable); + hr = pDwmEnableBlurBehindWindow(widget->winId(), &bb); + if (SUCCEEDED(hr)) { + result = true; + windowNotifier()->addWidget(widget); + } + } +#endif + return result; +} + +/*! + * ExtendFrameIntoClientArea. + * + * This controls the rendering of the frame inside the window. + * Note that passing margins of -1 (the default value) will completely + * remove the frame from the window. + * + * \note you should not call enableBlurBehindWindow before calling + * this functions + * + * \a enable tells if the blur should be enabled or not + */ +bool QtWin::extendFrameIntoClientArea(QWidget *widget, int left, int top, int right, int bottom) +{ + + Q_ASSERT(widget); + Q_UNUSED(left); + Q_UNUSED(top); + Q_UNUSED(right); + Q_UNUSED(bottom); + + bool result = false; +#ifdef Q_WS_WIN + if (resolveLibs()) { + QLibrary dwmLib(QString::fromAscii("dwmapi")); + HRESULT hr = S_OK; + MARGINS m = {left, top, right, bottom}; + hr = pDwmExtendFrameIntoClientArea(widget->winId(), &m); + if (SUCCEEDED(hr)) { + result = true; + windowNotifier()->addWidget(widget); + } + widget->setAttribute(Qt::WA_TranslucentBackground, result); + } +#endif + return result; +} + +/*! + * Returns the current colorizationColor for the window. + * + * \a enable tells if the blur should be enabled or not + */ +QColor QtWin::colorizatinColor() +{ + QColor resultColor = QApplication::palette().window().color(); + +#ifdef Q_WS_WIN + if (resolveLibs()) { + DWORD color = 0; + BOOL opaque = FALSE; + QLibrary dwmLib(QString::fromAscii("dwmapi")); + HRESULT hr = S_OK; + hr = pDwmGetColorizationColor(&color, &opaque); + if (SUCCEEDED(hr)) + resultColor = QColor(color); + } +#endif + return resultColor; +} + +#ifdef Q_WS_WIN +WindowNotifier *QtWin::windowNotifier() +{ + static WindowNotifier *windowNotifierInstance = 0; + if (!windowNotifierInstance) + windowNotifierInstance = new WindowNotifier; + return windowNotifierInstance; +} + + +/* Notify all enabled windows that the DWM state changed */ +bool WindowNotifier::winEvent(MSG *message, long *result) +{ + if (message && message->message == WM_DWMCOMPOSITIONCHANGED) { + bool compositionEnabled = QtWin::isCompositionEnabled(); + foreach(QWidget * widget, widgets) { + if (widget) { + widget->setAttribute(Qt::WA_NoSystemBackground, compositionEnabled); + } + widget->update(); + } + } + return QWidget::winEvent(message, result); +} +#endif diff --git a/src/qt/qtwin.h b/src/qt/qtwin.h new file mode 100644 index 0000000000..4008c7fa28 --- /dev/null +++ b/src/qt/qtwin.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#ifndef QTWIN_H +#define QTWIN_H + +#include <QColor> +#include <QWidget> +/** + * This is a helper class for using the Desktop Window Manager + * functionality on Windows 7 and Windows Vista. On other platforms + * these functions will simply not do anything. + */ + +class WindowNotifier; + +class QtWin +{ +public: + static bool enableBlurBehindWindow(QWidget *widget, bool enable = true); + static bool extendFrameIntoClientArea(QWidget *widget, + int left = -1, int top = -1, + int right = -1, int bottom = -1); + static bool isCompositionEnabled(); + static QColor colorizatinColor(); + +private: + static WindowNotifier *windowNotifier(); +}; + +#endif // QTWIN_H diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp new file mode 100644 index 0000000000..8ca230c9d7 --- /dev/null +++ b/src/qt/qvalidatedlineedit.cpp @@ -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(); +} diff --git a/src/qt/qvalidatedlineedit.h b/src/qt/qvalidatedlineedit.h new file mode 100644 index 0000000000..f7b9486a6e --- /dev/null +++ b/src/qt/qvalidatedlineedit.h @@ -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 diff --git a/src/qt/qvaluecombobox.cpp b/src/qt/qvaluecombobox.cpp new file mode 100644 index 0000000000..c0ad8c12e5 --- /dev/null +++ b/src/qt/qvaluecombobox.cpp @@ -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(); +} diff --git a/src/qt/qvaluecombobox.h b/src/qt/qvaluecombobox.h new file mode 100644 index 0000000000..2a3533da9e --- /dev/null +++ b/src/qt/qvaluecombobox.h @@ -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 diff --git a/src/qt/res/bitcoin-qt.rc b/src/qt/res/bitcoin-qt.rc new file mode 100644 index 0000000000..1a1ab53be4 --- /dev/null +++ b/src/qt/res/bitcoin-qt.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icons/bitcoin.ico" diff --git a/src/qt/res/icons/add.png b/src/qt/res/icons/add.png Binary files differnew file mode 100644 index 0000000000..f98e2a8ca7 --- /dev/null +++ b/src/qt/res/icons/add.png diff --git a/src/qt/res/icons/address-book.png b/src/qt/res/icons/address-book.png Binary files differnew file mode 100644 index 0000000000..1086fbeb63 --- /dev/null +++ b/src/qt/res/icons/address-book.png diff --git a/src/qt/res/icons/bitcoin.ico b/src/qt/res/icons/bitcoin.ico Binary files differnew file mode 100644 index 0000000000..01afd3c6da --- /dev/null +++ b/src/qt/res/icons/bitcoin.ico diff --git a/src/qt/res/icons/bitcoin.png b/src/qt/res/icons/bitcoin.png Binary files differnew file mode 100644 index 0000000000..bf43144e5e --- /dev/null +++ b/src/qt/res/icons/bitcoin.png diff --git a/src/qt/res/icons/bitcoin_testnet.png b/src/qt/res/icons/bitcoin_testnet.png Binary files differnew file mode 100644 index 0000000000..ee2dc40563 --- /dev/null +++ b/src/qt/res/icons/bitcoin_testnet.png diff --git a/src/qt/res/icons/clock1.png b/src/qt/res/icons/clock1.png Binary files differnew file mode 100644 index 0000000000..448e47f947 --- /dev/null +++ b/src/qt/res/icons/clock1.png diff --git a/src/qt/res/icons/clock2.png b/src/qt/res/icons/clock2.png Binary files differnew file mode 100644 index 0000000000..c1a6e99f7f --- /dev/null +++ b/src/qt/res/icons/clock2.png diff --git a/src/qt/res/icons/clock3.png b/src/qt/res/icons/clock3.png Binary files differnew file mode 100644 index 0000000000..e429a402cf --- /dev/null +++ b/src/qt/res/icons/clock3.png diff --git a/src/qt/res/icons/clock4.png b/src/qt/res/icons/clock4.png Binary files differnew file mode 100644 index 0000000000..ba036f47d3 --- /dev/null +++ b/src/qt/res/icons/clock4.png diff --git a/src/qt/res/icons/clock5.png b/src/qt/res/icons/clock5.png Binary files differnew file mode 100644 index 0000000000..411d7a78a0 --- /dev/null +++ b/src/qt/res/icons/clock5.png diff --git a/src/qt/res/icons/configure.png b/src/qt/res/icons/configure.png Binary files differnew file mode 100644 index 0000000000..95bd319ce1 --- /dev/null +++ b/src/qt/res/icons/configure.png diff --git a/src/qt/res/icons/connect0_16.png b/src/qt/res/icons/connect0_16.png Binary files differnew file mode 100644 index 0000000000..66f3ae4f86 --- /dev/null +++ b/src/qt/res/icons/connect0_16.png diff --git a/src/qt/res/icons/connect1_16.png b/src/qt/res/icons/connect1_16.png Binary files differnew file mode 100644 index 0000000000..76000beee2 --- /dev/null +++ b/src/qt/res/icons/connect1_16.png diff --git a/src/qt/res/icons/connect2_16.png b/src/qt/res/icons/connect2_16.png Binary files differnew file mode 100644 index 0000000000..6d9a37281a --- /dev/null +++ b/src/qt/res/icons/connect2_16.png diff --git a/src/qt/res/icons/connect3_16.png b/src/qt/res/icons/connect3_16.png Binary files differnew file mode 100644 index 0000000000..a211700785 --- /dev/null +++ b/src/qt/res/icons/connect3_16.png diff --git a/src/qt/res/icons/connect4_16.png b/src/qt/res/icons/connect4_16.png Binary files differnew file mode 100644 index 0000000000..e2fe97d496 --- /dev/null +++ b/src/qt/res/icons/connect4_16.png diff --git a/src/qt/res/icons/edit.png b/src/qt/res/icons/edit.png Binary files differnew file mode 100644 index 0000000000..1d69145151 --- /dev/null +++ b/src/qt/res/icons/edit.png diff --git a/src/qt/res/icons/editcopy.png b/src/qt/res/icons/editcopy.png Binary files differnew file mode 100644 index 0000000000..f882aa2ad8 --- /dev/null +++ b/src/qt/res/icons/editcopy.png diff --git a/src/qt/res/icons/editpaste.png b/src/qt/res/icons/editpaste.png Binary files differnew file mode 100644 index 0000000000..a192060bdd --- /dev/null +++ b/src/qt/res/icons/editpaste.png diff --git a/src/qt/res/icons/export.png b/src/qt/res/icons/export.png Binary files differnew file mode 100644 index 0000000000..69d59a38d2 --- /dev/null +++ b/src/qt/res/icons/export.png diff --git a/src/qt/res/icons/history.png b/src/qt/res/icons/history.png Binary files differnew file mode 100644 index 0000000000..60f1351783 --- /dev/null +++ b/src/qt/res/icons/history.png diff --git a/src/qt/res/icons/notsynced.png b/src/qt/res/icons/notsynced.png Binary files differnew file mode 100644 index 0000000000..c9e71184c5 --- /dev/null +++ b/src/qt/res/icons/notsynced.png diff --git a/src/qt/res/icons/overview.png b/src/qt/res/icons/overview.png Binary files differnew file mode 100644 index 0000000000..6b94b43a2c --- /dev/null +++ b/src/qt/res/icons/overview.png diff --git a/src/qt/res/icons/quit.png b/src/qt/res/icons/quit.png Binary files differnew file mode 100644 index 0000000000..0dde6f395c --- /dev/null +++ b/src/qt/res/icons/quit.png diff --git a/src/qt/res/icons/receive.png b/src/qt/res/icons/receive.png Binary files differnew file mode 100644 index 0000000000..e8f418a4f8 --- /dev/null +++ b/src/qt/res/icons/receive.png diff --git a/src/qt/res/icons/remove.png b/src/qt/res/icons/remove.png Binary files differnew file mode 100644 index 0000000000..a44b6d130b --- /dev/null +++ b/src/qt/res/icons/remove.png diff --git a/src/qt/res/icons/send.png b/src/qt/res/icons/send.png Binary files differnew file mode 100644 index 0000000000..55ce550b4f --- /dev/null +++ b/src/qt/res/icons/send.png diff --git a/src/qt/res/icons/synced.png b/src/qt/res/icons/synced.png Binary files differnew file mode 100644 index 0000000000..8e428b6a70 --- /dev/null +++ b/src/qt/res/icons/synced.png diff --git a/src/qt/res/icons/toolbar.png b/src/qt/res/icons/toolbar.png Binary files differnew file mode 100644 index 0000000000..84c18e29fa --- /dev/null +++ b/src/qt/res/icons/toolbar.png diff --git a/src/qt/res/icons/toolbar_testnet.png b/src/qt/res/icons/toolbar_testnet.png Binary files differnew file mode 100644 index 0000000000..90ed6d99f4 --- /dev/null +++ b/src/qt/res/icons/toolbar_testnet.png diff --git a/src/qt/res/icons/transaction0.png b/src/qt/res/icons/transaction0.png Binary files differnew file mode 100644 index 0000000000..4578666ee4 --- /dev/null +++ b/src/qt/res/icons/transaction0.png diff --git a/src/qt/res/icons/transaction2.png b/src/qt/res/icons/transaction2.png Binary files differnew file mode 100644 index 0000000000..01bb558a10 --- /dev/null +++ b/src/qt/res/icons/transaction2.png diff --git a/src/qt/res/icons/tx_inout.png b/src/qt/res/icons/tx_inout.png Binary files differnew file mode 100644 index 0000000000..5f092f97aa --- /dev/null +++ b/src/qt/res/icons/tx_inout.png diff --git a/src/qt/res/icons/tx_input.png b/src/qt/res/icons/tx_input.png Binary files differnew file mode 100644 index 0000000000..0f5fea3a84 --- /dev/null +++ b/src/qt/res/icons/tx_input.png diff --git a/src/qt/res/icons/tx_mined.png b/src/qt/res/icons/tx_mined.png Binary files differnew file mode 100644 index 0000000000..613f30fecc --- /dev/null +++ b/src/qt/res/icons/tx_mined.png diff --git a/src/qt/res/icons/tx_output.png b/src/qt/res/icons/tx_output.png Binary files differnew file mode 100644 index 0000000000..9ae39fb329 --- /dev/null +++ b/src/qt/res/icons/tx_output.png diff --git a/src/qt/res/images/about.png b/src/qt/res/images/about.png Binary files differnew file mode 100644 index 0000000000..c9ab9511ef --- /dev/null +++ b/src/qt/res/images/about.png diff --git a/src/qt/res/images/splash2.jpg b/src/qt/res/images/splash2.jpg Binary files differnew file mode 100644 index 0000000000..3846e6f68d --- /dev/null +++ b/src/qt/res/images/splash2.jpg diff --git a/src/qt/res/movies/update_spinner.mng b/src/qt/res/movies/update_spinner.mng Binary files differnew file mode 100644 index 0000000000..7df3baac6f --- /dev/null +++ b/src/qt/res/movies/update_spinner.mng diff --git a/src/qt/res/src/bitcoin.svg b/src/qt/res/src/bitcoin.svg new file mode 100644 index 0000000000..96f10178a2 --- /dev/null +++ b/src/qt/res/src/bitcoin.svg @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg2" + version="1.1" + inkscape:version="0.48.0 r9654" + width="256" + height="256" + sodipodi:docname="bitcoin.svg" + inkscape:export-filename="/store/orion/projects/bitcoin/BC_Logo_icon32.png" + inkscape:export-xdpi="11.25" + inkscape:export-ydpi="11.25" + style="display:inline"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6"> + <linearGradient + id="linearGradient3800"> + <stop + style="stop-color:#fbba6c;stop-opacity:1;" + offset="0" + id="stop3802" /> + <stop + style="stop-color:#f89825;stop-opacity:1;" + offset="1" + id="stop3804" /> + </linearGradient> + <filter + inkscape:collect="always" + id="filter3788"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="7.7328864" + id="feGaussianBlur3790" /> + </filter> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3800" + id="radialGradient3806" + cx="137.01819" + cy="136.81316" + fx="137.01819" + fy="136.81316" + r="120.22619" + gradientTransform="matrix(1,0,0,0.99768745,0,0.29784356)" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1204" + inkscape:window-height="972" + id="namedview4" + showgrid="false" + inkscape:zoom="1" + inkscape:cx="-54.927212" + inkscape:cy="128" + inkscape:window-x="238" + inkscape:window-y="143" + inkscape:window-maximized="0" + inkscape:current-layer="layer1" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:nodetypes="sssssssssssscssssscsssscscssssssssssscscsssssssscscssssscssscssssssscsssssssss" + inkscape:connector-curvature="0" + id="path2986" + d="M 111.77934,251.6677 C 67.147549,243.82973 30.788461,211.94464 17.111244,168.64858 7.1011053,136.9609 11.409199,100.0445 28.433065,71.630835 45.23974,43.579693 71.151762,24.409692 103.78036,15.888094 c 12.1652,-3.177178 33.00458,-3.99623 46.6577,-1.833789 45.59908,7.22219 82.56109,39.080427 96.48709,83.164011 10.01013,31.687684 5.70204,68.604084 -11.32183,97.017744 -16.8025,28.04418 -42.64131,47.16473 -75.34729,55.75648 -7.52834,1.97768 -11.60054,2.41566 -24.95273,2.68377 -10.90952,0.21906 -18.34974,-0.0999 -23.52396,-1.00861 z m 20.17619,-31.35425 c 0.63075,-2.39846 2.14458,-8.28561 3.36407,-13.08254 1.56481,-6.15521 2.69488,-8.60563 3.84031,-8.32722 0.89267,0.21696 7.39067,0.68131 14.43998,1.03189 16.31195,0.81124 23.9944,-1.14509 31.43693,-8.00537 7.63877,-7.04117 11.30337,-15.63267 11.82635,-27.72636 0.37592,-8.69327 0.10152,-10.44523 -2.27687,-14.53617 -1.48738,-2.55836 -5.17935,-6.48313 -8.20438,-8.7217 l -5.50006,-4.07013 4.37131,-1.87838 c 9.83682,-4.22697 14.47489,-11.84516 15.1401,-24.86816 0.41587,-8.14174 0.12573,-9.95645 -2.32406,-14.536178 -3.41249,-6.379435 -11.7129,-12.875301 -21.81801,-17.074696 -4.15735,-1.727672 -7.66196,-3.187464 -7.78803,-3.243983 -0.12607,-0.05651 1.28534,-6.088871 3.13645,-13.405229 1.85112,-7.316357 3.26253,-13.372021 3.13645,-13.457031 -0.12607,-0.08501 -2.84573,-0.778731 -6.04368,-1.541604 -3.19796,-0.762872 -6.6666,-1.640092 -7.70811,-1.949379 -1.54868,-0.459905 -2.47321,1.773902 -5.07541,12.262964 -2.215,8.928331 -3.69157,12.825303 -4.85951,12.825303 -2.30435,0 -12.01076,-2.489633 -12.01076,-3.080685 0,-0.263336 1.37739,-5.975631 3.06085,-12.693987 1.68345,-6.718356 2.78086,-12.49516 2.43868,-12.837341 -1.06646,-1.066455 -13.66473,-4.07084 -14.61292,-3.484827 -0.49474,0.305758 -2.13899,5.806491 -3.65393,12.223852 -1.51493,6.417361 -3.0693,12.177423 -3.45416,12.800138 -0.42537,0.68826 -6.84309,-0.416984 -16.36715,-2.818708 -8.617084,-2.173005 -15.989649,-3.583696 -16.383486,-3.13487 -0.393848,0.448826 -1.334199,3.432558 -2.089673,6.630515 -0.755474,3.197957 -1.623352,6.656241 -1.928612,7.685075 -0.438573,1.478093 0.970946,2.194444 6.717302,3.413902 7.407149,1.571906 12.549199,5.114005 12.549199,8.644526 0,3.559986 -18.770806,76.736553 -20.092504,78.329103 -1.007682,1.21419 -2.807318,1.40957 -7.196833,0.78135 -11.551776,-1.65329 -10.897277,-1.95681 -14.648875,6.7937 -1.878049,4.38054 -3.41465,8.32832 -3.41465,8.77286 0,0.44453 6.14881,2.29784 13.664008,4.11848 7.515199,1.82065 14.792647,3.72851 16.172114,4.23971 l 2.508106,0.92945 -3.31877,13.11716 c -1.825323,7.21442 -3.215633,13.21726 -3.089563,13.33965 0.549874,0.53382 15.058297,4.01313 15.382497,3.68892 0.19792,-0.19791 1.74688,-5.83164 3.44215,-12.51942 1.69526,-6.68778 3.30839,-12.52544 3.58472,-12.97256 0.443,-0.71676 11.73522,1.68597 12.76544,2.7162 0.21981,0.21981 -1.08467,6.26629 -2.89884,13.43664 l -3.29849,13.03696 4.8136,1.23571 c 2.6475,0.67965 5.33693,1.42786 5.97652,1.66271 3.63321,1.33406 5.19855,0.40079 6.2902,-3.75024 z m 0.68716,-43.88623 c -0.63959,-0.21166 -5.21848,-1.29512 -10.17531,-2.40768 -4.95684,-1.11255 -9.01243,-2.23295 -9.01243,-2.48975 0,-0.65492 8.11353,-33.1852 8.69895,-34.87745 0.34957,-1.01046 3.32218,-0.62569 10.96233,1.41895 12.84914,3.43866 18.00073,5.54314 23.11146,9.44128 8.35514,6.37277 10.49752,14.36232 5.75489,21.46161 -3.94917,5.91153 -9.15813,8.03413 -19.45529,7.92788 -4.79693,-0.0496 -9.245,-0.26317 -9.8846,-0.47484 z m 2.50991,-54.7082 c -3.73627,-0.93479 -7.21128,-1.958 -7.72221,-2.27376 -0.72057,-0.44534 6.3168,-31.740173 7.3756,-32.798964 0.64276,-0.642776 19.09068,4.708848 22.8588,6.631196 2.34875,1.19824 5.92162,4.059214 7.93975,6.357724 2.94919,3.358944 3.66929,5.201504 3.66929,9.388734 0,4.31035 -0.68714,5.95944 -3.98073,9.55341 -3.93586,4.2948 -4.08991,4.34657 -13.664,4.59252 -5.49512,0.14117 -12.62142,-0.48635 -16.4765,-1.45086 z" + style="opacity:0.8176923;fill:#000000;fill-opacity:1;filter:url(#filter3788)" /> + </g> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="Front" + style="display:inline"> + <path + style="fill:url(#radialGradient3806);fill-opacity:1;display:inline" + d="M 107.77934,247.6677 C 63.147549,239.82973 26.788461,207.94464 13.111244,164.64858 3.1011053,132.9609 7.4091993,96.044496 24.433065,67.630835 41.23974,39.579693 67.151762,20.409692 99.780356,11.888094 111.94556,8.7109163 132.78494,7.8918643 146.43806,10.054305 c 45.59908,7.22219 82.56109,39.080427 96.48709,83.164011 10.01013,31.687684 5.70204,68.604084 -11.32183,97.017744 -16.8025,28.04418 -42.64131,47.16473 -75.34729,55.75648 -7.52834,1.97768 -11.60054,2.41566 -24.95273,2.68377 -10.90952,0.21906 -18.34974,-0.0999 -23.52396,-1.00861 z m 20.17619,-31.35425 c 0.63075,-2.39846 2.14458,-8.28561 3.36407,-13.08254 1.56481,-6.15521 2.69488,-8.60563 3.84031,-8.32722 0.89267,0.21696 7.39067,0.68131 14.43998,1.03189 16.31195,0.81124 23.9944,-1.14509 31.43693,-8.00537 7.63877,-7.04117 11.30337,-15.63267 11.82635,-27.72636 0.37592,-8.69327 0.10152,-10.44523 -2.27687,-14.53617 -1.48738,-2.55836 -5.17935,-6.48313 -8.20438,-8.7217 l -5.50006,-4.07013 4.37131,-1.87838 c 9.83682,-4.22697 14.47489,-11.84516 15.1401,-24.86816 0.41587,-8.141744 0.12573,-9.956454 -2.32406,-14.536178 -3.41249,-6.379435 -11.7129,-12.875301 -21.81801,-17.074696 -4.15735,-1.727672 -7.66196,-3.187464 -7.78803,-3.243983 -0.12607,-0.05651 1.28534,-6.088871 3.13645,-13.405229 1.85112,-7.316357 3.26253,-13.372021 3.13645,-13.457031 -0.12607,-0.08501 -2.84573,-0.778731 -6.04368,-1.541604 -3.19796,-0.762872 -6.6666,-1.640092 -7.70811,-1.949379 -1.54868,-0.459905 -2.47321,1.773902 -5.07541,12.262964 -2.215,8.928331 -3.69157,12.825303 -4.85951,12.825303 -2.30435,0 -12.01076,-2.489633 -12.01076,-3.080685 0,-0.263336 1.37739,-5.975631 3.06085,-12.693987 1.68345,-6.718356 2.78086,-12.49516 2.43868,-12.837341 -1.06646,-1.066455 -13.66473,-4.07084 -14.61292,-3.484827 -0.49474,0.305758 -2.13899,5.806491 -3.65393,12.223852 -1.51493,6.417361 -3.0693,12.177423 -3.45416,12.800138 -0.42537,0.68826 -6.84309,-0.416984 -16.36715,-2.818708 -8.617084,-2.173005 -15.989649,-3.583696 -16.383486,-3.13487 -0.393848,0.448826 -1.334199,3.432558 -2.089673,6.630515 -0.755474,3.197957 -1.623352,6.656241 -1.928612,7.685075 -0.438573,1.478093 0.970946,2.194444 6.717302,3.413902 7.407145,1.571906 12.549199,5.114005 12.549199,8.644526 0,3.559986 -18.770806,76.736553 -20.092504,78.329103 -1.007682,1.21419 -2.807318,1.40957 -7.196833,0.78135 -11.551776,-1.65329 -10.897277,-1.95681 -14.648875,6.7937 -1.878049,4.38054 -3.41465,8.32832 -3.41465,8.77286 0,0.44453 6.14881,2.29784 13.664008,4.11848 7.515199,1.82065 14.792647,3.72851 16.172114,4.23971 l 2.508106,0.92945 -3.31877,13.11716 c -1.825323,7.21442 -3.215633,13.21726 -3.089563,13.33965 0.549874,0.53382 15.058293,4.01313 15.382493,3.68892 0.19792,-0.19791 1.74688,-5.83164 3.442154,-12.51942 1.69526,-6.68778 3.30839,-12.52544 3.58472,-12.97256 0.443,-0.71676 11.73522,1.68597 12.76544,2.7162 0.21981,0.21981 -1.08467,6.26629 -2.89884,13.43664 l -3.29849,13.03696 4.8136,1.23571 c 2.6475,0.67965 5.33693,1.42786 5.97652,1.66271 3.63321,1.33406 5.19855,0.40079 6.2902,-3.75024 z m 0.68716,-43.88623 c -0.63959,-0.21166 -5.21848,-1.29512 -10.17531,-2.40768 -4.95684,-1.11255 -9.01243,-2.23295 -9.01243,-2.48975 0,-0.65492 8.11353,-33.1852 8.69895,-34.87745 0.34957,-1.01046 3.32218,-0.62569 10.96233,1.41895 12.84914,3.43866 18.00073,5.54314 23.11146,9.44128 8.35514,6.37277 10.49752,14.36232 5.75489,21.46161 -3.94917,5.91153 -9.15813,8.03413 -19.45529,7.92788 -4.79693,-0.0496 -9.245,-0.26317 -9.8846,-0.47484 z m 2.50991,-54.7082 c -3.73627,-0.93479 -7.21128,-1.958 -7.72221,-2.27376 -0.72057,-0.44534 6.3168,-31.740173 7.3756,-32.798964 0.64276,-0.642776 19.09068,4.708848 22.8588,6.631196 2.34875,1.19824 5.92162,4.059214 7.93975,6.357724 2.94919,3.35894 3.66929,5.201504 3.66929,9.388734 0,4.31035 -0.68714,5.95944 -3.98073,9.55341 -3.93586,4.2948 -4.08991,4.34657 -13.664,4.59252 -5.49512,0.14117 -12.62142,-0.48635 -16.4765,-1.45086 z" + id="path2987" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssssssssscssssscsssscscssssssssssscscsssssssscscssssscssscssssssscsssssssss" + inkscape:export-xdpi="11.98" + inkscape:export-ydpi="11.98" /> + </g> +</svg> diff --git a/src/qt/res/src/clock1.svg b/src/qt/res/src/clock1.svg new file mode 100644 index 0000000000..793dc7f91c --- /dev/null +++ b/src/qt/res/src/clock1.svg @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock0.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#ff84a6;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#ff0a4d;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#909090;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#3c3c3c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#ff8282;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#ff1919;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="0.31530906" + inkscape:cy="12.478576" + inkscape:current-layer="layer4" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + sodipodi:insensitive="true" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 14.4287,3.6919089 A 7.7781744,7.7781744 0 1 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="5.675432" + sodipodi:end="10.999096" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/clock2.svg b/src/qt/res/src/clock2.svg new file mode 100644 index 0000000000..6a78adf700 --- /dev/null +++ b/src/qt/res/src/clock2.svg @@ -0,0 +1,262 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock0.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#ff848d;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#ff0a1b;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#909090;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#3c3c3c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#ffae82;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#ff6a19;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="10.569917" + inkscape:cy="10.828475" + inkscape:current-layer="layer5" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 15.197954,11.184912 A 7.7781744,7.7781744 0 1 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="0.4031442" + sodipodi:end="4.7159107" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/clock3.svg b/src/qt/res/src/clock3.svg new file mode 100644 index 0000000000..09ccc2549f --- /dev/null +++ b/src/qt/res/src/clock3.svg @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock1.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#ffa184;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#ff440a;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#909090;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#3c3c3c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#ffda82;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#ffbb19;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="23.894737" + inkscape:cx="16" + inkscape:cy="-1" + inkscape:current-layer="layer5" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 8.2135533,15.909749 A 7.7781744,7.7781744 0 1 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="1.5489111" + sodipodi:end="4.7159107" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/clock4.svg b/src/qt/res/src/clock4.svg new file mode 100644 index 0000000000..7d9dc37acb --- /dev/null +++ b/src/qt/res/src/clock4.svg @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock2.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#ffcc84;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#ff9a0a;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#909090;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#3c3c3c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#f8ff82;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#f1ff19;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="8.6111742" + inkscape:cy="6.6684704" + inkscape:current-layer="layer5" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 1.7005474,12.635546 A 7.7781744,7.7781744 0 0 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="2.5243203" + sodipodi:end="4.7159107" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/clock5.svg b/src/qt/res/src/clock5.svg new file mode 100644 index 0000000000..9fd58d9d97 --- /dev/null +++ b/src/qt/res/src/clock5.svg @@ -0,0 +1,262 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock3.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#fff884;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#fff10a;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#909090;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#3c3c3c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#ccff82;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#a0ff19;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16" + inkscape:cx="8.6111742" + inkscape:cy="6.6684704" + inkscape:current-layer="layer3" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 1.0968082,4.6340519 A 7.7781744,7.7781744 0 0 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="3.6082438" + sodipodi:end="4.7159107" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/clock_green.svg b/src/qt/res/src/clock_green.svg new file mode 100644 index 0000000000..e31f0e7995 --- /dev/null +++ b/src/qt/res/src/clock_green.svg @@ -0,0 +1,262 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2987" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="clock_green.svg"> + <defs + id="defs2989"> + <linearGradient + id="linearGradient4465"> + <stop + id="stop4467" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4469" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient4424"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4426" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="1" + id="stop4428" /> + </linearGradient> + <linearGradient + id="linearGradient4357"> + <stop + id="stop4359" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#baff84;stop-opacity:0.49803922;" + offset="0.36363637" + id="stop4473" /> + <stop + id="stop4361" + offset="1" + style="stop-color:#76ff0a;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient4347"> + <stop + id="stop4349" + offset="0" + style="stop-color:#c1c1c1;stop-opacity:1;" /> + <stop + id="stop4351" + offset="1" + style="stop-color:#8c8c8c;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3767"> + <stop + style="stop-color:#82ff82;stop-opacity:1;" + offset="0" + id="stop3769" /> + <stop + style="stop-color:#19ff19;stop-opacity:1;" + offset="1" + id="stop3771" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4347" + id="linearGradient3779" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter4339"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.3240906" + id="feGaussianBlur4341" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3767" + id="linearGradient4345" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4424" + id="linearGradient4430" + x1="10.740074" + y1="16.148634" + x2="6.3055735" + y2="-1.2798394" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4357" + id="radialGradient4440" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.5712985,0.01074232,-0.01353758,1.9801676,-4.5655476,-0.68355868)" + cx="8.1975746" + cy="-0.080271922" + fx="8.1975746" + fy="-0.080271922" + r="7.7781744" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4465" + id="linearGradient4471" + gradientUnits="userSpaceOnUse" + x1="2.224874" + y1="2.8301363" + x2="14.038582" + y2="13.171574" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="8" + inkscape:cx="9.1870806" + inkscape:cy="14.089546" + inkscape:current-layer="layer3" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="Shadow" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient3779);fill-opacity:1;stroke:none;display:inline;filter:url(#filter4339)" + id="path2997" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + transform="translate(0,-0.08838835)" /> + </g> + <g + id="layer1" + inkscape:label="Clock" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + d="m 15.821514,8.1334372 a 7.7781744,7.7781744 0 1 1 -15.55634867,0 7.7781744,7.7781744 0 1 1 15.55634867,0 z" + sodipodi:ry="7.7781744" + sodipodi:rx="7.7781744" + sodipodi:cy="8.1334372" + sodipodi:cx="8.0433397" + id="path4343" + style="fill:url(#linearGradient4345);fill-opacity:1;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer5" + inkscape:label="Block" + sodipodi:insensitive="true" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:url(#linearGradient4471);fill-opacity:1;stroke:none;display:inline" + id="path4462" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.7781744" + sodipodi:ry="7.7781744" + d="M 14.4287,3.6919089 A 7.7781744,7.7781744 0 1 1 8.0707322,0.35531099 L 8.0433397,8.1334372 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" + sodipodi:start="5.675432" + sodipodi:end="10.999096" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Ticks" + style="display:inline" + sodipodi:insensitive="true"> + <path + id="use4309" + transform="matrix(0.77323696,-0.44642857,0.44642857,0.77323696,-1.9715899,5.5529328)" + style="fill:url(#linearGradient4430);fill-opacity:1;stroke:none" + d="M 8.875,2.03125 C 8.875,2.5317581 8.4832492,2.9375 8,2.9375 7.5167508,2.9375 7.125,2.5317581 7.125,2.03125 7.125,1.5307419 7.5167508,1.125 8,1.125 c 0.4832492,0 0.875,0.4057419 0.875,0.90625 z M 5.8484195,2.6358993 C 6.0986735,3.069352 5.9622783,3.6166102 5.5437722,3.8582348 5.1252661,4.0998594 4.583129,3.944352 4.332875,3.5108993 4.082621,3.0774465 4.2190162,2.5301884 4.6375222,2.2885638 5.0560283,2.0469392 5.5981654,2.2024466 5.8484195,2.6358993 z M 3.5296488,4.6728304 C 3.9631015,4.9230844 4.1186089,5.4652215 3.8769843,5.8837276 3.6353597,6.3022337 3.0881015,6.4386289 2.6546488,6.1883748 2.221196,5.9381208 2.0656886,5.3959837 2.3073132,4.9774776 2.5489379,4.5589715 3.096196,4.4225763 3.5296488,4.6728304 z m -0.989649,2.9234201 c 0.5005081,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.4057419,0.875 -0.90625,0.875 -0.5005081,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.4057419,-0.875 0.90625,-0.875 z M 3.144649,10.622831 c 0.4334527,-0.250254 0.9807109,-0.113859 1.2223355,0.304647 0.2416246,0.418506 0.086117,0.960643 -0.3473355,1.210897 C 3.5861963,12.388629 3.0389381,12.252234 2.7973135,11.833728 2.5556889,11.415222 2.7111963,10.873085 3.144649,10.622831 z m 2.036931,2.31877 c 0.2502541,-0.433452 0.7923912,-0.58896 1.2108973,-0.347335 0.4185061,0.241624 0.5549012,0.788883 0.3046472,1.222335 -0.2502541,0.433453 -0.7923912,0.58896 -1.2108972,0.347336 C 5.0677212,13.922312 4.931326,13.375054 5.18158,12.941601 z m 2.9234201,0.989649 c 0,-0.500508 0.3917508,-0.90625 0.875,-0.90625 0.4832492,0 0.875,0.405742 0.875,0.90625 0,0.500509 -0.3917508,0.90625 -0.875,0.90625 -0.4832492,0 -0.875,-0.405741 -0.875,-0.90625 z M 11.13158,13.326601 c -0.250254,-0.433453 -0.113859,-0.980711 0.304647,-1.222335 0.418507,-0.241625 0.960644,-0.08612 1.210898,0.347335 0.250254,0.433453 0.113859,0.980711 -0.304648,1.222336 -0.418506,0.241624 -0.960643,0.08612 -1.210897,-0.347336 z m 2.318771,-2.036931 c -0.433453,-0.250254 -0.58896,-0.792391 -0.347335,-1.210897 0.241624,-0.4185064 0.788882,-0.5549016 1.222335,-0.3046476 0.433453,0.2502536 0.58896,0.7923916 0.347336,1.2108976 -0.241625,0.418506 -0.788883,0.554901 -1.222336,0.304647 z M 14.44,8.36625 c -0.500508,0 -0.90625,-0.3917508 -0.90625,-0.875 0,-0.4832492 0.405742,-0.875 0.90625,-0.875 0.500508,0 0.90625,0.3917508 0.90625,0.875 0,0.4832492 -0.405742,0.875 -0.90625,0.875 z M 13.835351,5.3396697 C 13.401898,5.5899238 12.85464,5.4535286 12.613016,5.0350225 12.371391,4.6165164 12.526898,4.0743793 12.960351,3.8241252 c 0.433453,-0.250254 0.980711,-0.1138588 1.222336,0.3046473 0.241624,0.4185061 0.08612,0.9606432 -0.347336,1.2108972 z M 11.79842,3.0208989 C 11.548166,3.4543516 11.006029,3.609859 10.587523,3.3682344 10.169017,3.1266098 10.032621,2.5793516 10.282875,2.1458989 10.533129,1.7124461 11.075267,1.5569388 11.493773,1.7985634 11.912279,2.040188 12.048674,2.5874462 11.79842,3.0208989 z" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.875,2.8017767 0,5.25" + id="path4436" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.7781746,7.4629419 10.783378,7.6397186" + id="path4438" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Shine" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient4440);fill-opacity:1;stroke:none" + id="path4353" + sodipodi:cx="8.0433397" + sodipodi:cy="8.1334372" + sodipodi:rx="7.3686199" + sodipodi:ry="7.4368792" + d="m 15.41196,8.1334372 a 7.3686199,7.4368792 0 1 1 -14.73724019,0 7.3686199,7.4368792 0 1 1 14.73724019,0 z" + transform="matrix(0.91562931,0,0,0.91562931,0.64737218,0.56658541)" /> + </g> +</svg> diff --git a/src/qt/res/src/inout.svg b/src/qt/res/src/inout.svg new file mode 100644 index 0000000000..bfab8ef6ab --- /dev/null +++ b/src/qt/res/src/inout.svg @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg3039" + version="1.1" + inkscape:version="0.48.0 r9654" + width="120" + height="120" + sodipodi:docname="inout3.svg" + inkscape:export-filename="/home/orion/projects2/bitcoin/tx_mined.png" + inkscape:export-xdpi="12" + inkscape:export-ydpi="12" + style="display:inline"> + <metadata + id="metadata3045"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs3043" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1127" + id="namedview3041" + showgrid="false" + inkscape:zoom="1" + inkscape:cx="87.12655" + inkscape:cy="67.287943" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:current-layer="layer7" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="base" + style="display:none"> + <path + style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#afafaf;fill-opacity:1;stroke:none;stroke-width:7;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" + d="m 45.375,17 c -13.984022,0.241082 -24.968241,3.28976 -37.4375,9.59375 -8.9920665,21.871558 -6.680947,46.393002 -1.96875,69 26.190925,13.15317 55.594909,12.97748 81.75,-0.21875 3.852144,-9.729509 1.599587,-12.800015 2.375,-23.15625 C 83.917272,72.423868 77.129905,67.688094 74.1875,75.25 72.702487,79.722025 76.119709,82.645519 69.15625,83.34375 53.023957,88.536726 37.721719,89.140031 21.875,82.28125 19.715285,68.367979 18.599566,53.65725 22.5,40 c 16.868164,-6.642068 33.121079,-6.120062 50.03125,-0.15625 1.285344,5.854815 1.574746,5.763255 2.375,11.6875 C 80.46378,51.970623 89.644961,53.334736 91.3125,46 91.85305,36.007959 89.372312,31.625095 87.1875,25.6875 73.709743,21.03478 59.804255,16.698888 45.375,17 z" + id="path3053" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="arrow_out" + style="display:none"> + <path + style="fill:#6e6e6e;fill-opacity:1;stroke:none;display:inline" + d="m 63.983409,65.48134 0.267857,-9.514444 36.161424,-0.194173 -7.414458,-8.757971 3.798312,-7.572721 c 0,0 23.437946,20.796656 22.500436,21.185 -0.93751,0.388345 -23.170094,22.73838 -23.170094,22.73838 l -3.128654,-8.931927 7.548388,-8.5638 z" + id="path3844" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccscccc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="arrow_in" + style="display:none"> + <path + inkscape:connector-curvature="0" + id="path4080" + d="m 99.28763,65.48134 -0.26786,-9.514444 -36.161422,-0.194173 7.768009,-6.283097 -4.151865,-9.279828 c 0,0 -23.43795,20.028889 -22.500436,20.417233 0.937514,0.388345 23.170094,20.970613 23.170094,20.970613 l 3.482207,-9.639034 -7.901936,-6.088926 z" + style="fill:#6e6e6e;fill-opacity:1;stroke:none;display:inline" + sodipodi:nodetypes="cccccscccc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer7" + inkscape:label="mined" + style="display:inline"> + <path + style="fill:#a9a9a9;fill-opacity:1;stroke:none;display:inline" + d="M 9.4375,99.758169 78.14665,33.110294 73.680556,28.300653 67.153187,8.375 l 18.55147,6.527369 4.80964,3.779003 4.809641,-4.80964 11.680552,12.367647 -6.18382,4.80964 13.74183,14.085377 -12.71119,14.42892 -14.77247,-16.14665 -65.273693,70.770424 z" + id="path4103" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccc" /> + <path + sodipodi:nodetypes="cccccccccccccc" + inkscape:connector-curvature="0" + id="path4619" + d="M 114.5625,99.758169 45.85335,33.110294 50.319444,28.300653 56.846813,8.375 l -18.55147,6.527369 -4.80964,3.779003 -4.809641,-4.80964 -11.680556,12.367647 6.183825,4.80964 L 9.4375,45.134396 22.148693,59.563316 36.92116,43.416666 102.19485,114.18709 z" + style="fill:#a9a9a9;fill-opacity:1;stroke:none;display:inline" /> + <path + style="fill:#6e6e6e;fill-opacity:1;stroke:none" + d="M 10.960155,44.693128 38.53732,16.408856 54.093669,10.752002 48.436815,28.076118 22.273864,56.713943 z" + id="path5256" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:#6e6e6e;fill-opacity:1;stroke:none" + d="M 69.296465,10.398449 85.559921,17.115963 111.72287,45.046681 102.17693,56.713943 74.953319,27.722565 z" + id="path5258" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccc" /> + </g> +</svg> diff --git a/src/qt/res/src/questionmark.svg b/src/qt/res/src/questionmark.svg new file mode 100644 index 0000000000..c03c159a5f --- /dev/null +++ b/src/qt/res/src/questionmark.svg @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16px" + height="16px" + id="svg2993" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="questionmark.svg" + inkscape:export-filename="/store/orion/projects/bitcoin/questionmark.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <defs + id="defs2995"> + <linearGradient + id="linearGradient3808"> + <stop + style="stop-color:#c8c8c8;stop-opacity:1;" + offset="0" + id="stop3810" /> + <stop + style="stop-color:#959595;stop-opacity:1" + offset="1" + id="stop3812" /> + </linearGradient> + <filter + inkscape:collect="always" + id="filter3804" + x="-0.2510722" + width="1.5021444" + y="-0.13164773" + height="1.2632955"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.73280473" + id="feGaussianBlur3806" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3808" + id="linearGradient3844" + x1="8.4916801" + y1="1.4395804" + x2="8.6022711" + y2="14.211697" + gradientUnits="userSpaceOnUse" /> + <filter + inkscape:collect="always" + id="filter3889" + x="-0.13954329" + width="1.2790866" + y="-0.073168421" + height="1.1463368"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.40728516" + id="feGaussianBlur3891" /> + </filter> + <filter + inkscape:collect="always" + id="filter3897" + x="-0.17178624" + width="1.3435725" + y="-0.090074761" + height="1.1801495"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.50139271" + id="feGaussianBlur3899" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="10.789648" + inkscape:cy="7.6382159" + inkscape:current-layer="layer2" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1920" + inkscape:window-height="1127" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" /> + <metadata + id="metadata2998"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Below" + style="display:inline"> + <text + sodipodi:linespacing="125%" + id="text3006" + y="14.748394" + x="4.1060953" + style="font-size:18px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;filter:url(#filter3897)" + xml:space="preserve"><tspan + y="14.748394" + x="4.1060953" + id="tspan3008" + sodipodi:role="line">?</tspan></text> + <text + xml:space="preserve" + style="font-size:18px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;filter:url(#filter3889)" + x="4.1060953" + y="14.748394" + id="text3824" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3826" + x="4.1060953" + y="14.748394">?</tspan></text> + </g> + <g + id="layer1" + inkscape:label="QuestionMark" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <text + xml:space="preserve" + style="font-size:18px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3844);fill-opacity:1;stroke:none;font-family:Sans" + x="4.1060953" + y="14.748394" + id="text3001" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3003" + x="4.1060953" + y="14.748394" + style="fill:url(#linearGradient3844);fill-opacity:1">?</tspan></text> + </g> +</svg> diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp new file mode 100644 index 0000000000..a9a89c282b --- /dev/null +++ b/src/qt/sendcoinsdialog.cpp @@ -0,0 +1,236 @@ +#include "sendcoinsdialog.h" +#include "ui_sendcoinsdialog.h" +#include "walletmodel.h" +#include "bitcoinunits.h" +#include "addressbookpage.h" +#include "optionsmodel.h" +#include "sendcoinsentry.h" +#include "guiutil.h" + +#include <QMessageBox> +#include <QLocale> +#include <QTextDocument> + +SendCoinsDialog::SendCoinsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SendCoinsDialog), + model(0) +{ + ui->setupUi(this); + + addEntry(); + + connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); +} + +void SendCoinsDialog::setModel(WalletModel *model) +{ + this->model = model; + + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); + if(entry) + { + entry->setModel(model); + } + } +} + +SendCoinsDialog::~SendCoinsDialog() +{ + delete ui; +} + +void SendCoinsDialog::on_sendButton_clicked() +{ + QList<SendCoinsRecipient> recipients; + bool valid = true; + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); + if(entry) + { + if(entry->validate()) + { + recipients.append(entry->getValue()); + } + else + { + valid = false; + } + } + } + + if(!valid || recipients.isEmpty()) + { + return; + } + + // Format confirmation message + QStringList formatted; + foreach(const SendCoinsRecipient &rcp, recipients) + { + formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address)); + } + + QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), + tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))), + QMessageBox::Yes|QMessageBox::Cancel, + QMessageBox::Cancel); + + if(retval != QMessageBox::Yes) + { + return; + } + + WalletModel::SendCoinsReturn sendstatus = model->sendCoins(recipients); + switch(sendstatus.status) + { + case WalletModel::InvalidAddress: + QMessageBox::warning(this, tr("Send Coins"), + tr("The recepient address is not valid, please recheck."), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::InvalidAmount: + QMessageBox::warning(this, tr("Send Coins"), + tr("The amount to pay must be larger than 0."), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::AmountExceedsBalance: + QMessageBox::warning(this, tr("Send Coins"), + tr("Amount exceeds your balance"), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::AmountWithFeeExceedsBalance: + QMessageBox::warning(this, tr("Send Coins"), + tr("Total exceeds your balance when the %1 transaction fee is included"). + arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendstatus.fee)), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::DuplicateAddress: + QMessageBox::warning(this, tr("Send Coins"), + tr("Duplicate address found, can only send to each address once in one send operation"), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::TransactionCreationFailed: + QMessageBox::warning(this, tr("Send Coins"), + tr("Error: Transaction creation failed "), + QMessageBox::Ok, QMessageBox::Ok); + break; + break; + case WalletModel::TransactionCommitFailed: + QMessageBox::warning(this, tr("Send Coins"), + tr("Error: 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."), + QMessageBox::Ok, QMessageBox::Ok); + break; + case WalletModel::OK: + accept(); + break; + } +} + +void SendCoinsDialog::clear() +{ + // Remove entries until only one left + while(ui->entries->count()) + { + delete ui->entries->takeAt(0)->widget(); + } + addEntry(); + + updateRemoveEnabled(); + + ui->sendButton->setDefault(true); +} + +void SendCoinsDialog::reject() +{ + clear(); +} + +void SendCoinsDialog::accept() +{ + clear(); +} + +SendCoinsEntry *SendCoinsDialog::addEntry() +{ + SendCoinsEntry *entry = new SendCoinsEntry(this); + entry->setModel(model); + ui->entries->addWidget(entry); + connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); + + updateRemoveEnabled(); + + // Focus the field, so that entry can start immediately + entry->clear(); + return entry; +} + +void SendCoinsDialog::updateRemoveEnabled() +{ + // Remove buttons are enabled as soon as there is more than one send-entry + bool enabled = (ui->entries->count() > 1); + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); + if(entry) + { + entry->setRemoveEnabled(enabled); + } + } + setupTabChain(0); +} + +void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) +{ + delete entry; + updateRemoveEnabled(); +} + +QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) +{ + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); + if(entry) + { + prev = entry->setupTabChain(prev); + } + } + QWidget::setTabOrder(prev, ui->addButton); + QWidget::setTabOrder(ui->addButton, ui->sendButton); + return ui->sendButton; +} + +void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) +{ + SendCoinsEntry *entry = 0; + // Replace the first entry if it is still unused + if(ui->entries->count() == 1) + { + SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget()); + if(first->isClear()) + { + entry = first; + } + } + if(!entry) + { + entry = addEntry(); + } + + entry->setValue(rv); +} + + +void SendCoinsDialog::handleURL(const QUrl *url) +{ + SendCoinsRecipient rv; + if(!GUIUtil::parseBitcoinURL(url, &rv)) + { + return; + } + pasteEntry(rv); +} diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h new file mode 100644 index 0000000000..9c56e51811 --- /dev/null +++ b/src/qt/sendcoinsdialog.h @@ -0,0 +1,51 @@ +#ifndef SENDCOINSDIALOG_H +#define SENDCOINSDIALOG_H + +#include <QDialog> + +namespace Ui { + class SendCoinsDialog; +} +class WalletModel; +class SendCoinsEntry; +class SendCoinsRecipient; + +QT_BEGIN_NAMESPACE +class QUrl; +QT_END_NAMESPACE + +class SendCoinsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SendCoinsDialog(QWidget *parent = 0); + ~SendCoinsDialog(); + + void setModel(WalletModel *model); + + // 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); + + void pasteEntry(const SendCoinsRecipient &rv); + void handleURL(const QUrl *url); + +public slots: + void clear(); + void reject(); + void accept(); + SendCoinsEntry *addEntry(); + void updateRemoveEnabled(); + +private: + Ui::SendCoinsDialog *ui; + WalletModel *model; + +private slots: + void on_sendButton_clicked(); + + void removeEntry(SendCoinsEntry* entry); +}; + +#endif // SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp new file mode 100644 index 0000000000..fccef232bb --- /dev/null +++ b/src/qt/sendcoinsentry.cpp @@ -0,0 +1,145 @@ +#include "sendcoinsentry.h" +#include "ui_sendcoinsentry.h" +#include "guiutil.h" +#include "bitcoinunits.h" +#include "addressbookpage.h" +#include "walletmodel.h" +#include "optionsmodel.h" +#include "addresstablemodel.h" + +#include <QApplication> +#include <QClipboard> + +SendCoinsEntry::SendCoinsEntry(QWidget *parent) : + QFrame(parent), + ui(new Ui::SendCoinsEntry), + model(0) +{ + ui->setupUi(this); + +#if QT_VERSION >= 0x040700 + ui->payTo->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); + ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); +#endif + setFocusPolicy(Qt::TabFocus); + setFocusProxy(ui->payTo); + + GUIUtil::setupAddressWidget(ui->payTo, this); +} + +SendCoinsEntry::~SendCoinsEntry() +{ + delete ui; +} + +void SendCoinsEntry::on_pasteButton_clicked() +{ + // Paste text from clipboard into recipient field + ui->payTo->setText(QApplication::clipboard()->text()); +} + +void SendCoinsEntry::on_addressBookButton_clicked() +{ + AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::SendingTab, this); + dlg.setModel(model->getAddressTableModel()); + if(dlg.exec()) + { + ui->payTo->setText(dlg.getReturnValue()); + ui->payAmount->setFocus(); + } +} + +void SendCoinsEntry::on_payTo_textChanged(const QString &address) +{ + ui->addAsLabel->setText(model->getAddressTableModel()->labelForAddress(address)); +} + +void SendCoinsEntry::setModel(WalletModel *model) +{ + this->model = model; +} + +void SendCoinsEntry::setRemoveEnabled(bool enabled) +{ + ui->deleteButton->setEnabled(enabled); +} + +void SendCoinsEntry::clear() +{ + ui->payTo->clear(); + ui->addAsLabel->clear(); + ui->payAmount->clear(); + ui->payTo->setFocus(); + if(model) + { + ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); + } +} + +void SendCoinsEntry::on_deleteButton_clicked() +{ + emit removeEntry(this); +} + +bool SendCoinsEntry::validate() +{ + // Check input validity + bool retval = true; + + if(!ui->payAmount->validate()) + { + retval = false; + } + else + { + if(ui->payAmount->value() <= 0) + { + // Cannot send 0 coins or less + ui->payAmount->setValid(false); + retval = false; + } + } + + if(!ui->payTo->hasAcceptableInput() || + (model && !model->validateAddress(ui->payTo->text()))) + { + ui->payTo->setValid(false); + retval = false; + } + + return retval; +} + +SendCoinsRecipient SendCoinsEntry::getValue() +{ + SendCoinsRecipient rv; + + rv.address = ui->payTo->text(); + rv.label = ui->addAsLabel->text(); + rv.amount = ui->payAmount->value(); + + return rv; +} + +QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) +{ + QWidget::setTabOrder(prev, ui->payTo); + QWidget::setTabOrder(ui->payTo, ui->addressBookButton); + QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); + QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); + QWidget::setTabOrder(ui->deleteButton, ui->addAsLabel); + return ui->payAmount->setupTabChain(ui->addAsLabel); +} + +void SendCoinsEntry::setValue(const SendCoinsRecipient &value) +{ + ui->payTo->setText(value.address); + ui->addAsLabel->setText(value.label); + ui->payAmount->setValue(value.amount); +} + +bool SendCoinsEntry::isClear() +{ + return ui->payTo->text().isEmpty(); +} + diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h new file mode 100644 index 0000000000..ccc223b5f5 --- /dev/null +++ b/src/qt/sendcoinsentry.h @@ -0,0 +1,51 @@ +#ifndef SENDCOINSENTRY_H +#define SENDCOINSENTRY_H + +#include <QFrame> + +namespace Ui { + class SendCoinsEntry; +} +class WalletModel; +class SendCoinsRecipient; + +class SendCoinsEntry : public QFrame +{ + Q_OBJECT + +public: + explicit SendCoinsEntry(QWidget *parent = 0); + ~SendCoinsEntry(); + + void setModel(WalletModel *model); + bool validate(); + SendCoinsRecipient getValue(); + + // Return true if the entry is still empty and unedited + bool isClear(); + + void setValue(const SendCoinsRecipient &value); + + // 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); + +public slots: + void setRemoveEnabled(bool enabled); + void clear(); + +signals: + void removeEntry(SendCoinsEntry *entry); + +private slots: + void on_deleteButton_clicked(); + void on_payTo_textChanged(const QString &address); + void on_addressBookButton_clicked(); + void on_pasteButton_clicked(); + +private: + Ui::SendCoinsEntry *ui; + WalletModel *model; +}; + +#endif // SENDCOINSENTRY_H diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp new file mode 100644 index 0000000000..612b5d8974 --- /dev/null +++ b/src/qt/transactiondesc.cpp @@ -0,0 +1,292 @@ +#include <transactiondesc.h> + +#include "guiutil.h" +#include "bitcoinunits.h" + +#include "headers.h" +#include "qtui.h" + +#include <QString> +#include <QTextDocument> // For Qt::escape + +using namespace std; + +QString TransactionDesc::HtmlEscape(const QString& str, bool fMultiLine) +{ + QString escaped = Qt::escape(str); + if(fMultiLine) + { + escaped = escaped.replace("\n", "<br>\n"); + } + return escaped; +} + +QString TransactionDesc::HtmlEscape(const std::string& str, bool fMultiLine) +{ + return HtmlEscape(QString::fromStdString(str), fMultiLine); +} + +QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) +{ + if (!wtx.IsFinal()) + { + if (wtx.nLockTime < LOCKTIME_THRESHOLD) + return tr("Open for %1 blocks").arg(nBestHeight - wtx.nLockTime); + else + return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.nLockTime)); + } + else + { + int nDepth = wtx.GetDepthInMainChain(); + if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) + return tr("%1/offline?").arg(nDepth); + else if (nDepth < 6) + return tr("%1/unconfirmed").arg(nDepth); + else + return tr("%1 confirmations").arg(nDepth); + } +} + +QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) +{ + QString strHTML; + CRITICAL_BLOCK(wallet->cs_mapAddressBook) + { + strHTML.reserve(4000); + strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>"; + + int64 nTime = wtx.GetTxTime(); + int64 nCredit = wtx.GetCredit(); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + + strHTML += tr("<b>Status:</b> ") + FormatTxStatus(wtx); + int nRequests = wtx.GetRequestCount(); + if (nRequests != -1) + { + if (nRequests == 0) + strHTML += tr(", has not been successfully broadcast yet"); + else if (nRequests == 1) + strHTML += tr(", broadcast through %1 node").arg(nRequests); + else + strHTML += tr(", broadcast through %1 nodes").arg(nRequests); + } + strHTML += "<br>"; + + strHTML += tr("<b>Date:</b> ") + (nTime ? GUIUtil::dateTimeStr(nTime) : QString("")) + "<br>"; + + // + // From + // + if (wtx.IsCoinBase()) + { + strHTML += tr("<b>Source:</b> Generated<br>"); + } + else if (!wtx.mapValue["from"].empty()) + { + // Online transaction + if (!wtx.mapValue["from"].empty()) + strHTML += tr("<b>From:</b> ") + HtmlEscape(wtx.mapValue["from"]) + "<br>"; + } + else + { + // Offline transaction + if (nNet > 0) + { + // Credit + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + if (wallet->IsMine(txout)) + { + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, wallet, address)) + { + if (wallet->mapAddressBook.count(address)) + { + strHTML += tr("<b>From:</b> ") + tr("unknown") + "<br>"; + strHTML += tr("<b>To:</b> "); + strHTML += HtmlEscape(address.ToString()); + if (!wallet->mapAddressBook[address].empty()) + strHTML += tr(" (yours, label: ") + HtmlEscape(wallet->mapAddressBook[address]) + ")"; + else + strHTML += tr(" (yours)"); + strHTML += "<br>"; + } + } + break; + } + } + } + } + + // + // To + // + string strAddress; + if (!wtx.mapValue["to"].empty()) + { + // Online transaction + strAddress = wtx.mapValue["to"]; + strHTML += tr("<b>To:</b> "); + if (wallet->mapAddressBook.count(strAddress) && !wallet->mapAddressBook[strAddress].empty()) + strHTML += HtmlEscape(wallet->mapAddressBook[strAddress]) + " "; + strHTML += HtmlEscape(strAddress) + "<br>"; + } + + // + // Amount + // + if (wtx.IsCoinBase() && nCredit == 0) + { + // + // Coinbase + // + int64 nUnmatured = 0; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + nUnmatured += wallet->GetCredit(txout); + strHTML += tr("<b>Credit:</b> "); + if (wtx.IsInMainChain()) + strHTML += tr("(%1 matures in %2 more blocks)") + .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nUnmatured)) + .arg(wtx.GetBlocksToMaturity()); + else + strHTML += tr("(not accepted)"); + strHTML += "<br>"; + } + else if (nNet > 0) + { + // + // Credit + // + strHTML += tr("<b>Credit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet) + "<br>"; + } + else + { + bool fAllFromMe = true; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && wallet->IsMine(txin); + + bool fAllToMe = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && wallet->IsMine(txout); + + if (fAllFromMe) + { + // + // Debit + // + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + if (wallet->IsMine(txout)) + continue; + + if (wtx.mapValue["to"].empty()) + { + // Offline transaction + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, 0, address)) + { + strHTML += tr("<b>To:</b> "); + if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) + strHTML += HtmlEscape(wallet->mapAddressBook[address]) + " "; + strHTML += HtmlEscape(address.ToString()); + strHTML += "<br>"; + } + } + + strHTML += tr("<b>Debit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -txout.nValue) + "<br>"; + } + + if (fAllToMe) + { + // Payment to self + int64 nChange = wtx.GetChange(); + int64 nValue = nCredit - nChange; + strHTML += tr("<b>Debit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nValue) + "<br>"; + strHTML += tr("<b>Credit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nValue) + "<br>"; + } + + int64 nTxFee = nDebit - wtx.GetValueOut(); + if (nTxFee > 0) + strHTML += tr("<b>Transaction fee:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-nTxFee) + "<br>"; + } + else + { + // + // Mixed debit transaction + // + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + if (wallet->IsMine(txin)) + strHTML += tr("<b>Debit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-wallet->GetDebit(txin)) + "<br>"; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if (wallet->IsMine(txout)) + strHTML += tr("<b>Credit:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,wallet->GetCredit(txout)) + "<br>"; + } + } + + strHTML += tr("<b>Net amount:</b> ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,nNet, true) + "<br>"; + + // + // Message + // + if (!wtx.mapValue["message"].empty()) + strHTML += QString("<br><b>") + tr("Message:") + "</b><br>" + HtmlEscape(wtx.mapValue["message"], true) + "<br>"; + if (!wtx.mapValue["comment"].empty()) + strHTML += QString("<br><b>") + tr("Comment:") + "</b><br>" + HtmlEscape(wtx.mapValue["comment"], true) + "<br>"; + + if (wtx.IsCoinBase()) + strHTML += QString("<br>") + tr("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.") + "<br>"; + + // + // Debug view + // + if (fDebug) + { + strHTML += "<hr><br>Debug information<br><br>"; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + if(wallet->IsMine(txin)) + strHTML += "<b>Debit:</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-wallet->GetDebit(txin)) + "<br>"; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if(wallet->IsMine(txout)) + strHTML += "<b>Credit:</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,wallet->GetCredit(txout)) + "<br>"; + + strHTML += "<br><b>Transaction:</b><br>"; + strHTML += HtmlEscape(wtx.ToString(), true); + + CTxDB txdb("r"); // To fetch source txouts + + strHTML += "<br><b>Inputs:</b>"; + strHTML += "<ul>"; + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + COutPoint prevout = txin.prevout; + + CTransaction prev; + if(txdb.ReadDiskTx(prevout.hash, prev)) + { + if (prevout.n < prev.vout.size()) + { + strHTML += "<li>"; + const CTxOut &vout = prev.vout[prevout.n]; + CBitcoinAddress address; + if (ExtractAddress(vout.scriptPubKey, 0, address)) + { + if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) + strHTML += HtmlEscape(wallet->mapAddressBook[address]) + " "; + strHTML += QString::fromStdString(address.ToString()); + } + strHTML = strHTML + " Amount=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,vout.nValue); + strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) ? "true" : "false") + "</li>"; + } + } + } + } + strHTML += "</ul>"; + } + + strHTML += "</font></html>"; + } + return strHTML; +} diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h new file mode 100644 index 0000000000..257b2cbb89 --- /dev/null +++ b/src/qt/transactiondesc.h @@ -0,0 +1,24 @@ +#ifndef TRANSACTIONDESC_H +#define TRANSACTIONDESC_H + +#include <QString> +#include <QObject> +#include <string> + +class CWallet; +class CWalletTx; + +class TransactionDesc: public QObject +{ +public: + // Provide human-readable extended HTML description of a transaction + static QString toHTML(CWallet *wallet, CWalletTx &wtx); +private: + TransactionDesc() {} + + static QString HtmlEscape(const QString& str, bool fMultiLine=false); + static QString HtmlEscape(const std::string &str, bool fMultiLine=false); + static QString FormatTxStatus(const CWalletTx& wtx); +}; + +#endif // TRANSACTIONDESC_H diff --git a/src/qt/transactiondescdialog.cpp b/src/qt/transactiondescdialog.cpp new file mode 100644 index 0000000000..3bd4808cb6 --- /dev/null +++ b/src/qt/transactiondescdialog.cpp @@ -0,0 +1,20 @@ +#include "transactiondescdialog.h" +#include "ui_transactiondescdialog.h" + +#include "transactiontablemodel.h" + +#include <QModelIndex> + +TransactionDescDialog::TransactionDescDialog(const QModelIndex &idx, QWidget *parent) : + QDialog(parent), + ui(new Ui::TransactionDescDialog) +{ + ui->setupUi(this); + QString desc = idx.data(TransactionTableModel::LongDescriptionRole).toString(); + ui->detailText->setHtml(desc); +} + +TransactionDescDialog::~TransactionDescDialog() +{ + delete ui; +} diff --git a/src/qt/transactiondescdialog.h b/src/qt/transactiondescdialog.h new file mode 100644 index 0000000000..4f8f754b2b --- /dev/null +++ b/src/qt/transactiondescdialog.h @@ -0,0 +1,25 @@ +#ifndef TRANSACTIONDESCDIALOG_H +#define TRANSACTIONDESCDIALOG_H + +#include <QDialog> + +namespace Ui { + class TransactionDescDialog; +} +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +class TransactionDescDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TransactionDescDialog(const QModelIndex &idx, QWidget *parent = 0); + ~TransactionDescDialog(); + +private: + Ui::TransactionDescDialog *ui; +}; + +#endif // TRANSACTIONDESCDIALOG_H diff --git a/src/qt/transactionfilterproxy.cpp b/src/qt/transactionfilterproxy.cpp new file mode 100644 index 0000000000..a4c5b37171 --- /dev/null +++ b/src/qt/transactionfilterproxy.cpp @@ -0,0 +1,86 @@ +#include "transactionfilterproxy.h" +#include "transactiontablemodel.h" + +#include <QDateTime> + +#include <cstdlib> + +// Earliest date that can be represented (far in the past) +const QDateTime TransactionFilterProxy::MIN_DATE = QDateTime::fromTime_t(0); +// Last date that can be represented (far in the future) +const QDateTime TransactionFilterProxy::MAX_DATE = QDateTime::fromTime_t(0xFFFFFFFF); + +TransactionFilterProxy::TransactionFilterProxy(QObject *parent) : + QSortFilterProxyModel(parent), + dateFrom(MIN_DATE), + dateTo(MAX_DATE), + addrPrefix(), + typeFilter(ALL_TYPES), + minAmount(0), + limitRows(-1) +{ +} + +bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + int type = index.data(TransactionTableModel::TypeRole).toInt(); + QDateTime datetime = index.data(TransactionTableModel::DateRole).toDateTime(); + QString address = index.data(TransactionTableModel::AddressRole).toString(); + QString label = index.data(TransactionTableModel::LabelRole).toString(); + qint64 amount = llabs(index.data(TransactionTableModel::AmountRole).toLongLong()); + + if(!(TYPE(type) & typeFilter)) + return false; + if(datetime < dateFrom || datetime > dateTo) + return false; + if(!address.startsWith(addrPrefix) && !label.startsWith(addrPrefix)) + return false; + if(amount < minAmount) + return false; + + return true; +} + +void TransactionFilterProxy::setDateRange(const QDateTime &from, const QDateTime &to) +{ + this->dateFrom = from; + this->dateTo = to; + invalidateFilter(); +} + +void TransactionFilterProxy::setAddressPrefix(const QString &addrPrefix) +{ + this->addrPrefix = addrPrefix; + invalidateFilter(); +} + +void TransactionFilterProxy::setTypeFilter(quint32 modes) +{ + this->typeFilter = modes; + invalidateFilter(); +} + +void TransactionFilterProxy::setMinAmount(qint64 minimum) +{ + this->minAmount = minimum; + invalidateFilter(); +} + +void TransactionFilterProxy::setLimit(int limit) +{ + this->limitRows = limit; +} + +int TransactionFilterProxy::rowCount(const QModelIndex &parent) const +{ + if(limitRows != -1) + { + return std::min(QSortFilterProxyModel::rowCount(parent), limitRows); + } + else + { + return QSortFilterProxyModel::rowCount(parent); + } +} diff --git a/src/qt/transactionfilterproxy.h b/src/qt/transactionfilterproxy.h new file mode 100644 index 0000000000..4dd2a8e5c6 --- /dev/null +++ b/src/qt/transactionfilterproxy.h @@ -0,0 +1,50 @@ +#ifndef TRANSACTIONFILTERPROXY_H +#define TRANSACTIONFILTERPROXY_H + +#include <QSortFilterProxyModel> +#include <QDateTime> + +// Filter transaction list according to pre-specified rules +class TransactionFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit TransactionFilterProxy(QObject *parent = 0); + + // Earliest date that can be represented (far in the past) + static const QDateTime MIN_DATE; + // Last date that can be represented (far in the future) + static const QDateTime MAX_DATE; + // Type filter bit field (all types) + static const quint32 ALL_TYPES = 0xFFFFFFFF; + + static quint32 TYPE(int type) { return 1<<type; } + + void setDateRange(const QDateTime &from, const QDateTime &to); + void setAddressPrefix(const QString &addrPrefix); + // Type filter takes a bitfield created with TYPE() or ALL_TYPES + void setTypeFilter(quint32 modes); + void setMinAmount(qint64 minimum); + + // Set maximum number of rows returned, -1 if unlimited + void setLimit(int limit); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; +protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + +private: + QDateTime dateFrom; + QDateTime dateTo; + QString addrPrefix; + quint32 typeFilter; + qint64 minAmount; + int limitRows; + +signals: + +public slots: + +}; + +#endif // TRANSACTIONFILTERPROXY_H diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp new file mode 100644 index 0000000000..77c5a01260 --- /dev/null +++ b/src/qt/transactionrecord.cpp @@ -0,0 +1,264 @@ +#include "transactionrecord.h" + +#include "headers.h" + +/* Return positive answer if transaction should be shown in list. + */ +bool TransactionRecord::showTransaction(const CWalletTx &wtx) +{ + if (wtx.IsCoinBase()) + { + // Don't show generated coin until confirmed by at least one block after it + // so we don't get the user's hopes up until it looks like it's probably accepted. + // + // It is not an error when generated blocks are not accepted. By design, + // some percentage of blocks, like 10% or more, will end up not accepted. + // This is the normal mechanism by which the network copes with latency. + // + // We display regular transactions right away before any confirmation + // because they can always get into some block eventually. Generated coins + // are special because if their block is not accepted, they are not valid. + // + if (wtx.GetDepthInMainChain() < 2) + { + return false; + } + } + return true; +} + +/* + * Decompose CWallet transaction to model transaction records. + */ +QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx) +{ + QList<TransactionRecord> parts; + int64 nTime = wtx.nTimeDisplayed = wtx.GetTxTime(); + int64 nCredit = wtx.GetCredit(true); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + uint256 hash = wtx.GetHash(); + std::map<std::string, std::string> mapValue = wtx.mapValue; + + if (showTransaction(wtx)) + { + if (nNet > 0 || wtx.IsCoinBase()) + { + // + // Credit + // + TransactionRecord sub(hash, nTime); + + sub.credit = nNet; + + if (wtx.IsCoinBase()) + { + // Generated + sub.type = TransactionRecord::Generated; + + if (nCredit == 0) + { + int64 nUnmatured = 0; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + nUnmatured += wallet->GetCredit(txout); + sub.credit = nUnmatured; + } + } + else if (!mapValue["from"].empty() || !mapValue["message"].empty()) + { + // Received by IP connection + sub.type = TransactionRecord::RecvFromIP; + if (!mapValue["from"].empty()) + sub.address = mapValue["from"]; + } + else + { + // Received by Bitcoin Address + sub.type = TransactionRecord::RecvWithAddress; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + if(wallet->IsMine(txout)) + { + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, wallet, address)) + { + sub.address = address.ToString(); + } + break; + } + } + } + parts.append(sub); + } + else + { + bool fAllFromMe = true; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && wallet->IsMine(txin); + + bool fAllToMe = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && wallet->IsMine(txout); + + if (fAllFromMe && fAllToMe) + { + // Payment to self + int64 nChange = wtx.GetChange(); + + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", + -(nDebit - nChange), nCredit - nChange)); + } + else if (fAllFromMe) + { + // + // Debit + // + int64 nTxFee = nDebit - wtx.GetValueOut(); + + for (int nOut = 0; nOut < wtx.vout.size(); nOut++) + { + const CTxOut& txout = wtx.vout[nOut]; + TransactionRecord sub(hash, nTime); + sub.idx = parts.size(); + + if(wallet->IsMine(txout)) + { + // Ignore parts sent to self, as this is usually the change + // from a transaction sent back to our own address. + continue; + } + else if(!mapValue["to"].empty()) + { + // Sent to IP + sub.type = TransactionRecord::SendToIP; + sub.address = mapValue["to"]; + } + else + { + // Sent to Bitcoin Address + sub.type = TransactionRecord::SendToAddress; + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, 0, address)) + { + sub.address = address.ToString(); + } + } + + int64 nValue = txout.nValue; + /* Add fee to first output */ + if (nTxFee > 0) + { + nValue += nTxFee; + nTxFee = 0; + } + sub.debit = -nValue; + + parts.append(sub); + } + } + else + { + // + // Mixed debit transaction, can't break down payees + // + bool fAllMine = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllMine = fAllMine && wallet->IsMine(txout); + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllMine = fAllMine && wallet->IsMine(txin); + + parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); + } + } + } + + return parts; +} + +void TransactionRecord::updateStatus(const CWalletTx &wtx) +{ + // Determine transaction status + + // Find the block the tx is in + CBlockIndex* pindex = NULL; + std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock); + if (mi != mapBlockIndex.end()) + pindex = (*mi).second; + + // Sort order, unrecorded transactions sort to the top + status.sortKey = strprintf("%010d-%01d-%010u-%03d", + (pindex ? pindex->nHeight : INT_MAX), + (wtx.IsCoinBase() ? 1 : 0), + wtx.nTimeReceived, + idx); + status.confirmed = wtx.IsConfirmed(); + status.depth = wtx.GetDepthInMainChain(); + status.cur_num_blocks = nBestHeight; + + if (!wtx.IsFinal()) + { + if (wtx.nLockTime < LOCKTIME_THRESHOLD) + { + status.status = TransactionStatus::OpenUntilBlock; + status.open_for = nBestHeight - wtx.nLockTime; + } + else + { + status.status = TransactionStatus::OpenUntilDate; + status.open_for = wtx.nLockTime; + } + } + else + { + if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) + { + status.status = TransactionStatus::Offline; + } + else if (status.depth < NumConfirmations) + { + status.status = TransactionStatus::Unconfirmed; + } + else + { + status.status = TransactionStatus::HaveConfirmations; + } + } + + // For generated transactions, determine maturity + if(type == TransactionRecord::Generated) + { + int64 nCredit = wtx.GetCredit(true); + if (nCredit == 0) + { + status.maturity = TransactionStatus::Immature; + + if (wtx.IsInMainChain()) + { + status.matures_in = wtx.GetBlocksToMaturity(); + + // Check if the block was requested by anyone + if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) + status.maturity = TransactionStatus::MaturesWarning; + } + else + { + status.maturity = TransactionStatus::NotAccepted; + } + } + else + { + status.maturity = TransactionStatus::Mature; + } + } +} + +bool TransactionRecord::statusUpdateNeeded() +{ + return status.cur_num_blocks != nBestHeight; +} + +std::string TransactionRecord::getTxID() +{ + return hash.ToString() + strprintf("-%03d", idx); +} + diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h new file mode 100644 index 0000000000..0050c878ee --- /dev/null +++ b/src/qt/transactionrecord.h @@ -0,0 +1,118 @@ +#ifndef TRANSACTIONRECORD_H +#define TRANSACTIONRECORD_H + +#include "uint256.h" + +#include <QList> + +class CWallet; +class CWalletTx; + +class TransactionStatus +{ +public: + TransactionStatus(): + confirmed(false), sortKey(""), maturity(Mature), + matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1) + { } + + enum Maturity + { + Immature, + Mature, + MaturesWarning, /* Will likely not mature because no nodes have confirmed */ + NotAccepted + }; + + enum Status { + OpenUntilDate, + OpenUntilBlock, + Offline, + Unconfirmed, + HaveConfirmations + }; + + bool confirmed; + std::string sortKey; + + /* For "Generated" transactions */ + Maturity maturity; + int matures_in; + + /* Reported status */ + Status status; + int64 depth; + int64 open_for; /* Timestamp if status==OpenUntilDate, otherwise number of blocks */ + + /* Current number of blocks (to know whether cached status is still valid. */ + int cur_num_blocks; +}; + +class TransactionRecord +{ +public: + enum Type + { + Other, + Generated, + SendToAddress, + SendToIP, + RecvWithAddress, + RecvFromIP, + SendToSelf + }; + + /* Number of confirmation needed for transaction */ + static const int NumConfirmations = 6; + + TransactionRecord(): + hash(), time(0), type(Other), address(""), debit(0), credit(0), idx(0) + { + } + + TransactionRecord(uint256 hash, int64 time): + hash(hash), time(time), type(Other), address(""), debit(0), + credit(0), idx(0) + { + } + + TransactionRecord(uint256 hash, int64 time, + Type type, const std::string &address, + int64 debit, int64 credit): + hash(hash), time(time), type(type), address(address), debit(debit), credit(credit), + idx(0) + { + } + + /* Decompose CWallet transaction to model transaction records. + */ + static bool showTransaction(const CWalletTx &wtx); + static QList<TransactionRecord> decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx); + + /* Fixed */ + uint256 hash; + int64 time; + Type type; + std::string address; + int64 debit; + int64 credit; + + /* Subtransaction index, for sort key */ + int idx; + + /* Status: can change with block chain update */ + TransactionStatus status; + + /* Return the unique identifier for this transaction (part) */ + std::string getTxID(); + + /* Update status from wallet tx. + */ + void updateStatus(const CWalletTx &wtx); + + /* Is a status update needed? + */ + bool statusUpdateNeeded(); +}; + +#endif // TRANSACTIONRECORD_H diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp new file mode 100644 index 0000000000..353cd79145 --- /dev/null +++ b/src/qt/transactiontablemodel.cpp @@ -0,0 +1,622 @@ +#include "transactiontablemodel.h" +#include "guiutil.h" +#include "transactionrecord.h" +#include "guiconstants.h" +#include "transactiondesc.h" +#include "walletmodel.h" +#include "optionsmodel.h" +#include "addresstablemodel.h" +#include "bitcoinunits.h" + +#include "headers.h" + +#include <QLocale> +#include <QList> +#include <QColor> +#include <QTimer> +#include <QIcon> +#include <QDateTime> +#include <QtAlgorithms> + +// Amount column is right-aligned it contains numbers +static int column_alignments[] = { + Qt::AlignLeft|Qt::AlignVCenter, + Qt::AlignLeft|Qt::AlignVCenter, + Qt::AlignLeft|Qt::AlignVCenter, + Qt::AlignLeft|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter + }; + +// Comparison operator for sort/binary search of model tx list +struct TxLessThan +{ + bool operator()(const TransactionRecord &a, const TransactionRecord &b) const + { + return a.hash < b.hash; + } + bool operator()(const TransactionRecord &a, const uint256 &b) const + { + return a.hash < b; + } + bool operator()(const uint256 &a, const TransactionRecord &b) const + { + return a < b.hash; + } +}; + +// Private implementation +struct TransactionTablePriv +{ + TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent): + wallet(wallet), + parent(parent) + { + } + CWallet *wallet; + TransactionTableModel *parent; + + /* Local cache of wallet. + * As it is in the same order as the CWallet, by definition + * this is sorted by sha256. + */ + QList<TransactionRecord> cachedWallet; + + /* Query entire wallet anew from core. + */ + void refreshWallet() + { +#ifdef WALLET_UPDATE_DEBUG + qDebug() << "refreshWallet"; +#endif + cachedWallet.clear(); + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it) + { + cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); + } + } + } + + /* Update our model of the wallet incrementally, to synchronize our model of the wallet + with that of the core. + + Call with list of hashes of transactions that were added, removed or changed. + */ + void updateWallet(const QList<uint256> &updated) + { + // Walk through updated transactions, update model as needed. +#ifdef WALLET_UPDATE_DEBUG + qDebug() << "updateWallet"; +#endif + // Sort update list, and iterate through it in reverse, so that model updates + // can be emitted from end to beginning (so that earlier updates will not influence + // the indices of latter ones). + QList<uint256> updated_sorted = updated; + qSort(updated_sorted); + + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx) + { + const uint256 &hash = updated_sorted.at(update_idx); + // Find transaction in wallet + std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); + bool inWallet = mi != wallet->mapWallet.end(); + // Find bounds of this transaction in model + QList<TransactionRecord>::iterator lower = qLowerBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + QList<TransactionRecord>::iterator upper = qUpperBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + int lowerIndex = (lower - cachedWallet.begin()); + int upperIndex = (upper - cachedWallet.begin()); + + // Determine if transaction is in model already + bool inModel = false; + if(lower != upper) + { + inModel = true; + } + +#ifdef WALLET_UPDATE_DEBUG + qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel + << lowerIndex << "-" << upperIndex; +#endif + + if(inWallet && !inModel) + { + // Added -- insert at the right position + QList<TransactionRecord> toInsert = + TransactionRecord::decomposeTransaction(wallet, mi->second); + if(!toInsert.isEmpty()) /* only if something to insert */ + { + parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1); + int insert_idx = lowerIndex; + foreach(const TransactionRecord &rec, toInsert) + { + cachedWallet.insert(insert_idx, rec); + insert_idx += 1; + } + parent->endInsertRows(); + } + } + else if(!inWallet && inModel) + { + // Removed -- remove entire transaction from table + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedWallet.erase(lower, upper); + parent->endRemoveRows(); + } + else if(inWallet && inModel) + { + // Updated -- nothing to do, status update will take care of this + } + } + } + } + + int size() + { + return cachedWallet.size(); + } + + TransactionRecord *index(int idx) + { + if(idx >= 0 && idx < cachedWallet.size()) + { + TransactionRecord *rec = &cachedWallet[idx]; + + // If a status update is needed (blocks came in since last check), + // update the status of this transaction from the wallet. Otherwise, + // simply re-use the cached status. + if(rec->statusUpdateNeeded()) + { + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); + + if(mi != wallet->mapWallet.end()) + { + rec->updateStatus(mi->second); + } + } + } + return rec; + } + else + { + return 0; + } + } + + QString describe(TransactionRecord *rec) + { + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); + if(mi != wallet->mapWallet.end()) + { + return TransactionDesc::toHTML(wallet, mi->second); + } + } + return QString(""); + } + +}; + +TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent): + QAbstractTableModel(parent), + wallet(wallet), + walletModel(parent), + priv(new TransactionTablePriv(wallet, this)) +{ + columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount"); + + priv->refreshWallet(); + + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(update())); + timer->start(MODEL_UPDATE_DELAY); +} + +TransactionTableModel::~TransactionTableModel() +{ + delete priv; +} + +void TransactionTableModel::update() +{ + QList<uint256> updated; + + // Check if there are changes to wallet map + TRY_CRITICAL_BLOCK(wallet->cs_mapWallet) + { + if(!wallet->vWalletUpdated.empty()) + { + BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated) + { + updated.append(hash); + } + wallet->vWalletUpdated.clear(); + } + } + + if(!updated.empty()) + { + priv->updateWallet(updated); + + // Status (number of confirmations) and (possibly) description + // columns changed for all rows. + emit dataChanged(index(0, Status), index(priv->size()-1, Status)); + emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress)); + } +} + +int TransactionTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return priv->size(); +} + +int TransactionTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return columns.length(); +} + +QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const +{ + QString status; + + switch(wtx->status.status) + { + case TransactionStatus::OpenUntilBlock: + status = tr("Open for %n block(s)","",wtx->status.open_for); + break; + case TransactionStatus::OpenUntilDate: + status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for)); + break; + case TransactionStatus::Offline: + status = tr("Offline (%1 confirmations)").arg(wtx->status.depth); + break; + case TransactionStatus::Unconfirmed: + status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations); + break; + case TransactionStatus::HaveConfirmations: + status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); + break; + } + if(wtx->type == TransactionRecord::Generated) + { + status += "\n"; + switch(wtx->status.maturity) + { + case TransactionStatus::Immature: + status += tr("Mined balance will be available in %n more blocks", "", + wtx->status.matures_in); + break; + case TransactionStatus::Mature: + break; + case TransactionStatus::MaturesWarning: + status += tr("This block was not received by any other nodes and will probably not be accepted!"); + break; + case TransactionStatus::NotAccepted: + status += tr("Generated but not accepted"); + break; + } + } + + return status; +} + +QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const +{ + if(wtx->time) + { + return GUIUtil::dateTimeStr(wtx->time); + } + else + { + return QString(); + } +} + +/* Look up address in address book, if found return label (address) + otherwise just return (address) + */ +QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const +{ + QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address)); + QString description; + if(!label.isEmpty()) + { + description += label + QString(" "); + } + if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip) + { + description += QString("(") + QString::fromStdString(address) + QString(")"); + } + return description; +} + +QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const +{ + switch(wtx->type) + { + case TransactionRecord::RecvWithAddress: + return tr("Received with"); + case TransactionRecord::RecvFromIP: + return tr("Received from IP"); + case TransactionRecord::SendToAddress: + return tr("Sent to"); + case TransactionRecord::SendToIP: + return tr("Sent to IP"); + case TransactionRecord::SendToSelf: + return tr("Payment to yourself"); + case TransactionRecord::Generated: + return tr("Mined"); + default: + return QString(); + } +} + +QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const +{ + switch(wtx->type) + { + case TransactionRecord::Generated: + return QIcon(":/icons/tx_mined"); + case TransactionRecord::RecvWithAddress: + case TransactionRecord::RecvFromIP: + return QIcon(":/icons/tx_input"); + case TransactionRecord::SendToAddress: + case TransactionRecord::SendToIP: + return QIcon(":/icons/tx_output"); + default: + return QIcon(":/icons/tx_inout"); + } + return QVariant(); +} + +QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const +{ + switch(wtx->type) + { + case TransactionRecord::RecvFromIP: + return QString::fromStdString(wtx->address); + case TransactionRecord::RecvWithAddress: + case TransactionRecord::SendToAddress: + return lookupAddress(wtx->address, tooltip); + case TransactionRecord::SendToIP: + return QString::fromStdString(wtx->address); + case TransactionRecord::SendToSelf: + case TransactionRecord::Generated: + default: + return tr("(n/a)"); + } +} + +QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const +{ + // Show addresses without label in a less visible color + switch(wtx->type) + { + case TransactionRecord::RecvWithAddress: + case TransactionRecord::SendToAddress: + { + QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address)); + if(label.isEmpty()) + return COLOR_BAREADDRESS; + } break; + case TransactionRecord::SendToSelf: + case TransactionRecord::Generated: + return COLOR_BAREADDRESS; + default: + break; + } + return QVariant(); +} + +QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const +{ + QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit); + if(showUnconfirmed) + { + if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature) + { + str = QString("[") + str + QString("]"); + } + } + return QString(str); +} + +QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const +{ + if(wtx->type == TransactionRecord::Generated) + { + switch(wtx->status.maturity) + { + case TransactionStatus::Immature: { + int total = wtx->status.depth + wtx->status.matures_in; + int part = (wtx->status.depth * 4 / total) + 1; + return QIcon(QString(":/icons/transaction_%1").arg(part)); + } + case TransactionStatus::Mature: + return QIcon(":/icons/transaction_confirmed"); + case TransactionStatus::MaturesWarning: + case TransactionStatus::NotAccepted: + return QIcon(":/icons/transaction_0"); + } + } + else + { + switch(wtx->status.status) + { + case TransactionStatus::OpenUntilBlock: + case TransactionStatus::OpenUntilDate: + return QColor(64,64,255); + break; + case TransactionStatus::Offline: + return QColor(192,192,192); + case TransactionStatus::Unconfirmed: + switch(wtx->status.depth) + { + case 0: return QIcon(":/icons/transaction_0"); + case 1: return QIcon(":/icons/transaction_1"); + case 2: return QIcon(":/icons/transaction_2"); + case 3: return QIcon(":/icons/transaction_3"); + case 4: return QIcon(":/icons/transaction_4"); + default: return QIcon(":/icons/transaction_5"); + }; + case TransactionStatus::HaveConfirmations: + return QIcon(":/icons/transaction_confirmed"); + } + } + return QColor(0,0,0); +} + +QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const +{ + QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec); + if(rec->type==TransactionRecord::RecvFromIP || rec->type==TransactionRecord::SendToIP || + rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress) + { + tooltip += QString(" ") + formatTxToAddress(rec, true); + } + return tooltip; +} + +QVariant TransactionTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer()); + + switch(role) + { + case Qt::DecorationRole: + switch(index.column()) + { + case Status: + return txStatusDecoration(rec); + case ToAddress: + return txAddressDecoration(rec); + } + break; + case Qt::DisplayRole: + switch(index.column()) + { + case Date: + return formatTxDate(rec); + case Type: + return formatTxType(rec); + case ToAddress: + return formatTxToAddress(rec, false); + case Amount: + return formatTxAmount(rec); + } + break; + case Qt::EditRole: + // Edit role is used for sorting, so return the unformatted values + switch(index.column()) + { + case Status: + return QString::fromStdString(rec->status.sortKey); + case Date: + return rec->time; + case Type: + return formatTxType(rec); + case ToAddress: + return formatTxToAddress(rec, true); + case Amount: + return rec->credit + rec->debit; + } + break; + case Qt::ToolTipRole: + return formatTooltip(rec); + case Qt::TextAlignmentRole: + return column_alignments[index.column()]; + case Qt::ForegroundRole: + // Non-confirmed transactions are grey + if(!rec->status.confirmed) + { + return COLOR_UNCONFIRMED; + } + if(index.column() == Amount && (rec->credit+rec->debit) < 0) + { + return COLOR_NEGATIVE; + } + if(index.column() == ToAddress) + { + return addressColor(rec); + } + break; + case TypeRole: + return rec->type; + case DateRole: + return QDateTime::fromTime_t(static_cast<uint>(rec->time)); + case LongDescriptionRole: + return priv->describe(rec); + case AddressRole: + return QString::fromStdString(rec->address); + case LabelRole: + return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address)); + case AmountRole: + return rec->credit + rec->debit; + case TxIDRole: + return QString::fromStdString(rec->getTxID()); + case ConfirmedRole: + // Return True if transaction counts for balance + return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && + rec->status.maturity != TransactionStatus::Mature); + case FormattedAmountRole: + return formatTxAmount(rec, false); + } + return QVariant(); +} + +QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole) + { + return columns[section]; + } + else if (role == Qt::TextAlignmentRole) + { + return column_alignments[section]; + } else if (role == Qt::ToolTipRole) + { + switch(section) + { + case Status: + return tr("Transaction status. Hover over this field to show number of confirmations."); + case Date: + return tr("Date and time that the transaction was received."); + case Type: + return tr("Type of transaction."); + case ToAddress: + return tr("Destination address of transaction."); + case Amount: + return tr("Amount removed from or added to balance."); + } + } + } + return QVariant(); +} + +QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + TransactionRecord *data = priv->index(row); + if(data) + { + return createIndex(row, column, priv->index(row)); + } + else + { + return QModelIndex(); + } +} + diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h new file mode 100644 index 0000000000..da55495e1e --- /dev/null +++ b/src/qt/transactiontablemodel.h @@ -0,0 +1,79 @@ +#ifndef TRANSACTIONTABLEMODEL_H +#define TRANSACTIONTABLEMODEL_H + +#include <QAbstractTableModel> +#include <QStringList> + +class CWallet; +class TransactionTablePriv; +class TransactionRecord; +class WalletModel; + +class TransactionTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + explicit TransactionTableModel(CWallet* wallet, WalletModel *parent = 0); + ~TransactionTableModel(); + + enum { + Status = 0, + Date = 1, + Type = 2, + ToAddress = 3, + Amount = 4 + } ColumnIndex; + + // Roles to get specific information from a transaction row + // These are independent of column + enum { + // Type of transaction + TypeRole = Qt::UserRole, + // Date and time this transaction was created + DateRole, + // Long description (HTML format) + LongDescriptionRole, + // Address of transaction + AddressRole, + // Label of address related to transaction + LabelRole, + // Net amount of transaction + AmountRole, + // Unique identifier + TxIDRole, + // Is transaction confirmed? + ConfirmedRole, + // Formatted amount, without brackets when unconfirmed + FormattedAmountRole + } RoleIndex; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; +private: + CWallet* wallet; + WalletModel *walletModel; + QStringList columns; + TransactionTablePriv *priv; + + QString lookupAddress(const std::string &address, bool tooltip) const; + QVariant addressColor(const TransactionRecord *wtx) const; + QString formatTxStatus(const TransactionRecord *wtx) const; + QString formatTxDate(const TransactionRecord *wtx) const; + QString formatTxType(const TransactionRecord *wtx) const; + QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const; + QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const; + QString formatTooltip(const TransactionRecord *rec) const; + QVariant txStatusDecoration(const TransactionRecord *wtx) const; + QVariant txAddressDecoration(const TransactionRecord *wtx) const; + +private slots: + void update(); + + friend class TransactionTablePriv; +}; + +#endif + diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp new file mode 100644 index 0000000000..0b2a3e6092 --- /dev/null +++ b/src/qt/transactionview.cpp @@ -0,0 +1,385 @@ +#include "transactionview.h" + +#include "transactionfilterproxy.h" +#include "transactionrecord.h" +#include "walletmodel.h" +#include "addresstablemodel.h" +#include "transactiontablemodel.h" +#include "bitcoinunits.h" +#include "csvmodelwriter.h" +#include "transactiondescdialog.h" +#include "editaddressdialog.h" +#include "optionsmodel.h" + +#include <QScrollBar> +#include <QComboBox> +#include <QDoubleValidator> +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QLineEdit> +#include <QTableView> +#include <QHeaderView> +#include <QPushButton> +#include <QFileDialog> +#include <QMessageBox> +#include <QPoint> +#include <QMenu> +#include <QApplication> +#include <QClipboard> +#include <QLabel> +#include <QDateTimeEdit> + +TransactionView::TransactionView(QWidget *parent) : + QWidget(parent), model(0), transactionProxyModel(0), + transactionView(0) +{ + // Build filter row + setContentsMargins(0,0,0,0); + + QHBoxLayout *hlayout = new QHBoxLayout(); + hlayout->setContentsMargins(0,0,0,0); + hlayout->setSpacing(0); + + hlayout->addSpacing(23); + + dateWidget = new QComboBox(this); + dateWidget->setMaximumWidth(120); + dateWidget->setMinimumWidth(120); + dateWidget->addItem(tr("All"), All); + dateWidget->addItem(tr("Today"), Today); + dateWidget->addItem(tr("This week"), ThisWeek); + dateWidget->addItem(tr("This month"), ThisMonth); + dateWidget->addItem(tr("Last month"), LastMonth); + dateWidget->addItem(tr("This year"), ThisYear); + dateWidget->addItem(tr("Range..."), Range); + hlayout->addWidget(dateWidget); + + typeWidget = new QComboBox(this); + typeWidget->setMaximumWidth(120); + typeWidget->setMinimumWidth(120); + + typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES); + typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::RecvFromIP)); + typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToIP)); + typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); + typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); + typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); + + hlayout->addWidget(typeWidget); + + addressWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + addressWidget->setPlaceholderText("Enter address or label to search"); +#endif + hlayout->addWidget(addressWidget); + + amountWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + amountWidget->setPlaceholderText("Min amount"); +#endif + amountWidget->setMaximumWidth(100); + amountWidget->setMinimumWidth(100); + amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); + hlayout->addWidget(amountWidget); + + QVBoxLayout *vlayout = new QVBoxLayout(this); + vlayout->setContentsMargins(0,0,0,0); + vlayout->setSpacing(0); + //vlayout->addLayout(hlayout2); + + QTableView *view = new QTableView(this); + vlayout->addLayout(hlayout); + vlayout->addWidget(createDateRangeWidget()); + vlayout->addWidget(view); + vlayout->setSpacing(0); + int width = view->verticalScrollBar()->sizeHint().width(); + // Cover scroll bar width with spacing + hlayout->addSpacing(width); + // Always show scroll bar + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + view->setTabKeyNavigation(false); + view->setContextMenuPolicy(Qt::CustomContextMenu); + + transactionView = view; + + // Actions + QAction *copyAddressAction = new QAction("Copy address", this); + QAction *copyLabelAction = new QAction("Copy label", this); + QAction *editLabelAction = new QAction("Edit label", this); + QAction *showDetailsAction = new QAction("Show details...", this); + + contextMenu = new QMenu(); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(editLabelAction); + contextMenu->addAction(showDetailsAction); + + // Connect actions + connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int))); + connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int))); + connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString))); + connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString))); + + connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex))); + + connect(view, + SIGNAL(customContextMenuRequested(QPoint)), + this, + SLOT(contextualMenu(QPoint))); + + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel())); + connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); +} + +void TransactionView::setModel(WalletModel *model) +{ + this->model = model; + + transactionProxyModel = new TransactionFilterProxy(this); + transactionProxyModel->setSourceModel(model->getTransactionTableModel()); + transactionProxyModel->setDynamicSortFilter(true); + + transactionProxyModel->setSortRole(Qt::EditRole); + + transactionView->setModel(transactionProxyModel); + transactionView->setAlternatingRowColors(true); + transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); + transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection); + transactionView->setSortingEnabled(true); + transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder); + transactionView->verticalHeader()->hide(); + + transactionView->horizontalHeader()->resizeSection( + TransactionTableModel::Status, 23); + transactionView->horizontalHeader()->resizeSection( + TransactionTableModel::Date, 120); + transactionView->horizontalHeader()->resizeSection( + TransactionTableModel::Type, 120); + transactionView->horizontalHeader()->setResizeMode( + TransactionTableModel::ToAddress, QHeaderView::Stretch); + transactionView->horizontalHeader()->resizeSection( + TransactionTableModel::Amount, 100); + +} + +void TransactionView::chooseDate(int idx) +{ + QDate current = QDate::currentDate(); + dateRangeWidget->setVisible(false); + switch(dateWidget->itemData(idx).toInt()) + { + case All: + transactionProxyModel->setDateRange( + TransactionFilterProxy::MIN_DATE, + TransactionFilterProxy::MAX_DATE); + break; + case Today: + transactionProxyModel->setDateRange( + QDateTime(current), + TransactionFilterProxy::MAX_DATE); + break; + case ThisWeek: { + // Find last monday + QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1)); + transactionProxyModel->setDateRange( + QDateTime(startOfWeek), + TransactionFilterProxy::MAX_DATE); + + } break; + case ThisMonth: + transactionProxyModel->setDateRange( + QDateTime(QDate(current.year(), current.month(), 1)), + TransactionFilterProxy::MAX_DATE); + break; + case LastMonth: + transactionProxyModel->setDateRange( + QDateTime(QDate(current.year(), current.month()-1, 1)), + QDateTime(QDate(current.year(), current.month(), 1))); + break; + case ThisYear: + transactionProxyModel->setDateRange( + QDateTime(QDate(current.year(), 1, 1)), + TransactionFilterProxy::MAX_DATE); + break; + case Range: + dateRangeWidget->setVisible(true); + dateRangeChanged(); + break; + } +} + +void TransactionView::chooseType(int idx) +{ + transactionProxyModel->setTypeFilter( + typeWidget->itemData(idx).toInt()); +} + +void TransactionView::changedPrefix(const QString &prefix) +{ + transactionProxyModel->setAddressPrefix(prefix); +} + +void TransactionView::changedAmount(const QString &amount) +{ + qint64 amount_parsed = 0; + if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed)) + { + transactionProxyModel->setMinAmount(amount_parsed); + } + else + { + transactionProxyModel->setMinAmount(0); + } +} + +void TransactionView::exportClicked() +{ + // CSV is currently the only supported format + QString filename = QFileDialog::getSaveFileName( + this, + tr("Export Transaction Data"), + QDir::currentPath(), + tr("Comma separated file (*.csv)")); + + if (filename.isNull()) return; + + CSVModelWriter writer(filename); + + // name, column, role + writer.setModel(transactionProxyModel); + writer.addColumn("Confirmed", 0, TransactionTableModel::ConfirmedRole); + writer.addColumn("Date", 0, TransactionTableModel::DateRole); + writer.addColumn("Type", TransactionTableModel::Type, Qt::EditRole); + writer.addColumn("Label", 0, TransactionTableModel::LabelRole); + writer.addColumn("Address", 0, TransactionTableModel::AddressRole); + writer.addColumn("Amount", 0, TransactionTableModel::FormattedAmountRole); + writer.addColumn("ID", 0, TransactionTableModel::TxIDRole); + + if(!writer.write()) + { + QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename), + QMessageBox::Abort, QMessageBox::Abort); + } +} + +void TransactionView::contextualMenu(const QPoint &point) +{ + QModelIndex index = transactionView->indexAt(point); + if(index.isValid()) + { + contextMenu->exec(QCursor::pos()); + } +} + +void TransactionView::copyAddress() +{ + QModelIndexList selection = transactionView->selectionModel()->selectedRows(); + if(!selection.isEmpty()) + { + QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::AddressRole).toString()); + } +} + +void TransactionView::copyLabel() +{ + QModelIndexList selection = transactionView->selectionModel()->selectedRows(); + if(!selection.isEmpty()) + { + QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::LabelRole).toString()); + } +} + +void TransactionView::editLabel() +{ + QModelIndexList selection = transactionView->selectionModel()->selectedRows(); + if(!selection.isEmpty()) + { + AddressTableModel *addressBook = model->getAddressTableModel(); + QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); + if(address.isEmpty()) + { + // If this transaction has no associated address, exit + return; + } + int idx = addressBook->lookupAddress(address); + if(idx != -1) + { + // Edit sending / receiving address + QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); + // Determine type of address, launch appropriate editor dialog type + QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); + + EditAddressDialog dlg(type==AddressTableModel::Receive + ? EditAddressDialog::EditReceivingAddress + : EditAddressDialog::EditSendingAddress, + this); + dlg.setModel(addressBook); + dlg.loadRow(idx); + dlg.exec(); + } + else + { + // Add sending address + EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, + this); + dlg.exec(); + } + } +} + +void TransactionView::showDetails() +{ + QModelIndexList selection = transactionView->selectionModel()->selectedRows(); + if(!selection.isEmpty()) + { + TransactionDescDialog dlg(selection.at(0)); + dlg.exec(); + } +} + +QWidget *TransactionView::createDateRangeWidget() +{ + dateRangeWidget = new QFrame(); + dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + dateRangeWidget->setContentsMargins(1,1,1,1); + QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); + layout->setContentsMargins(0,0,0,0); + layout->addSpacing(23); + layout->addWidget(new QLabel("Range:")); + + dateFrom = new QDateTimeEdit(this); + dateFrom->setDisplayFormat("dd/MM/yy"); + dateFrom->setCalendarPopup(true); + dateFrom->setMinimumWidth(100); + dateFrom->setDate(QDate::currentDate().addDays(-7)); + layout->addWidget(dateFrom); + layout->addWidget(new QLabel("to")); + + dateTo = new QDateTimeEdit(this); + dateTo->setDisplayFormat("dd/MM/yy"); + dateTo->setCalendarPopup(true); + dateTo->setMinimumWidth(100); + dateTo->setDate(QDate::currentDate()); + layout->addWidget(dateTo); + layout->addStretch(); + + // Hide by default + dateRangeWidget->setVisible(false); + + // Notify on change + connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); + connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); + + return dateRangeWidget; +} + +void TransactionView::dateRangeChanged() +{ + transactionProxyModel->setDateRange( + QDateTime(dateFrom->date()), + QDateTime(dateTo->date()).addDays(1)); +} diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h new file mode 100644 index 0000000000..f4f815b162 --- /dev/null +++ b/src/qt/transactionview.h @@ -0,0 +1,77 @@ +#ifndef TRANSACTIONVIEW_H +#define TRANSACTIONVIEW_H + +#include <QWidget> + +class WalletModel; +class TransactionFilterProxy; + +QT_BEGIN_NAMESPACE +class QTableView; +class QComboBox; +class QLineEdit; +class QModelIndex; +class QMenu; +class QFrame; +class QDateTimeEdit; +QT_END_NAMESPACE + +class TransactionView : public QWidget +{ + Q_OBJECT +public: + explicit TransactionView(QWidget *parent = 0); + + void setModel(WalletModel *model); + + // Date ranges for filter + enum DateEnum + { + All, + Today, + ThisWeek, + ThisMonth, + LastMonth, + ThisYear, + Range + }; + +private: + WalletModel *model; + TransactionFilterProxy *transactionProxyModel; + QTableView *transactionView; + + QComboBox *dateWidget; + QComboBox *typeWidget; + QLineEdit *addressWidget; + QLineEdit *amountWidget; + + QMenu *contextMenu; + + QFrame *dateRangeWidget; + QDateTimeEdit *dateFrom; + QDateTimeEdit *dateTo; + + QWidget *createDateRangeWidget(); + +private slots: + void contextualMenu(const QPoint &); + void dateRangeChanged(); + +signals: + void doubleClicked(const QModelIndex&); + +public slots: + void chooseDate(int idx); + void chooseType(int idx); + void changedPrefix(const QString &prefix); + void changedAmount(const QString &amount); + void exportClicked(); + void showDetails(); + void copyAddress(); + void editLabel(); + void copyLabel(); + +}; + +#endif // TRANSACTIONVIEW_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp new file mode 100644 index 0000000000..10b3738c67 --- /dev/null +++ b/src/qt/walletmodel.cpp @@ -0,0 +1,182 @@ +#include "walletmodel.h" +#include "guiconstants.h" +#include "optionsmodel.h" +#include "addresstablemodel.h" +#include "transactiontablemodel.h" + +#include "headers.h" + +#include <QTimer> +#include <QSet> + +WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : + QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), + transactionTableModel(0), + cachedBalance(0), cachedUnconfirmedBalance(0), cachedNumTransactions(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); + + addressTableModel = new AddressTableModel(wallet, this); + transactionTableModel = new TransactionTableModel(wallet, this); +} + +qint64 WalletModel::getBalance() const +{ + return wallet->GetBalance(); +} + +qint64 WalletModel::getUnconfirmedBalance() const +{ + return wallet->GetUnconfirmedBalance(); +} + +int WalletModel::getNumTransactions() const +{ + int numTransactions = 0; + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + numTransactions = wallet->mapWallet.size(); + } + return numTransactions; +} + +void WalletModel::update() +{ + qint64 newBalance = getBalance(); + qint64 newUnconfirmedBalance = getUnconfirmedBalance(); + int newNumTransactions = getNumTransactions(); + + if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) + emit balanceChanged(newBalance, newUnconfirmedBalance); + + if(cachedNumTransactions != newNumTransactions) + emit numTransactionsChanged(newNumTransactions); + + cachedBalance = newBalance; + cachedUnconfirmedBalance = newUnconfirmedBalance; + cachedNumTransactions = newNumTransactions; + + addressTableModel->update(); +} + +bool WalletModel::validateAddress(const QString &address) +{ + CBitcoinAddress addressParsed(address.toStdString()); + return addressParsed.IsValid(); +} + +WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients) +{ + qint64 total = 0; + QSet<QString> setAddress; + QString hex; + + if(recipients.empty()) + { + return OK; + } + + // Pre-check input data for validity + foreach(const SendCoinsRecipient &rcp, recipients) + { + if(!validateAddress(rcp.address)) + { + return InvalidAddress; + } + setAddress.insert(rcp.address); + + if(rcp.amount <= 0) + { + return InvalidAmount; + } + total += rcp.amount; + } + + if(recipients.size() > setAddress.size()) + { + return DuplicateAddress; + } + + if(total > getBalance()) + { + return AmountExceedsBalance; + } + + if((total + nTransactionFee) > getBalance()) + { + return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee); + } + + CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(wallet->cs_mapWallet) + { + // Sendmany + std::vector<std::pair<CScript, int64> > vecSend; + foreach(const SendCoinsRecipient &rcp, recipients) + { + CScript scriptPubKey; + scriptPubKey.SetBitcoinAddress(rcp.address.toStdString()); + vecSend.push_back(make_pair(scriptPubKey, rcp.amount)); + } + + CWalletTx wtx; + CReserveKey keyChange(wallet); + int64 nFeeRequired = 0; + bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired); + + if(!fCreated) + { + if((total + nFeeRequired) > wallet->GetBalance()) + { + return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired); + } + return TransactionCreationFailed; + } + if(!ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString(), NULL)) + { + return Aborted; + } + if(!wallet->CommitTransaction(wtx, keyChange)) + { + return TransactionCommitFailed; + } + hex = QString::fromStdString(wtx.GetHash().GetHex()); + } + + // Add addresses that we've sent to to the address book + foreach(const SendCoinsRecipient &rcp, recipients) + { + std::string strAddress = rcp.address.toStdString(); + CRITICAL_BLOCK(wallet->cs_mapAddressBook) + { + if (!wallet->mapAddressBook.count(strAddress)) + wallet->SetAddressBookName(strAddress, rcp.label.toStdString()); + } + } + + // Update our model of the address table + addressTableModel->updateList(); + + return SendCoinsReturn(OK, 0, hex); +} + +OptionsModel *WalletModel::getOptionsModel() +{ + return optionsModel; +} + +AddressTableModel *WalletModel::getAddressTableModel() +{ + return addressTableModel; +} + +TransactionTableModel *WalletModel::getTransactionTableModel() +{ + return transactionTableModel; +} + + diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h new file mode 100644 index 0000000000..bb1c6e85d3 --- /dev/null +++ b/src/qt/walletmodel.h @@ -0,0 +1,92 @@ +#ifndef WALLETMODEL_H +#define WALLETMODEL_H + +#include <QObject> + +class OptionsModel; +class AddressTableModel; +class TransactionTableModel; +class CWallet; + +struct SendCoinsRecipient +{ + QString address; + QString label; + qint64 amount; +}; + +// Interface to Bitcoin wallet from Qt view code +class WalletModel : public QObject +{ + Q_OBJECT +public: + explicit WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); + + enum StatusCode + { + OK, + InvalidAmount, + InvalidAddress, + AmountExceedsBalance, + AmountWithFeeExceedsBalance, + DuplicateAddress, + TransactionCreationFailed, + TransactionCommitFailed, + Aborted, + MiscError + }; + + OptionsModel *getOptionsModel(); + AddressTableModel *getAddressTableModel(); + TransactionTableModel *getTransactionTableModel(); + + qint64 getBalance() const; + qint64 getUnconfirmedBalance() const; + int getNumTransactions() const; + + // Check address for validity + bool validateAddress(const QString &address); + + // Return status record for SendCoins, contains error id + information + struct SendCoinsReturn + { + SendCoinsReturn(StatusCode status, + qint64 fee=0, + QString hex=QString()): + status(status), fee(fee), hex(hex) {} + StatusCode status; + qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance" + QString hex; // is filled with the transaction hash if status is "OK" + }; + + // Send coins to a list of recipients + SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients); +private: + CWallet *wallet; + + // Wallet has an options model for wallet-specific options + // (transaction fee, for example) + OptionsModel *optionsModel; + + AddressTableModel *addressTableModel; + TransactionTableModel *transactionTableModel; + + qint64 cachedBalance; + qint64 cachedUnconfirmedBalance; + qint64 cachedNumTransactions; + +signals: + void balanceChanged(qint64 balance, qint64 unconfirmedBalance); + void numTransactionsChanged(int count); + + // Asynchronous error notification + void error(const QString &title, const QString &message); + +public slots: + +private slots: + void update(); +}; + + +#endif // WALLETMODEL_H diff --git a/src/qtui.h b/src/qtui.h new file mode 100644 index 0000000000..17fc44e94b --- /dev/null +++ b/src/qtui.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_EXTERNUI_H +#define BITCOIN_EXTERNUI_H + +#include <string> +#include <boost/function/function0.hpp> +#include "wallet.h" + +typedef void wxWindow; +#define wxYES 0x00000002 +#define wxOK 0x00000004 +#define wxNO 0x00000008 +#define wxYES_NO (wxYES|wxNO) +#define wxCANCEL 0x00000010 +#define wxAPPLY 0x00000020 +#define wxCLOSE 0x00000040 +#define wxOK_DEFAULT 0x00000000 +#define wxYES_DEFAULT 0x00000000 +#define wxNO_DEFAULT 0x00000080 +#define wxCANCEL_DEFAULT 0x80000000 +#define wxICON_EXCLAMATION 0x00000100 +#define wxICON_HAND 0x00000200 +#define wxICON_WARNING wxICON_EXCLAMATION +#define wxICON_ERROR wxICON_HAND +#define wxICON_QUESTION 0x00000400 +#define wxICON_INFORMATION 0x00000800 +#define wxICON_STOP wxICON_HAND +#define wxICON_ASTERISK wxICON_INFORMATION +#define wxICON_MASK (0x00000100|0x00000200|0x00000400|0x00000800) +#define wxFORWARD 0x00001000 +#define wxBACKWARD 0x00002000 +#define wxRESET 0x00004000 +#define wxHELP 0x00008000 +#define wxMORE 0x00010000 +#define wxSETUP 0x00020000 + +extern int MyMessageBox(const std::string& message, const std::string& caption="Message", int style=wxOK, wxWindow* parent=NULL, int x=-1, int y=-1); +#define wxMessageBox MyMessageBox +extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK, wxWindow* parent=NULL, int x=-1, int y=-1); +extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption, wxWindow* parent); +extern void CalledSetStatusBar(const std::string& strText, int nField); +extern void UIThreadCall(boost::function0<void> fn); +extern void MainFrameRepaint(); +extern void InitMessage(const std::string &message); +extern std::string _(const char* psz); + +#endif diff --git a/src/script.cpp b/src/script.cpp index 0fc4611fda..f5ca87ced8 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1138,6 +1138,7 @@ bool static ExtractAddressInner(const CScript& scriptPubKey, const CKeyStore* ke if (keystore == NULL || keystore->HaveKey(addressRet)) return true; } + return false; } diff --git a/src/serialize.h b/src/serialize.h index c2bfa2780e..367e849b55 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -30,6 +30,7 @@ typedef unsigned long long uint64; #endif #ifdef __WXMSW__ +#include <windows.h> // This is used to attempt to keep keying material out of swap // Note that VirtualLock does not provide this as a guarantee on Windows, // but, in practice, memory that has been VirtualLock'd almost never gets written to diff --git a/src/util.cpp b/src/util.cpp index 7693aaa52a..7cd92d2e8e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -263,8 +263,7 @@ int my_snprintf(char* buffer, size_t limit, const char* format, ...) return ret; } - -string strprintf(const char* format, ...) +string strprintf(const std::string &format, ...) { char buffer[50000]; char* p = buffer; @@ -274,7 +273,7 @@ string strprintf(const char* format, ...) { va_list arg_ptr; va_start(arg_ptr, format); - ret = _vsnprintf(p, limit, format, arg_ptr); + ret = _vsnprintf(p, limit, format.c_str(), arg_ptr); va_end(arg_ptr); if (ret >= 0 && ret < limit) break; @@ -291,14 +290,13 @@ string strprintf(const char* format, ...) return str; } - -bool error(const char* format, ...) +bool error(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) { diff --git a/src/util.h b/src/util.h index 8588201dae..a1e3c263d5 100644 --- a/src/util.h +++ b/src/util.h @@ -64,7 +64,7 @@ typedef unsigned long long uint64; #endif // This is needed because the foreach macro can't get over the comma in pair<t1, t2> -#define PAIRTYPE(t1, t2) pair<t1, t2> +#define PAIRTYPE(t1, t2) std::pair<t1, t2> // Used to bypass the rule against non-const reference to temporary // where it makes sense with wrappers such as CFlatData or CTxDB @@ -139,8 +139,7 @@ inline int myclosesocket(SOCKET& hSocket) return ret; } #define closesocket(s) myclosesocket(s) - -#ifndef GUI +#if !defined(QT_GUI) && !defined(GUI) inline const char* _(const char* psz) { return psz; @@ -155,7 +154,6 @@ inline const char* _(const char* psz) - extern std::map<std::string, std::string> mapArgs; extern std::map<std::string, std::vector<std::string> > mapMultiArgs; extern bool fDebug; @@ -176,8 +174,8 @@ void RandAddSeed(); void RandAddSeedPerfmon(); int OutputDebugStringF(const char* pszFormat, ...); int my_snprintf(char* buffer, size_t limit, const char* format, ...); -std::string strprintf(const char* format, ...); -bool error(const char* format, ...); +std::string strprintf(const std::string &format, ...); +bool error(const std::string &format, ...); void LogException(std::exception* pex, const char* pszThread); void PrintException(std::exception* pex, const char* pszThread); void PrintExceptionContinue(std::exception* pex, const char* pszThread); diff --git a/src/wallet.cpp b/src/wallet.cpp index 1f3f44bfa0..fcd49e66b9 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -261,7 +261,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) if (fInsertedNew || fUpdated) if (!wtx.WriteToDisk()) return false; - +#ifndef QT_GUI // If default receiving address gets used, replace it with a new one CScript scriptDefaultKey; scriptDefaultKey.SetBitcoinAddress(vchDefaultKey); @@ -273,7 +273,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) SetAddressBookName(CBitcoinAddress(vchDefaultKey), ""); } } - +#endif // Notify UI vWalletUpdated.push_back(hash); @@ -735,6 +735,21 @@ int64 CWallet::GetBalance() const return nTotal; } +int64 CWallet::GetUnconfirmedBalance() const +{ + int64 nTotal = 0; + CRITICAL_BLOCK(cs_mapWallet) + { + for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsFinal() && pcoin->IsConfirmed()) + continue; + nTotal += pcoin->GetAvailableCredit(); + } + } + return nTotal; +} bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const { diff --git a/src/wallet.h b/src/wallet.h index 51dfa5df5b..32ccd7d63b 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -76,6 +76,7 @@ public: void ReacceptWalletTransactions(); void ResendWalletTransactions(); int64 GetBalance() const; + int64 GetUnconfirmedBalance() const; bool CreateTransaction(const std::vector<std::pair<CScript, int64> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); |