diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 8a735a7cc..2e4000e52 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -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" diff --git a/doc/readme-qt.rst b/doc/readme-qt.rst index b12aa3090..b7f0d2c1f 100644 --- a/doc/readme-qt.rst +++ b/doc/readme-qt.rst @@ -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: diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index ee64cc2c8..6be59a082 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -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: diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 60a907420..c8e332419 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -16,6 +16,7 @@ #include #include #include +#include // 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(); diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index f1edc62bb..19cd5655c 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -1,33 +1,30 @@ #include "bitcoinamountfield.h" -#include "qvalidatedlineedit.h" #include "qvaluecombobox.h" #include "bitcoinunits.h" +#include "guiconstants.h" + #include #include #include #include #include +#include #include +#include +#include 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("."))); - 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(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) { diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index cc92159fe..8457a418c 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -4,7 +4,7 @@ #include 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; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index be10b97c0..b9995fdd6 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -22,6 +22,10 @@ #include "askpassphrasedialog.h" #include "notificator.h" +#ifdef Q_WS_MAC +#include "macdockiconhandler.h" +#endif + #include #include #include @@ -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); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 59661350c..a91219219 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -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(); diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index f9dd02fef..e5e19e101 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -58,9 +58,6 @@ - - 6 - diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 13593c2c1..0297d17f1 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -83,7 +83,7 @@ - + 0 @@ -98,7 +98,7 @@ - + Choose adress from address book @@ -112,16 +112,10 @@ Alt+A - - false - - - false - - + Paste address from clipboard @@ -135,13 +129,10 @@ Alt+P - - false - - + Remove this recipient diff --git a/src/qt/locale/bitcoin_de.ts b/src/qt/locale/bitcoin_de.ts index ba2e25d62..c674eb439 100644 --- a/src/qt/locale/bitcoin_de.ts +++ b/src/qt/locale/bitcoin_de.ts @@ -351,7 +351,7 @@ Are you sure you wish to encrypt your wallet? - &Exit + E&xit Beenden diff --git a/src/qt/locale/bitcoin_nl.ts b/src/qt/locale/bitcoin_nl.ts index 9940e90e1..a7ea7e666 100644 --- a/src/qt/locale/bitcoin_nl.ts +++ b/src/qt/locale/bitcoin_nl.ts @@ -372,7 +372,7 @@ Are you sure you wish to encrypt your wallet? - &Exit + E&xit A&fsluiten diff --git a/src/qt/locale/bitcoin_ru.ts b/src/qt/locale/bitcoin_ru.ts index ea32d9560..b6127959f 100644 --- a/src/qt/locale/bitcoin_ru.ts +++ b/src/qt/locale/bitcoin_ru.ts @@ -343,7 +343,7 @@ Are you sure you wish to encrypt your wallet? - &Exit + E&xit Вы&ход diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h new file mode 100644 index 000000000..cc78c7a78 --- /dev/null +++ b/src/qt/macdockiconhandler.h @@ -0,0 +1,37 @@ +#ifndef MACDOCKICONHANDLER_H +#define MACDOCKICONHANDLER_H + +#include + +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 diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm new file mode 100644 index 000000000..df56e6949 --- /dev/null +++ b/src/qt/macdockiconhandler.mm @@ -0,0 +1,99 @@ + +#include "macdockiconhandler.h" + +#include +#include + +extern void qt_mac_set_dock_menu(QMenu*); + +#undef slots +#include + +@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(); +} diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index cf0c0a390..a2314caa4 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -8,12 +8,19 @@ #include #include #include +#include +#include #ifdef USE_DBUS #include #include #endif +#ifdef Q_WS_MAC +#include +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(¬ificationIconFile, "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) { diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 4217f7e06..ed69ae5c6 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -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 diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 7267e3d10..ea3164e3d 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -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); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index f84a79fe3..6dedde027 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -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))); } diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 58eb5c21f..719cc5188 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -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())); diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index fccef232b..1802095b3 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -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")); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index b2777b7b2..92dda5784 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -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);