Merge pull request #583 from laanwj/qt3

More Qt GUI updates
- Make USE_SSL qmake build flag actually work
- Improve mac experience, general UI improvements
- Add keyboard shortcut to switch between tabs
This commit is contained in:
Wladimir J. van der Laan 2011-10-15 08:33:10 -07:00
commit b68a8a6b34
22 changed files with 465 additions and 131 deletions

View File

@ -17,10 +17,17 @@ OBJECTS_DIR = build
MOC_DIR = build
UI_DIR = build
# use: qmake "USE_UPNP=0" (disable by default) or "USE_UPNP=1" (enable by default)
# miniupnpc (http://miniupnp.free.fr/files/) must be installed
count(USE_UPNP, 1) {
# use: qmake "USE_UPNP=1" ( enabled by default; default)
# or: qmake "USE_UPNP=0" (disabled by default)
# or: qmake "USE_UPNP=-" (not supported)
# miniupnpc (http://miniupnp.free.fr/files/) must be installed for support
contains(USE_UPNP, -) {
message(Building without UPNP support)
} else {
message(Building with UPNP support)
count(USE_UPNP, 0) {
USE_UPNP=1
}
DEFINES += USE_UPNP=$$USE_UPNP
LIBS += -lminiupnpc
}
@ -33,7 +40,7 @@ contains(USE_DBUS, 1) {
}
# use: qmake "USE_SSL=1"
contains(USE_DBUS, 1) {
contains(USE_SSL, 1) {
message(Building with SSL support for RPC)
DEFINES += USE_SSL
}
@ -221,6 +228,9 @@ windows:LIBS += -lws2_32 -lgdi32
windows:DEFINES += WIN32
windows:RC_FILE = src/qt/res/bitcoin-qt.rc
macx:HEADERS += src/qt/macdockiconhandler.h
macx:OBJECTIVE_SOURCES += src/qt/macdockiconhandler.mm
macx:LIBS += -framework Foundation -framework ApplicationServices -framework AppKit
macx:DEFINES += MAC_OSX MSG_NOSIGNAL=0 BOOST_FILESYSTEM_VERSION=3
macx:ICON = src/qt/res/icons/bitcoin.icns
macx:TARGET = "Bitcoin-Qt"

View File

@ -119,13 +119,13 @@ http://miniupnp.tuxfamily.org/files/. UPnP support is not compiled in by defaul
Set USE_UPNP to a different value to control this:
+------------+--------------------------------------------------------------+
| USE_UPNP= | (the default) no UPnP support, miniupnpc not required; |
+------------+--------------------------------------------------------------+
| USE_UPNP=0 | UPnP support turned off by default at runtime; |
+------------+--------------------------------------------------------------+
| USE_UPNP=1 | UPnP support turned on by default at runtime. |
+------------+--------------------------------------------------------------+
+------------+--------------------------------------------------------------------------+
| USE_UPNP=- | no UPnP support, miniupnpc not required; |
+------------+--------------------------------------------------------------------------+
| USE_UPNP=0 | (the default) built with UPnP, support turned off by default at runtime; |
+------------+--------------------------------------------------------------------------+
| USE_UPNP=1 | build with UPnP support turned on by default at runtime. |
+------------+--------------------------------------------------------------------------+
Mac OS X users: miniupnpc is currently outdated on MacPorts. An updated Portfile is provided in contrib/miniupnpc within this project.
You can execute the following commands in a terminal to install it:

View File

@ -18,6 +18,13 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
tab(tab)
{
ui->setupUi(this);
#ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac
ui->newAddressButton->setIcon(QIcon());
ui->copyToClipboard->setIcon(QIcon());
ui->deleteButton->setIcon(QIcon());
#endif
switch(mode)
{
case ForSending:

View File

@ -16,6 +16,7 @@
#include <QLocale>
#include <QTranslator>
#include <QSplashScreen>
#include <QLibraryInfo>
// Need a global reference for the notifications to find the GUI
BitcoinGUI *guiref;
@ -119,9 +120,16 @@ int main(int argc, char *argv[])
// Load language file for system locale
QString locale = QLocale::system().name();
QTranslator qtTranslator;
qtTranslator.load(QLibraryInfo::location(QLibraryInfo::TranslationsPath) + "/qt_" + locale);
if (!qtTranslator.isEmpty())
app.installTranslator(&qtTranslator);
QTranslator translator;
translator.load(":/translations/"+locale);
app.installTranslator(&translator);
if (!translator.isEmpty())
app.installTranslator(&translator);
app.setApplicationName(QApplication::translate("main", "Bitcoin Qt"));
QSplashScreen splash(QPixmap(":/images/splash"), 0);
splash.show();

View File

@ -1,33 +1,30 @@
#include "bitcoinamountfield.h"
#include "qvalidatedlineedit.h"
#include "qvaluecombobox.h"
#include "bitcoinunits.h"
#include "guiconstants.h"
#include <QLabel>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QApplication>
#include <qmath.h>
BitcoinAmountField::BitcoinAmountField(QWidget *parent):
QWidget(parent), amount(0), decimals(0), currentUnit(-1)
QWidget(parent), amount(0), currentUnit(-1)
{
amount = new QValidatedLineEdit(this);
amount->setValidator(new QRegExpValidator(QRegExp("[0-9]*"), this));
amount->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
amount = new QDoubleSpinBox(this);
amount->setLocale(QLocale::c());
amount->setDecimals(8);
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);
amount->setMaximumWidth(170);
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);
@ -40,8 +37,7 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent):
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(amount, SIGNAL(valueChanged(QString)), this, SIGNAL(textChanged()));
connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
// Set default based on configuration
@ -50,79 +46,72 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent):
void BitcoinAmountField::setText(const QString &text)
{
const QStringList parts = text.split(QString("."));
if(parts.size() == 2)
{
amount->setText(parts[0]);
decimals->setText(parts[1]);
}
if (text.isEmpty())
amount->clear();
else
{
amount->setText(QString());
decimals->setText(QString());
}
amount->setValue(text.toDouble());
}
void BitcoinAmountField::clear()
{
amount->clear();
decimals->clear();
unit->setCurrentIndex(0);
}
bool BitcoinAmountField::validate()
{
bool valid = true;
if(decimals->text().isEmpty())
{
decimals->setValid(false);
if (amount->value() == 0.0)
valid = false;
}
if(!BitcoinUnits::parse(currentUnit, text(), 0))
{
setValid(false);
if (valid && !BitcoinUnits::parse(currentUnit, text(), 0))
valid = false;
}
setValid(valid);
return valid;
}
void BitcoinAmountField::setValid(bool valid)
{
amount->setValid(valid);
decimals->setValid(valid);
if (valid)
amount->setStyleSheet("");
else
amount->setStyleSheet(STYLE_INVALID);
}
QString BitcoinAmountField::text() const
{
if(decimals->text().isEmpty() && amount->text().isEmpty())
{
if (amount->text().isEmpty())
return QString();
}
return amount->text() + QString(".") + decimals->text();
else
return amount->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)
if (event->type() == QEvent::FocusIn)
{
// Clear invalid flag on focus
setValid(true);
}
else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Period || keyEvent->key() == Qt::Key_Comma)
if (keyEvent->key() == Qt::Key_Comma)
{
decimals->setFocus();
decimals->selectAll();
// Translate a comma into a period
QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
qApp->sendEvent(object, &periodKeyEvent);
return true;
}
}
return false;
return QWidget::eventFilter(object, event);
}
QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
{
QWidget::setTabOrder(prev, amount);
QWidget::setTabOrder(amount, decimals);
return decimals;
return amount;
}
qint64 BitcoinAmountField::value(bool *valid_out) const
@ -156,8 +145,8 @@ void BitcoinAmountField::unitChanged(int idx)
currentUnit = newUnit;
// Set max length after retrieving the value, to prevent truncation
amount->setMaxLength(BitcoinUnits::amountDigits(currentUnit));
decimals->setMaxLength(BitcoinUnits::decimals(currentUnit));
amount->setDecimals(BitcoinUnits::decimals(currentUnit));
amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals()));
if(valid)
{

View File

@ -4,7 +4,7 @@
#include <QWidget>
QT_BEGIN_NAMESPACE
class QValidatedLineEdit;
class QDoubleSpinBox;
class QValueComboBox;
QT_END_NAMESPACE
@ -13,7 +13,7 @@ QT_END_NAMESPACE
class BitcoinAmountField: public QWidget
{
Q_OBJECT
Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY textChanged USER true);
Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY textChanged USER true)
public:
explicit BitcoinAmountField(QWidget *parent = 0);
@ -38,12 +38,11 @@ signals:
void textChanged();
protected:
// Intercept '.' and ',' keys, if pressed focus a specified widget
// Intercept focus-in event and ',' keypresses
bool eventFilter(QObject *object, QEvent *event);
private:
QValidatedLineEdit *amount;
QValidatedLineEdit *decimals;
QDoubleSpinBox *amount;
QValueComboBox *unit;
int currentUnit;

