From c3e0734dbc3c9ace13fbf4188b52aff6e56832b8 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 1 Jun 2011 14:40:06 +0200 Subject: [PATCH] implement options model / improve view with validators --- bitcoin.pro | 8 +- gui/include/mainoptionspage.h | 37 --------- gui/include/monitoreddatamapper.h | 32 ++++++++ gui/include/optionsdialog.h | 5 +- gui/include/optionsmodel.h | 4 +- gui/src/mainoptionspage.cpp | 82 ------------------- gui/src/monitoreddatamapper.cpp | 39 +++++++++ gui/src/optionsdialog.cpp | 131 ++++++++++++++++++++++++++++-- gui/src/optionsmodel.cpp | 73 ++++++++++++++++- 9 files changed, 276 insertions(+), 135 deletions(-) delete mode 100644 gui/include/mainoptionspage.h create mode 100644 gui/include/monitoreddatamapper.h delete mode 100644 gui/src/mainoptionspage.cpp create mode 100644 gui/src/monitoreddatamapper.cpp diff --git a/bitcoin.pro b/bitcoin.pro index 53835632a..d34488dd4 100644 --- a/bitcoin.pro +++ b/bitcoin.pro @@ -15,7 +15,6 @@ HEADERS += gui/include/bitcoingui.h \ gui/include/transactiontablemodel.h \ gui/include/addresstablemodel.h \ gui/include/optionsdialog.h \ - gui/include/mainoptionspage.h \ gui/include/sendcoinsdialog.h \ gui/include/addressbookdialog.h \ gui/include/aboutdialog.h \ @@ -61,12 +60,12 @@ HEADERS += gui/include/bitcoingui.h \ gui/include/guiutil.h \ gui/include/transactionrecord.h \ gui/include/guiconstants.h \ - gui/include/optionsmodel.h + gui/include/optionsmodel.h \ + gui/include/monitoreddatamapper.h SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \ gui/src/transactiontablemodel.cpp \ gui/src/addresstablemodel.cpp \ gui/src/optionsdialog.cpp \ - gui/src/mainoptionspage.cpp \ gui/src/sendcoinsdialog.cpp \ gui/src/addressbookdialog.cpp \ gui/src/aboutdialog.cpp \ @@ -88,7 +87,8 @@ SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \ gui/src/clientmodel.cpp \ gui/src/guiutil.cpp \ gui/src/transactionrecord.cpp \ - gui/src/optionsmodel.cpp + gui/src/optionsmodel.cpp \ + gui/src/monitoreddatamapper.cpp RESOURCES += \ gui/bitcoin.qrc diff --git a/gui/include/mainoptionspage.h b/gui/include/mainoptionspage.h deleted file mode 100644 index 4ef5e60a2..000000000 --- a/gui/include/mainoptionspage.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef MAINOPTIONSPAGE_H -#define MAINOPTIONSPAGE_H - -#include - -QT_BEGIN_NAMESPACE -class QDataWidgetMapper; -class QCheckBox; -class QLineEdit; -QT_END_NAMESPACE - -class OptionsModel; - -class MainOptionsPage : public QWidget -{ - Q_OBJECT -public: - explicit MainOptionsPage(QWidget *parent=0); - - void setMapper(QDataWidgetMapper *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; - QLineEdit *fee_edit; - -signals: - -public slots: - -}; - -#endif // MAINOPTIONSPAGE_H diff --git a/gui/include/monitoreddatamapper.h b/gui/include/monitoreddatamapper.h new file mode 100644 index 000000000..4dd2d1a86 --- /dev/null +++ b/gui/include/monitoreddatamapper.h @@ -0,0 +1,32 @@ +#ifndef MONITOREDDATAMAPPER_H +#define MONITOREDDATAMAPPER_H + +#include + +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/gui/include/optionsdialog.h b/gui/include/optionsdialog.h index ff8542d41..07e85297d 100644 --- a/gui/include/optionsdialog.h +++ b/gui/include/optionsdialog.h @@ -7,11 +7,11 @@ QT_BEGIN_NAMESPACE class QStackedWidget; class QListWidget; class QListWidgetItem; -class QDataWidgetMapper; class QPushButton; QT_END_NAMESPACE class OptionsModel; class MainOptionsPage; +class MonitoredDataMapper; class OptionsDialog : public QDialog { @@ -30,12 +30,13 @@ private slots: void cancelClicked(); void applyClicked(); void enableApply(); + void disableApply(); private: QListWidget *contents_widget; QStackedWidget *pages_widget; MainOptionsPage *main_options_page; OptionsModel *model; - QDataWidgetMapper *mapper; + MonitoredDataMapper *mapper; QPushButton *apply_button; void setupMainPage(); diff --git a/gui/include/optionsmodel.h b/gui/include/optionsmodel.h index 3e0bcc1dd..4fb6d2514 100644 --- a/gui/include/optionsmodel.h +++ b/gui/include/optionsmodel.h @@ -3,7 +3,7 @@ #include -/* Configuration data structure for bitcoin client */ +/* Interface from QT to configuration data structure for bitcoin client */ class OptionsModel : public QAbstractListModel { Q_OBJECT @@ -28,6 +28,8 @@ public: /* Explicit getters */ qint64 getTransactionFee(); + bool getMinimizeToTray(); + bool getMinimizeOnClose(); signals: public slots: diff --git a/gui/src/mainoptionspage.cpp b/gui/src/mainoptionspage.cpp deleted file mode 100644 index 3d69baeda..000000000 --- a/gui/src/mainoptionspage.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "mainoptionspage.h" -#include "optionsmodel.h" - -#include -#include -#include -#include -#include -#include -#include - -MainOptionsPage::MainOptionsPage(QWidget *parent): - QWidget(parent) -{ - QVBoxLayout *layout = new QVBoxLayout(); - - bitcoin_at_startup = new QCheckBox(tr("&Start Bitcoin on window system startup")); - layout->addWidget(bitcoin_at_startup); - - minimize_to_tray = new QCheckBox(tr("&Minimize to the tray instead of the taskbar")); - layout->addWidget(minimize_to_tray); - - map_port_upnp = new QCheckBox(tr("Map port using &UPnP")); - layout->addWidget(map_port_upnp); - - minimize_on_close = new QCheckBox(tr("M&inimize on close")); - layout->addWidget(minimize_on_close); - - connect_socks4 = new QCheckBox(tr("&Connect through socks4 proxy:")); - 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_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_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 QLineEdit(); - fee_edit->setMaximumWidth(70); - 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); -} - -void MainOptionsPage::setMapper(QDataWidgetMapper *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); -} - diff --git a/gui/src/monitoreddatamapper.cpp b/gui/src/monitoreddatamapper.cpp new file mode 100644 index 000000000..e70aa7ebf --- /dev/null +++ b/gui/src/monitoreddatamapper.cpp @@ -0,0 +1,39 @@ +#include "monitoreddatamapper.h" + +#include +#include +#include +#include + + +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/gui/src/optionsdialog.cpp b/gui/src/optionsdialog.cpp index 4d0493a26..8e7f403a7 100644 --- a/gui/src/optionsdialog.cpp +++ b/gui/src/optionsdialog.cpp @@ -1,14 +1,42 @@ #include "optionsdialog.h" #include "optionsmodel.h" -#include "mainoptionspage.h" +#include "monitoreddatamapper.h" #include #include #include #include #include -#include -#include + +#include +#include +#include +#include +#include +#include + +/* First (currently only) 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; + QLineEdit *fee_edit; + +signals: + +public slots: + +}; OptionsDialog::OptionsDialog(QWidget *parent): QDialog(parent), contents_widget(0), pages_widget(0), @@ -50,10 +78,13 @@ OptionsDialog::OptionsDialog(QWidget *parent): setWindowTitle(tr("Options")); /* Widget-to-option mapper */ - mapper = new QDataWidgetMapper(); + mapper = new MonitoredDataMapper(this); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setOrientation(Qt::Vertical); - connect(mapper->itemDelegate(), SIGNAL(commitData(QWidget*)), this, SLOT(enableApply())); + /* 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(ok_button, SIGNAL(clicked()), this, SLOT(okClicked())); @@ -101,3 +132,93 @@ 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")); + layout->addWidget(bitcoin_at_startup); + + minimize_to_tray = new QCheckBox(tr("&Minimize to the tray instead of the taskbar")); + layout->addWidget(minimize_to_tray); + + map_port_upnp = new QCheckBox(tr("Map port using &UPnP")); + layout->addWidget(map_port_upnp); + + minimize_on_close = new QCheckBox(tr("M&inimize on close")); + layout->addWidget(minimize_on_close); + + connect_socks4 = new QCheckBox(tr("&Connect through socks4 proxy:")); + 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_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_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 QLineEdit(); + fee_edit->setMaximumWidth(70); + + QDoubleValidator *amountValidator = new QDoubleValidator(this); + amountValidator->setDecimals(8); + amountValidator->setBottom(0.0); + fee_edit->setValidator(amountValidator); + + 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))); +} + +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); +} + diff --git a/gui/src/optionsmodel.cpp b/gui/src/optionsmodel.cpp index e3287f391..f653f67e5 100644 --- a/gui/src/optionsmodel.cpp +++ b/gui/src/optionsmodel.cpp @@ -15,10 +15,8 @@ int OptionsModel::rowCount(const QModelIndex & parent) const QVariant OptionsModel::data(const QModelIndex & index, int role) const { - qDebug() << "OptionsModel::data" << " " << index.row() << " " << role; if(role == Qt::EditRole) { - /* Delegate to specific column handlers */ switch(index.row()) { case StartAtStartup: @@ -46,12 +44,79 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) { - qDebug() << "OptionsModel::setData" << " " << index.row() << "=" << value; + bool successful = true; /* set to false on parse error */ + if(role == Qt::EditRole) + { + switch(index.row()) + { + case StartAtStartup: + successful = false; /*TODO*/ + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + break; + case MapPortUPnP: + fUseUPnP = value.toBool(); + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + break; + case ConnectSOCKS4: + fUseProxy = value.toBool(); + break; + case ProxyIP: + { + /* Use CAddress to parse IP */ + CAddress addr(value.toString().toStdString() + ":1"); + if (addr.ip != INADDR_NONE) + { + addrProxy.ip = addr.ip; + } else { + successful = false; + } + } + break; + case ProxyPort: + { + int nPort = atoi(value.toString().toAscii().data()); + if (nPort > 0 && nPort < USHRT_MAX) + { + addrProxy.port = htons(nPort); + } else { + successful = false; + } + } + break; + case Fee: { + int64 retval; + if(ParseMoney(value.toString().toStdString(), retval)) + { + nTransactionFee = retval; + } else { + successful = false; /* parse error */ + } + } + break; + default: + break; + } + } emit dataChanged(index, index); - return true; + + return successful; } qint64 OptionsModel::getTransactionFee() { return nTransactionFee; } + +bool getMinimizeToTray() +{ + return fMinimizeToTray; +} + +bool getMinimizeOnClose() +{ + return fMinimizeOnClose; +}