diff --git a/README.rst b/README.rst index e3d6bd75..bb14be60 100644 --- a/README.rst +++ b/README.rst @@ -14,20 +14,18 @@ This has been implemented: - GUI only functionality (copy to clipboard, select address, address/transaction filter proxys) -- Bitcoin core is made compatible with Qt4, and linked against +- Bitcoin core is made compatible with Qt4 - Send coins dialog: address and input validation - Address book and transactions views and models +- Options dialog + - Sending coins This has to be done: -- Settings are not remembered between invocations yet - -- Minimize to tray / Minimize on close - - Start at system start - Internationalization (convert WX language files) diff --git a/bitcoin.pro b/bitcoin.pro index e2f282a3..9a3570aa 100644 --- a/bitcoin.pro +++ b/bitcoin.pro @@ -62,7 +62,8 @@ HEADERS += gui/include/bitcoingui.h \ gui/include/transactionrecord.h \ gui/include/guiconstants.h \ gui/include/optionsmodel.h \ - gui/include/monitoreddatamapper.h + gui/include/monitoreddatamapper.h \ + core/include/externui.h SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \ gui/src/transactiontablemodel.cpp \ gui/src/addresstablemodel.cpp \ diff --git a/core/include/externui.h b/core/include/externui.h new file mode 100644 index 00000000..e58ccc22 --- /dev/null +++ b/core/include/externui.h @@ -0,0 +1,45 @@ +// 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 + +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 fn); +extern void MainFrameRepaint(); + +#endif diff --git a/core/include/headers.h b/core/include/headers.h index d40c5ed0..33aeef33 100644 --- a/core/include/headers.h +++ b/core/include/headers.h @@ -127,7 +127,7 @@ #include "uibase.h" #include "ui.h" #else -#include "noui.h" +#include "externui.h" #endif #include "init.h" diff --git a/gui/include/bitcoingui.h b/gui/include/bitcoingui.h index 955d7b47..c2c786b2 100644 --- a/gui/include/bitcoingui.h +++ b/gui/include/bitcoingui.h @@ -11,6 +11,8 @@ class ClientModel; QT_BEGIN_NAMESPACE class QLabel; class QLineEdit; +class QTableView; +class QAbstractItemModel; QT_END_NAMESPACE class BitcoinGUI : public QMainWindow @@ -27,8 +29,12 @@ public: Sent = 2, Received = 3 } TabIndex; + +protected: + void changeEvent(QEvent *e); + void closeEvent(QCloseEvent *event); + private: - TransactionTableModel *transaction_model; ClientModel *model; QLineEdit *address; @@ -41,15 +47,17 @@ private: QAction *sendcoins; QAction *addressbook; QAction *about; - QAction *receiving_addresses; + QAction *receivingAddresses; QAction *options; - QAction *openBitCoin; + QAction *openBitcoin; QSystemTrayIcon *trayIcon; + QList transactionViews; void createActions(); QWidget *createTabs(); void createTrayIcon(); + void setTabsModel(QAbstractItemModel *transaction_model); public slots: void setBalance(qint64 balance); @@ -57,6 +65,7 @@ public slots: void setNumConnections(int count); void setNumBlocks(int count); void setNumTransactions(int count); + void error(const QString &title, const QString &message); private slots: void sendcoinsClicked(); @@ -64,10 +73,9 @@ private slots: void optionsClicked(); void receivingAddressesClicked(); void aboutClicked(); - void newAddressClicked(); void copyClipboardClicked(); - void error(const QString &title, const QString &message); + void trayIconActivated(QSystemTrayIcon::ActivationReason reason); }; #endif diff --git a/gui/include/clientmodel.h b/gui/include/clientmodel.h index d68b34fe..09d1fc92 100644 --- a/gui/include/clientmodel.h +++ b/gui/include/clientmodel.h @@ -5,6 +5,7 @@ class OptionsModel; class AddressTableModel; +class TransactionTableModel; class ClientModel : public QObject { @@ -25,6 +26,7 @@ public: OptionsModel *getOptionsModel(); AddressTableModel *getAddressTableModel(); + TransactionTableModel *getTransactionTableModel(); qint64 getBalance(); QString getAddress(); @@ -39,6 +41,7 @@ public: private: OptionsModel *optionsModel; AddressTableModel *addressTableModel; + TransactionTableModel *transactionTableModel; signals: void balanceChanged(qint64 balance); diff --git a/gui/include/optionsmodel.h b/gui/include/optionsmodel.h index 4fb6d251..0124e2ab 100644 --- a/gui/include/optionsmodel.h +++ b/gui/include/optionsmodel.h @@ -3,7 +3,12 @@ #include -/* Interface from QT to configuration data structure for bitcoin client */ +/* 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 diff --git a/gui/src/aboutdialog.cpp b/gui/src/aboutdialog.cpp index 3d7a3f98..13347961 100644 --- a/gui/src/aboutdialog.cpp +++ b/gui/src/aboutdialog.cpp @@ -1,11 +1,14 @@ #include "aboutdialog.h" #include "ui_aboutdialog.h" +#include "util.h" + AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); + ui->versionLabel->setText(QString::fromStdString(FormatFullVersion())); } AboutDialog::~AboutDialog() diff --git a/gui/src/bitcoin.cpp b/gui/src/bitcoin.cpp index c843cc40..dc3e8070 100644 --- a/gui/src/bitcoin.cpp +++ b/gui/src/bitcoin.cpp @@ -5,21 +5,85 @@ #include "clientmodel.h" #include "util.h" #include "init.h" +#include "externui.h" #include +#include + +// Need a global reference to process net thread +BitcoinGUI *guiref; + +int MyMessageBox(const std::string& message, const std::string& caption, int style, wxWindow* parent, int x, int y) +{ + // Message from main thread + printf("MyMessageBox\n"); + 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) +{ + // Query from network thread + // TODO + return true; +} + +void CalledSetStatusBar(const std::string& strText, int nField) +{ + // Only used for built-in mining, which is disabled, simple ignore +} + +void UIThreadCall(boost::function0 fn) +{ + // Only used for built-in mining, which is disabled, simple ignore +} + +void MainFrameRepaint() +{ +} int main(int argc, char *argv[]) { QApplication app(argc, argv); + app.setQuitOnLastWindowClosed(false); + BitcoinGUI window; + guiref = &window; try { if(AppInit2(argc, argv)) { ClientModel model; - BitcoinGUI window; window.setModel(&model); window.show(); + guiref = 0; /* Depending on settings: QApplication::setQuitOnLastWindowClosed(false); */ int retval = app.exec(); diff --git a/gui/src/bitcoingui.cpp b/gui/src/bitcoingui.cpp index 59964960..b6872046 100644 --- a/gui/src/bitcoingui.cpp +++ b/gui/src/bitcoingui.cpp @@ -12,6 +12,7 @@ #include "clientmodel.h" #include "guiutil.h" #include "editaddressdialog.h" +#include "optionsmodel.h" #include "main.h" @@ -48,26 +49,26 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): createActions(); - /* Menus */ + // Menus QMenu *file = menuBar()->addMenu("&File"); file->addAction(sendcoins); file->addSeparator(); file->addAction(quit); QMenu *settings = menuBar()->addMenu("&Settings"); - settings->addAction(receiving_addresses); + settings->addAction(receivingAddresses); settings->addAction(options); QMenu *help = menuBar()->addMenu("&Help"); help->addAction(about); - /* Toolbar */ + // Toolbar QToolBar *toolbar = addToolBar("Main toolbar"); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(sendcoins); toolbar->addAction(addressbook); - /* Address:
: New... : Paste to clipboard */ + // Address:
: New... : Paste to clipboard QHBoxLayout *hbox_address = new QHBoxLayout(); hbox_address->addWidget(new QLabel(tr("Your Bitcoin Address:"))); address = new QLineEdit(); @@ -80,7 +81,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): hbox_address->addWidget(button_new); hbox_address->addWidget(button_clipboard); - /* Balance: */ + // Balance: QHBoxLayout *hbox_balance = new QHBoxLayout(); hbox_balance->addWidget(new QLabel(tr("Balance:"))); hbox_balance->addSpacing(5);/* Add some spacing between the label and the text */ @@ -93,8 +94,6 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): QVBoxLayout *vbox = new QVBoxLayout(); vbox->addLayout(hbox_address); vbox->addLayout(hbox_balance); - - transaction_model = new TransactionTableModel(this); vbox->addWidget(createTabs()); @@ -102,7 +101,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): centralwidget->setLayout(vbox); setCentralWidget(centralwidget); - /* Create status bar */ + // Create status bar statusBar(); labelConnections = new QLabel(); @@ -121,7 +120,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): statusBar()->addPermanentWidget(labelBlocks); statusBar()->addPermanentWidget(labelTransactions); - /* Action bindings */ + // Action bindings connect(button_new, SIGNAL(clicked()), this, SLOT(newAddressClicked())); connect(button_clipboard, SIGNAL(clicked()), this, SLOT(copyClipboardClicked())); @@ -134,22 +133,24 @@ void BitcoinGUI::createActions() sendcoins = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this); addressbook = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this); about = new QAction(QIcon(":/icons/bitcoin"), tr("&About"), this); - receiving_addresses = new QAction(QIcon(":/icons/receiving-addresses"), tr("Your &Receiving Addresses..."), this); + receivingAddresses = new QAction(QIcon(":/icons/receiving-addresses"), tr("Your &Receiving Addresses..."), this); options = new QAction(QIcon(":/icons/options"), tr("&Options..."), this); - openBitCoin = new QAction(QIcon(":/icons/bitcoin"), "Open Bitcoin", this); + openBitcoin = new QAction(QIcon(":/icons/bitcoin"), "Open &Bitcoin", this); connect(quit, SIGNAL(triggered()), qApp, SLOT(quit())); connect(sendcoins, SIGNAL(triggered()), this, SLOT(sendcoinsClicked())); connect(addressbook, SIGNAL(triggered()), this, SLOT(addressbookClicked())); - connect(receiving_addresses, SIGNAL(triggered()), this, SLOT(receivingAddressesClicked())); + connect(receivingAddresses, SIGNAL(triggered()), this, SLOT(receivingAddressesClicked())); connect(options, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(about, SIGNAL(triggered()), this, SLOT(aboutClicked())); + connect(openBitcoin, SIGNAL(triggered()), this, SLOT(show())); } void BitcoinGUI::setModel(ClientModel *model) { this->model = model; + // Keep up to date with client setBalance(model->getBalance()); connect(model, SIGNAL(balanceChanged(qint64)), this, SLOT(setBalance(qint64))); @@ -165,14 +166,17 @@ void BitcoinGUI::setModel(ClientModel *model) setAddress(model->getAddress()); connect(model, SIGNAL(addressChanged(QString)), this, SLOT(setAddress(QString))); - /* Report errors from network/worker thread */ - connect(model, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString))); + // Report errors from network/worker thread + connect(model, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString))); + + // Put transaction list in tabs + setTabsModel(model->getTransactionTableModel()); } void BitcoinGUI::createTrayIcon() { QMenu *trayIconMenu = new QMenu(this); - trayIconMenu->addAction(openBitCoin); + trayIconMenu->addAction(openBitcoin); trayIconMenu->addAction(sendcoins); trayIconMenu->addAction(options); trayIconMenu->addSeparator(); @@ -181,23 +185,48 @@ void BitcoinGUI::createTrayIcon() trayIcon = new QSystemTrayIcon(this); trayIcon->setContextMenu(trayIconMenu); 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::DoubleClick) + { + // Doubleclick on system tray icon triggers "open bitcoin" + openBitcoin->trigger(); + } +} + QWidget *BitcoinGUI::createTabs() { - QStringList tab_filters, tab_labels; - tab_filters << "^." - << "^["+TransactionTableModel::Sent+TransactionTableModel::Received+"]" - << "^["+TransactionTableModel::Sent+"]" - << "^["+TransactionTableModel::Received+"]"; + QStringList tab_labels; tab_labels << tr("All transactions") << tr("Sent/Received") << tr("Sent") << tr("Received"); - QTabWidget *tabs = new QTabWidget(this); + QTabWidget *tabs = new QTabWidget(this); for(int i = 0; i < tab_labels.size(); ++i) + { + QTableView *view = new QTableView(this); + tabs->addTab(view, tab_labels.at(i)); + transactionViews.append(view); + } + + return tabs; +} + +void BitcoinGUI::setTabsModel(QAbstractItemModel *transaction_model) +{ + QStringList tab_filters; + tab_filters << "^." + << "^["+TransactionTableModel::Sent+TransactionTableModel::Received+"]" + << "^["+TransactionTableModel::Sent+"]" + << "^["+TransactionTableModel::Received+"]"; + + for(int i = 0; i < transactionViews.size(); ++i) { QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this); proxy_model->setSourceModel(transaction_model); @@ -206,7 +235,7 @@ QWidget *BitcoinGUI::createTabs() proxy_model->setFilterRegExp(QRegExp(tab_filters.at(i))); proxy_model->setSortRole(Qt::EditRole); - QTableView *transaction_table = new QTableView(this); + QTableView *transaction_table = transactionViews.at(i); transaction_table->setModel(proxy_model); transaction_table->setSelectionBehavior(QAbstractItemView::SelectRows); transaction_table->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -224,10 +253,7 @@ QWidget *BitcoinGUI::createTabs() TransactionTableModel::Debit, 79); transaction_table->horizontalHeader()->resizeSection( TransactionTableModel::Credit, 79); - - tabs->addTab(transaction_table, tab_labels.at(i)); } - return tabs; } void BitcoinGUI::sendcoinsClicked() @@ -273,7 +299,7 @@ void BitcoinGUI::newAddressClicked() if(dlg.exec()) { QString newAddress = dlg.saveCurrentRow(); - /* Set returned address as new default address */ + // Set returned address as new default addres if(!newAddress.isEmpty()) { model->setAddress(newAddress); @@ -283,7 +309,7 @@ void BitcoinGUI::newAddressClicked() void BitcoinGUI::copyClipboardClicked() { - /* Copy text in address to clipboard */ + // Copy text in address to clipboard QApplication::clipboard()->setText(address->text()); } @@ -314,8 +340,43 @@ void BitcoinGUI::setNumTransactions(int count) void BitcoinGUI::error(const QString &title, const QString &message) { - /* Report errors from network/worker thread */ - QMessageBox::critical(this, title, - message, - QMessageBox::Ok, QMessageBox::Ok); + // 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(model->getOptionsModel()->getMinimizeToTray()) + { + if (isMinimized()) + { + hide(); + e->ignore(); + } else { + e->accept(); + } + } + } + QMainWindow::changeEvent(e); +} + +void BitcoinGUI::closeEvent(QCloseEvent *event) +{ + if(!model->getOptionsModel()->getMinimizeToTray() && + !model->getOptionsModel()->getMinimizeOnClose()) + { + qApp->quit(); + } + QMainWindow::closeEvent(event); } diff --git a/gui/src/clientmodel.cpp b/gui/src/clientmodel.cpp index 5f3517ab..8fd3599e 100644 --- a/gui/src/clientmodel.cpp +++ b/gui/src/clientmodel.cpp @@ -3,11 +3,13 @@ #include "guiconstants.h" #include "optionsmodel.h" #include "addresstablemodel.h" +#include "transactiontablemodel.h" #include ClientModel::ClientModel(QObject *parent) : - QObject(parent), optionsModel(0), addressTableModel(0) + QObject(parent), optionsModel(0), addressTableModel(0), + transactionTableModel(0) { /* Until signal notifications is built into the bitcoin core, simply update everything after polling using a timer. @@ -18,6 +20,7 @@ ClientModel::ClientModel(QObject *parent) : optionsModel = new OptionsModel(this); addressTableModel = new AddressTableModel(this); + transactionTableModel = new TransactionTableModel(this); } qint64 ClientModel::getBalance() @@ -140,3 +143,8 @@ AddressTableModel *ClientModel::getAddressTableModel() { return addressTableModel; } + +TransactionTableModel *ClientModel::getTransactionTableModel() +{ + return transactionTableModel; +} diff --git a/gui/src/optionsdialog.cpp b/gui/src/optionsdialog.cpp index 8e7f403a..1ec777c4 100644 --- a/gui/src/optionsdialog.cpp +++ b/gui/src/optionsdialog.cpp @@ -207,6 +207,10 @@ MainOptionsPage::MainOptionsPage(QWidget *parent): 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) diff --git a/gui/src/optionsmodel.cpp b/gui/src/optionsmodel.cpp index f653f67e..37d5cb15 100644 --- a/gui/src/optionsmodel.cpp +++ b/gui/src/optionsmodel.cpp @@ -1,5 +1,6 @@ #include "optionsmodel.h" #include "main.h" +#include "net.h" #include @@ -47,6 +48,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in bool successful = true; /* set to false on parse error */ if(role == Qt::EditRole) { + CWalletDB walletdb; switch(index.row()) { case StartAtStartup: @@ -54,15 +56,22 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in 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: { @@ -71,6 +80,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in if (addr.ip != INADDR_NONE) { addrProxy.ip = addr.ip; + walletdb.WriteSetting("addrProxy", addrProxy); } else { successful = false; } @@ -82,6 +92,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in if (nPort > 0 && nPort < USHRT_MAX) { addrProxy.port = htons(nPort); + walletdb.WriteSetting("addrProxy", addrProxy); } else { successful = false; } @@ -92,6 +103,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in if(ParseMoney(value.toString().toStdString(), retval)) { nTransactionFee = retval; + walletdb.WriteSetting("nTransactionFee", nTransactionFee); } else { successful = false; /* parse error */ } @@ -111,12 +123,12 @@ qint64 OptionsModel::getTransactionFee() return nTransactionFee; } -bool getMinimizeToTray() +bool OptionsModel::getMinimizeToTray() { return fMinimizeToTray; } -bool getMinimizeOnClose() +bool OptionsModel::getMinimizeOnClose() { return fMinimizeOnClose; }