View File

@ -22,6 +22,10 @@
#include "askpassphrasedialog.h"
#include "notificator.h"
#ifdef Q_WS_MAC
#include "macdockiconhandler.h"
#endif
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
@ -57,40 +61,26 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
{
resize(850, 550);
setWindowTitle(tr("Bitcoin Wallet"));
#ifndef Q_WS_MAC
setWindowIcon(QIcon(":icons/bitcoin"));
#else
setUnifiedTitleAndToolBarOnMac(true);
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
// Accept D&D of URIs
setAcceptDrops(true);
// Create actions for the toolbar, menu bar and tray/dock icon
createActions();
// Menus
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(sendCoinsAction);
file->addAction(receiveCoinsAction);
file->addSeparator();
file->addAction(quitAction);
QMenu *settings = menuBar()->addMenu(tr("&Settings"));
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addSeparator();
settings->addAction(optionsAction);
// Create application menu bar
createMenuBar();
QMenu *help = menuBar()->addMenu(tr("&Help"));
help->addAction(aboutAction);
// Toolbars
QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar->addAction(overviewAction);
toolbar->addAction(sendCoinsAction);
toolbar->addAction(receiveCoinsAction);
toolbar->addAction(historyAction);
toolbar->addAction(addressBookAction);
// Create the toolbars
createToolBars();
QToolBar *toolbar2 = addToolBar(tr("Actions toolbar"));
toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar2->addAction(exportAction);
// Create the tray icon (or setup the dock icon)
createTrayIcon();
// Create tabs
overviewPage = new OverviewPage();
@ -149,8 +139,6 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
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
@ -162,6 +150,13 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
gotoOverviewPage();
}
BitcoinGUI::~BitcoinGUI()
{
#ifdef Q_WS_MAC
delete appMenuBar;
#endif
}
void BitcoinGUI::createActions()
{
QActionGroup *tabGroup = new QActionGroup(this);
@ -169,26 +164,31 @@ void BitcoinGUI::createActions()
overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
overviewAction->setToolTip(tr("Show general overview of wallet"));
overviewAction->setCheckable(true);
overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
tabGroup->addAction(overviewAction);
historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
historyAction->setToolTip(tr("Browse transaction history"));
historyAction->setCheckable(true);
historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
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);
addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5));
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);
receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
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);
sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
tabGroup->addAction(sendCoinsAction);
connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
@ -197,12 +197,16 @@ void BitcoinGUI::createActions()
connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
quitAction = new QAction(QIcon(":/icons/quit"), tr("&Exit"), this);
quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
quitAction->setToolTip(tr("Quit application"));
aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About"), this);
quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
quitAction->setMenuRole(QAction::QuitRole);
aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About %1").arg(qApp->applicationName()), this);
aboutAction->setToolTip(tr("Show information about Bitcoin"));
aboutAction->setMenuRole(QAction::AboutQtRole);
optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
optionsAction->setToolTip(tr("Modify configuration options for bitcoin"));
optionsAction->setMenuRole(QAction::PreferencesRole);
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);
@ -221,6 +225,45 @@ void BitcoinGUI::createActions()
connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
}
void BitcoinGUI::createMenuBar()
{
#ifdef Q_WS_MAC
// Create a decoupled menu bar on Mac which stays even if the window is closed
appMenuBar = new QMenuBar();
#else
// Get the main window's menu bar on other platforms
appMenuBar = menuBar();
#endif
// Configure the menus
QMenu *file = appMenuBar->addMenu(tr("&File"));
file->addAction(quitAction);
QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addSeparator();
settings->addAction(optionsAction);
QMenu *help = appMenuBar->addMenu(tr("&Help"));
help->addAction(aboutAction);
}
void BitcoinGUI::createToolBars()
{
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);
}
void BitcoinGUI::setClientModel(ClientModel *clientModel)
{
this->clientModel = clientModel;
@ -229,7 +272,11 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel)
{
QString title_testnet = windowTitle() + QString(" ") + tr("[testnet]");
setWindowTitle(title_testnet);
#ifndef Q_WS_MAC
setWindowIcon(QIcon(":icons/bitcoin_testnet"));
#else
MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet"));
#endif
if(trayIcon)
{
trayIcon->setToolTip(title_testnet);
@ -276,23 +323,39 @@ void BitcoinGUI::setWalletModel(WalletModel *walletModel)
void BitcoinGUI::createTrayIcon()
{
QMenu *trayIconMenu = new QMenu(this);
trayIconMenu->addAction(openBitcoinAction);
trayIconMenu->addAction(optionsAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
QMenu *trayIconMenu;
#ifndef Q_WS_MAC
trayIcon = new QSystemTrayIcon(this);
trayIconMenu = new QMenu(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();
#else
// Note: On Mac, the dock icon is used to provide the tray's functionality.
MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
connect(dockIconHandler, SIGNAL(dockIconClicked()), openBitcoinAction, SLOT(trigger()));
trayIconMenu = dockIconHandler->dockMenu();
#endif
// Configuration of the tray icon (or dock icon) icon menu
trayIconMenu->addAction(openBitcoinAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(receiveCoinsAction);
trayIconMenu->addAction(sendCoinsAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(optionsAction);
#ifndef Q_WS_MAC // This is built-in on Mac
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
#endif
notificator = new Notificator(tr("bitcoin-qt"), trayIcon);
}
#ifndef Q_WS_MAC
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
if(reason == QSystemTrayIcon::Trigger)
@ -302,6 +365,7 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
}
}
#endif
void BitcoinGUI::optionsClicked()
{
@ -405,9 +469,10 @@ void BitcoinGUI::error(const QString &title, const QString &message)
void BitcoinGUI::changeEvent(QEvent *e)
{
#ifndef Q_WS_MAC // Ignored on Mac
if (e->type() == QEvent::WindowStateChange)
{
if(clientModel->getOptionsModel()->getMinimizeToTray())
if (clientModel->getOptionsModel()->getMinimizeToTray())
{
if (isMinimized())
{
@ -421,16 +486,19 @@ void BitcoinGUI::changeEvent(QEvent *e)
}
}
}
#endif
QMainWindow::changeEvent(e);
}
void BitcoinGUI::closeEvent(QCloseEvent *event)
{
#ifndef Q_WS_MAC // Ignored on Mac
if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
!clientModel->getOptionsModel()->getMinimizeOnClose())
{
qApp->quit();
}
#endif
QMainWindow::closeEvent(event);
}
@ -482,6 +550,7 @@ void BitcoinGUI::incomingTransaction(const QModelIndex & parent, int start, int
void BitcoinGUI::gotoOverviewPage()
{
show();
overviewAction->setChecked(true);
centralWidget->setCurrentWidget(overviewPage);
@ -491,6 +560,7 @@ void BitcoinGUI::gotoOverviewPage()
void BitcoinGUI::gotoHistoryPage()
{
show();
historyAction->setChecked(true);
centralWidget->setCurrentWidget(transactionsPage);
@ -501,6 +571,7 @@ void BitcoinGUI::gotoHistoryPage()
void BitcoinGUI::gotoAddressBookPage()
{
show();
addressBookAction->setChecked(true);
centralWidget->setCurrentWidget(addressBookPage);
@ -511,6 +582,7 @@ void BitcoinGUI::gotoAddressBookPage()
void BitcoinGUI::gotoReceiveCoinsPage()
{
show();
receiveCoinsAction->setChecked(true);
centralWidget->setCurrentWidget(receiveCoinsPage);
@ -521,6 +593,7 @@ void BitcoinGUI::gotoReceiveCoinsPage()
void BitcoinGUI::gotoSendCoinsPage()
{
show();
sendCoinsAction->setChecked(true);
centralWidget->setCurrentWidget(sendCoinsPage);

View File

@ -29,6 +29,8 @@ class BitcoinGUI : public QMainWindow
Q_OBJECT
public:
explicit BitcoinGUI(QWidget *parent = 0);
~BitcoinGUI();
void setClientModel(ClientModel *clientModel);
void setWalletModel(WalletModel *walletModel);
@ -64,6 +66,7 @@ private:
QLabel *progressBarLabel;
QProgressBar *progressBar;
QMenuBar *appMenuBar;
QAction *overviewAction;
QAction *historyAction;
QAction *quitAction;
@ -84,6 +87,8 @@ private:
QMovie *syncIconMovie;
void createActions();
void createMenuBar();
void createToolBars();
QWidget *createTabs();
void createTrayIcon();
@ -110,7 +115,9 @@ private slots:
// Misc actions
void optionsClicked();
void aboutClicked();
#ifndef Q_WS_MAC
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
#endif
void incomingTransaction(const QModelIndex & parent, int start, int end);
void encryptWallet(bool status);
void changePassphrase();

View File

@ -58,9 +58,6 @@
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="addButton">
<property name="toolTip">

View File

@ -83,7 +83,7 @@
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QHBoxLayout" name="payToLayout">
<property name="spacing">
<number>0</number>
</property>
@ -98,7 +98,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="addressBookButton">
<widget class="QToolButton" name="addressBookButton">
<property name="toolTip">
<string>Choose adress from address book</string>
</property>
@ -112,16 +112,10 @@
<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">
<widget class="QToolButton" name="pasteButton">
<property name="toolTip">
<string>Paste address from clipboard</string>
</property>
@ -135,13 +129,10 @@
<property name="shortcut">
<string>Alt+P</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<widget class="QToolButton" name="deleteButton">
<property name="toolTip">
<string>Remove this recipient</string>
</property>

View File

@ -351,7 +351,7 @@ Are you sure you wish to encrypt your wallet?</source>
</message>
<message>
<location filename="../bitcoingui.cpp" line="200"/>
<source>&amp;Exit</source>
<source>E&amp;xit</source>
<translation>Beenden</translation>
</message>
<message>

View File

@ -372,7 +372,7 @@ Are you sure you wish to encrypt your wallet?</source>
</message>
<message>
<location filename="../bitcoingui.cpp" line="200"/>
<source>&amp;Exit</source>
<source>E&amp;xit</source>
<translation>A&amp;fsluiten</translation>
</message>
<message>

View File

@ -343,7 +343,7 @@ Are you sure you wish to encrypt your wallet?</source>
</message>
<message>
<location filename="../bitcoingui.cpp" line="200"/>
<source>&amp;Exit</source>
<source>E&amp;xit</source>
<translation>Вы&amp;ход</translation>
</message>
<message>

View File

@ -0,0 +1,37 @@
#ifndef MACDOCKICONHANDLER_H
#define MACDOCKICONHANDLER_H
#include <QtCore/QObject>
class QMenu;
class QIcon;
class QWidget;
class objc_object;
class MacDockIconHandler : public QObject
{
Q_OBJECT
public:
~MacDockIconHandler();
QMenu *dockMenu();
void setIcon(const QIcon &icon);
static MacDockIconHandler *instance();
void handleDockIconClickEvent();
signals:
void dockIconClicked();
public slots:
private:
MacDockIconHandler();
objc_object *m_dockIconClickEventHandler;
QWidget *m_dummyWidget;
QMenu *m_dockMenu;
};
#endif // MACDOCKICONCLICKHANDLER_H

View File

@ -0,0 +1,99 @@
#include "macdockiconhandler.h"
#include <QtGui/QMenu>
#include <QtGui/QWidget>
extern void qt_mac_set_dock_menu(QMenu*);
#undef slots
#include <Cocoa/Cocoa.h>
@interface DockIconClickEventHandler : NSObject
{
MacDockIconHandler* dockIconHandler;
}
@end
@implementation DockIconClickEventHandler
- (id)initWithDockIconHandler:(MacDockIconHandler *)aDockIconHandler
{
self = [super init];
if (self) {
dockIconHandler = aDockIconHandler;
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:@selector(handleDockClickEvent:withReplyEvent:)
forEventClass:kCoreEventClass
andEventID:kAEReopenApplication];
}
return self;
}
- (void)handleDockClickEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
Q_UNUSED(event)
Q_UNUSED(replyEvent)
if (dockIconHandler)
dockIconHandler->handleDockIconClickEvent();
}
@end
MacDockIconHandler::MacDockIconHandler() : QObject()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
this->m_dockIconClickEventHandler = [[DockIconClickEventHandler alloc] initWithDockIconHandler:this];
this->m_dummyWidget = new QWidget();
this->m_dockMenu = new QMenu(this->m_dummyWidget);
qt_mac_set_dock_menu(this->m_dockMenu);
[pool release];
}
MacDockIconHandler::~MacDockIconHandler()
{
[this->m_dockIconClickEventHandler release];
delete this->m_dummyWidget;
}
QMenu *MacDockIconHandler::dockMenu()
{
return this->m_dockMenu;
}
void MacDockIconHandler::setIcon(const QIcon &icon)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSImage *image;
if (icon.isNull())
image = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
else {
QSize size = icon.actualSize(QSize(128, 128));
QPixmap pixmap = icon.pixmap(size);
CGImageRef cgImage = pixmap.toMacCGImageRef();
image = [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
CFRelease(cgImage);
}
[NSApp setApplicationIconImage:image];
[image release];
[pool release];
}
MacDockIconHandler *MacDockIconHandler::instance()
{
static MacDockIconHandler *s_instance = NULL;
if (!s_instance)
s_instance = new MacDockIconHandler();
return s_instance;
}
void MacDockIconHandler::handleDockIconClickEvent()
{
emit this->dockIconClicked();
}

View File

@ -8,12 +8,19 @@
#include <QByteArray>
#include <QSystemTrayIcon>
#include <QMessageBox>
#include <QTemporaryFile>
#include <QImageWriter>
#ifdef USE_DBUS
#include <QtDBus/QtDBus>
#include <stdint.h>
#endif
#ifdef Q_WS_MAC
#include <ApplicationServices/ApplicationServices.h>
extern bool qt_mac_execute_apple_script(const QString &script, AEDesc *ret);
#endif
// https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least 128
const int FREEDESKTOP_NOTIFICATION_ICON_SIZE = 128;
@ -39,6 +46,19 @@ Notificator::Notificator(const QString &programName, QSystemTrayIcon *trayicon,
mode = Freedesktop;
}
#endif
#ifdef Q_WS_MAC
// Check if Growl is installed (based on Qt's tray icon implementation)
CFURLRef cfurl;
OSStatus status = LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, CFSTR("growlTicket"), kLSRolesAll, 0, &cfurl);
if (status != kLSApplicationNotFoundErr) {
CFBundleRef bundle = CFBundleCreate(0, cfurl);
CFRelease(cfurl);
if (CFStringCompare(CFBundleGetIdentifier(bundle), CFSTR("com.Growl.GrowlHelperApp"), kCFCompareCaseInsensitive | kCFCompareBackwards) == kCFCompareEqualTo) {
mode = Growl;
}
CFRelease(bundle);
}
#endif
}
Notificator::~Notificator()
@ -201,6 +221,54 @@ void Notificator::notifySystray(Class cls, const QString &title, const QString &
trayIcon->showMessage(title, text, sicon, millisTimeout);
}
// Based on Qt's tray icon implementation
#ifdef Q_WS_MAC
void Notificator::notifyGrowl(Class cls, const QString &title, const QString &text, const QIcon &icon)
{
const QString script(
"tell application \"GrowlHelperApp\"\n"
" set the allNotificationsList to {\"Notification\"}\n" // -- Make a list of all the notification types (all)
" set the enabledNotificationsList to {\"Notification\"}\n" // -- Make a list of the notifications (enabled)
" register as application \"%1\" all notifications allNotificationsList default notifications enabledNotificationsList\n" // -- Register our script with Growl
" notify with name \"Notification\" title \"%2\" description \"%3\" application name \"%1\"%4\n" // -- Send a Notification
"end tell"
);
QString notificationApp(QApplication::applicationName());
if (notificationApp.isEmpty())
notificationApp = "Application";
QPixmap notificationIconPixmap;
if (icon.isNull()) { // If no icon specified, set icon based on class
QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion;
switch (cls)
{
case Information: sicon = QStyle::SP_MessageBoxInformation; break;
case Warning: sicon = QStyle::SP_MessageBoxWarning; break;
case Critical: sicon = QStyle::SP_MessageBoxCritical; break;
}
notificationIconPixmap = QApplication::style()->standardPixmap(sicon);
}
else {
QSize size = icon.actualSize(QSize(48, 48));
notificationIconPixmap = icon.pixmap(size);
}
QString notificationIcon;
QTemporaryFile notificationIconFile;
if (!notificationIconPixmap.isNull() && notificationIconFile.open()) {
QImageWriter writer(&notificationIconFile, "PNG");
if (writer.write(notificationIconPixmap.toImage()))
notificationIcon = QString(" image from location \"file://%1\"").arg(notificationIconFile.fileName());
}
QString quotedTitle(title), quotedText(text);
quotedTitle.replace("\\", "\\\\").replace("\"", "\\");
quotedText.replace("\\", "\\\\").replace("\"", "\\");
qt_mac_execute_apple_script(script.arg(notificationApp, quotedTitle, quotedText, notificationIcon), 0);
}
#endif
void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
{
switch(mode)
@ -213,6 +281,11 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c
case QSystemTray:
notifySystray(cls, title, text, icon, millisTimeout);
break;
#ifdef Q_WS_MAC
case Growl:
notifyGrowl(cls, title, text, icon);
break;
#endif
default:
if(cls == Critical)
{

View File

@ -48,6 +48,7 @@ private:
None,
Freedesktop, // Use DBus org.freedesktop.Notifications
QSystemTray, // Use QSystemTray::showMessage
Growl // Use the Growl notification system (Mac only)
};
QString programName;
Mode mode;
@ -58,6 +59,9 @@ private:
void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout);
#endif
void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout);
#ifdef Q_WS_MAC
void notifyGrowl(Class cls, const QString &title, const QString &text, const QIcon &icon);
#endif
};
#endif // NOTIFICATOR_H

View File

@ -30,9 +30,13 @@ public:
void setMapper(MonitoredDataMapper *mapper);
private:
QCheckBox *bitcoin_at_startup;
#ifndef Q_WS_MAC
QCheckBox *minimize_to_tray;
#endif
QCheckBox *map_port_upnp;
#ifndef Q_WS_MAC
QCheckBox *minimize_on_close;
#endif
QCheckBox *connect_socks4;
QLineEdit *proxy_ip;
QLineEdit *proxy_port;
@ -167,17 +171,21 @@ MainOptionsPage::MainOptionsPage(QWidget *parent):
bitcoin_at_startup->setToolTip(tr("Automatically start Bitcoin after the computer is turned on"));
layout->addWidget(bitcoin_at_startup);
#ifndef Q_WS_MAC
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);
#endif
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);
#ifndef Q_WS_MAC
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);
#endif
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)"));
@ -239,9 +247,13 @@ void MainOptionsPage::setMapper(MonitoredDataMapper *mapper)
{
// Map model to widgets
mapper->addMapping(bitcoin_at_startup, OptionsModel::StartAtStartup);
#ifndef Q_WS_MAC
mapper->addMapping(minimize_to_tray, OptionsModel::MinimizeToTray);
#endif
mapper->addMapping(map_port_upnp, OptionsModel::MapPortUPnP);
#ifndef Q_WS_MAC
mapper->addMapping(minimize_on_close, OptionsModel::MinimizeOnClose);
#endif
mapper->addMapping(connect_socks4, OptionsModel::ConnectSOCKS4);
mapper->addMapping(proxy_ip, OptionsModel::ProxyIP);
mapper->addMapping(proxy_port, OptionsModel::ProxyPort);

View File

@ -116,6 +116,7 @@ OverviewPage::OverviewPage(QWidget *parent) :
ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
ui->listTransactions->setSelectionMode(QAbstractItemView::NoSelection);
ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
ui->listTransactions->setAttribute(Qt::WA_MacShowFocusRect, false);
connect(ui->listTransactions, SIGNAL(clicked(QModelIndex)), this, SIGNAL(transactionClicked(QModelIndex)));
}

View File

@ -19,6 +19,12 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
{
ui->setupUi(this);
#ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac
ui->addButton->setIcon(QIcon());
ui->clearButton->setIcon(QIcon());
ui->sendButton->setIcon(QIcon());
#endif
addEntry();
connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));

View File

@ -17,6 +17,10 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
{
ui->setupUi(this);
#ifdef Q_WS_MAC
ui->payToLayout->setSpacing(4);
#endif
#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"));

View File

@ -38,13 +38,20 @@ TransactionView::TransactionView(QWidget *parent) :
QHBoxLayout *hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0,0,0,0);
#ifdef Q_WS_MAC
hlayout->setSpacing(5);
hlayout->addSpacing(26);
#else
hlayout->setSpacing(0);
hlayout->addSpacing(23);
#endif
dateWidget = new QComboBox(this);
dateWidget->setMaximumWidth(120);
dateWidget->setMinimumWidth(120);
#ifdef Q_WS_MAC
dateWidget->setFixedWidth(121);
#else
dateWidget->setFixedWidth(120);
#endif
dateWidget->addItem(tr("All"), All);
dateWidget->addItem(tr("Today"), Today);
dateWidget->addItem(tr("This week"), ThisWeek);
@ -55,8 +62,11 @@ TransactionView::TransactionView(QWidget *parent) :
hlayout->addWidget(dateWidget);
typeWidget = new QComboBox(this);
typeWidget->setMaximumWidth(120);
typeWidget->setMinimumWidth(120);
#ifdef Q_WS_MAC
typeWidget->setFixedWidth(121);
#else
typeWidget->setFixedWidth(120);
#endif
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
@ -79,8 +89,11 @@ TransactionView::TransactionView(QWidget *parent) :
#if QT_VERSION >= 0x040700
amountWidget->setPlaceholderText(tr("Min amount"));
#endif
amountWidget->setMaximumWidth(100);
amountWidget->setMinimumWidth(100);
#ifdef Q_WS_MAC
amountWidget->setFixedWidth(97);
#else
amountWidget->setFixedWidth(100);
#endif
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
hlayout->addWidget(amountWidget);
@ -96,7 +109,11 @@ TransactionView::TransactionView(QWidget *parent) :
vlayout->setSpacing(0);
int width = view->verticalScrollBar()->sizeHint().width();
// Cover scroll bar width with spacing
#ifdef Q_WS_MAC
hlayout->addSpacing(width+2);
#else
hlayout->addSpacing(width);
#endif
// Always show scroll bar
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->setTabKeyNavigation(false);