bitcoingui.cpp 36 KB
Newer Older
1
// Copyright (c) 2011-2014 The Bitcoin developers
2
3
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
5
#include "bitcoingui.h"
6

7
#include "bitcoinunits.h"
8
#include "clientmodel.h"
9
#include "guiconstants.h"
10
#include "guiutil.h"
11
#include "notificator.h"
12
#include "openuridialog.h"
13
14
#include "optionsdialog.h"
#include "optionsmodel.h"
15
#include "rpcconsole.h"
16
#include "utilitydialog.h"
17
#ifdef ENABLE_WALLET
18
19
#include "walletframe.h"
#include "walletmodel.h"
20
#endif
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
21

Philip Kaufmann's avatar
Philip Kaufmann committed
22
#ifdef Q_OS_MAC
23
24
25
#include "macdockiconhandler.h"
#endif

26
#include "init.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
27
#include "util.h"
28
29
30
31
#include "ui_interface.h"

#include <iostream>

32
#include <QAction>
33
#include <QApplication>
34
35
36
#include <QDateTime>
#include <QDesktopWidget>
#include <QDragEnterEvent>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
37
#include <QIcon>
38
39
#include <QListWidget>
#include <QMenuBar>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
40
#include <QMessageBox>
41
#include <QMimeData>
42
#include <QProgressBar>
Cozz Lovan's avatar
Cozz Lovan committed
43
#include <QProgressDialog>
44
#include <QSettings>
45
#include <QStackedWidget>
46
47
#include <QStatusBar>
#include <QStyle>
48
#include <QTimer>
49
50
51
#include <QToolBar>
#include <QVBoxLayout>

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
52
#if QT_VERSION < 0x050000
53
#include <QUrl>
54
#include <QTextDocument>
55
56
#else
#include <QUrlQuery>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
57
#endif
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
58

59
60
const QString BitcoinGUI::DEFAULT_WALLET = "~Default";

61
BitcoinGUI::BitcoinGUI(bool fIsTestnet, QWidget *parent) :
62
63
    QMainWindow(parent),
    clientModel(0),
64
    walletFrame(0),
65
66
    encryptWalletAction(0),
    changePassphraseAction(0),
67
    aboutQtAction(0),
68
    trayIcon(0),
69
    notificator(0),
70
    rpcConsole(0),
71
72
    prevBlocks(0),
    spinnerFrame(0)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
73
{
74
    GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 550), this);
75

76
77
78
79
80
81
82
83
84
85
86
87
88
89
    QString windowTitle = tr("Bitcoin Core") + " - ";
#ifdef ENABLE_WALLET
    /* if compiled with wallet support, -disablewallet can still disable the wallet */
    bool enableWallet = !GetBoolArg("-disablewallet", false);
#else
    bool enableWallet = false;
#endif
    if(enableWallet)
    {
        windowTitle += tr("Wallet");
    } else {
        windowTitle += tr("Node");
    }

90
91
    if (!fIsTestnet)
    {
92
#ifndef Q_OS_MAC
93
94
        QApplication::setWindowIcon(QIcon(":icons/bitcoin"));
        setWindowIcon(QIcon(":icons/bitcoin"));
95
96
97
#else
        MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin"));
#endif
98
99
100
    }
    else
    {
101
        windowTitle += " " + tr("[testnet]");
102
#ifndef Q_OS_MAC
103
104
        QApplication::setWindowIcon(QIcon(":icons/bitcoin_testnet"));
        setWindowIcon(QIcon(":icons/bitcoin_testnet"));
105
#else
106
        MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet"));
107
#endif
108
    }
109
    setWindowTitle(windowTitle);
110
111
112
113
114
115

#if defined(Q_OS_MAC) && QT_VERSION < 0x050000
    // This property is not implemented in Qt 5. Setting it has no effect.
    // A replacement API (QtMacUnifiedToolBar) is available in QtMacExtras.
    setUnifiedTitleAndToolBarOnMac(true);
#endif
116

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    rpcConsole = new RPCConsole(enableWallet ? this : 0);
#ifdef ENABLE_WALLET
    if(enableWallet)
    {
        /** Create wallet frame and make it the central widget */
        walletFrame = new WalletFrame(this);
        setCentralWidget(walletFrame);
    } else
#endif
    {
        /* When compiled without wallet or -disablewallet is provided,
         * the central widget is the rpc console.
         */
        setCentralWidget(rpcConsole);
    }
132

133
134
    // Accept D&D of URIs
    setAcceptDrops(true);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
135

136
    // Create actions for the toolbar, menu bar and tray/dock icon
137
    // Needs walletFrame to be initialized
138
    createActions(fIsTestnet);
139

140
141
    // Create application menu bar
    createMenuBar();
142

143
144
    // Create the toolbars
    createToolBars();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
145

146
    // Create system tray icon and notification
147
    createTrayIcon(fIsTestnet);
148

149
    // Create status bar
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
150
    statusBar();
151

152
    // Status bar notification icons
153
    QFrame *frameBlocks = new QFrame();
154
    frameBlocks->setContentsMargins(0,0,0,0);
155
    frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
156
157
158
    QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
    frameBlocksLayout->setContentsMargins(3,0,3,0);
    frameBlocksLayout->setSpacing(3);
159
    unitDisplayControl = new UnitDisplayStatusBarControl();
160
    labelEncryptionIcon = new QLabel();
161
    labelConnectionsIcon = new QLabel();
162
    labelBlocksIcon = new QLabel();
163
164
165
166
167
168
169
    if(enableWallet)
    {
        frameBlocksLayout->addStretch();
        frameBlocksLayout->addWidget(unitDisplayControl);
        frameBlocksLayout->addStretch();
        frameBlocksLayout->addWidget(labelEncryptionIcon);
    }
170
    frameBlocksLayout->addStretch();
171
172
    frameBlocksLayout->addWidget(labelConnectionsIcon);
    frameBlocksLayout->addStretch();
173
174
    frameBlocksLayout->addWidget(labelBlocksIcon);
    frameBlocksLayout->addStretch();
175

176
177
    // Progress bar and label for blocks download
    progressBarLabel = new QLabel();
178
179
    progressBarLabel->setVisible(false);
    progressBar = new QProgressBar();
180
    progressBar->setAlignment(Qt::AlignCenter);
181
182
    progressBar->setVisible(false);

183
184
185
    // Override style sheet for progress bar for styles that have a segmented progress bar,
    // as they make the text unreadable (workaround for issue #1071)
    // See https://qt-project.org/doc/qt-4.8/gallery.html
186
    QString curStyle = QApplication::style()->metaObject()->className();
187
188
189
190
191
    if(curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle")
    {
        progressBar->setStyleSheet("QProgressBar { background-color: #e8e8e8; border: 1px solid grey; border-radius: 7px; padding: 1px; text-align: center; } QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: 7px; margin: 0px; }");
    }

192
193
    statusBar()->addWidget(progressBarLabel);
    statusBar()->addWidget(progressBar);
194
    statusBar()->addPermanentWidget(frameBlocks);
195

196
    connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show()));
197

Mathy Vanvoorden's avatar
Mathy Vanvoorden committed
198
    // prevents an open debug window from becoming stuck/unusable on client shutdown
199
    connect(quitAction, SIGNAL(triggered()), rpcConsole, SLOT(hide()));
200

201
202
    // Install event filter to be able to catch status tip events (QEvent::StatusTip)
    this->installEventFilter(this);
203
204
205

    // Initially wallet actions should be disabled
    setWalletActionsEnabled(false);
206
207
208

    // Subscribe to notifications from core
    subscribeToCoreSignals();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
209
210
}

211
212
BitcoinGUI::~BitcoinGUI()
{
213
214
215
    // Unsubscribe from notifications from core
    unsubscribeFromCoreSignals();

216
    GUIUtil::saveWindowGeometry("nWindow", this);
217
218
    if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
        trayIcon->hide();
Philip Kaufmann's avatar
Philip Kaufmann committed
219
#ifdef Q_OS_MAC
220
    delete appMenuBar;
221
    MacDockIconHandler::instance()->setMainWindow(NULL);
222
223
224
#endif
}

225
void BitcoinGUI::createActions(bool fIsTestnet)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
226
{
227
    QActionGroup *tabGroup = new QActionGroup(this);
228

229
    overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
230
231
    overviewAction->setStatusTip(tr("Show general overview of wallet"));
    overviewAction->setToolTip(overviewAction->statusTip());
232
    overviewAction->setCheckable(true);
233
    overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
234
    tabGroup->addAction(overviewAction);
235

236
    sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send"), this);
237
238
    sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
    sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
Philip Kaufmann's avatar
Philip Kaufmann committed
239
240
241
242
    sendCoinsAction->setCheckable(true);
    sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
    tabGroup->addAction(sendCoinsAction);

243
    receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive"), this);
244
    receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)"));
245
    receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
Philip Kaufmann's avatar
Philip Kaufmann committed
246
247
248
249
    receiveCoinsAction->setCheckable(true);
    receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
    tabGroup->addAction(receiveCoinsAction);

250
    historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
251
252
    historyAction->setStatusTip(tr("Browse transaction history"));
    historyAction->setToolTip(historyAction->statusTip());
253
    historyAction->setCheckable(true);
254
    historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
255
256
    tabGroup->addAction(historyAction);

257
258
    // These showNormalIfMinimized are needed because Send Coins and Receive Coins
    // can be triggered from the tray menu, and need to show the GUI to be useful.
259
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
260
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
Philip Kaufmann's avatar
Philip Kaufmann committed
261
262
263
264
    connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
    connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
    connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
    connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
265
    connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
266
    connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
267

268
    quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
269
    quitAction->setStatusTip(tr("Quit application"));
270
    quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
271
    quitAction->setMenuRole(QAction::QuitRole);
272
    if (!fIsTestnet)
273
        aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About Bitcoin Core"), this);
274
    else
275
        aboutAction = new QAction(QIcon(":/icons/bitcoin_testnet"), tr("&About Bitcoin Core"), this);
276
    aboutAction->setStatusTip(tr("Show information about Bitcoin Core"));
277
    aboutAction->setMenuRole(QAction::AboutRole);
278
#if QT_VERSION < 0x050000
279
    aboutQtAction = new QAction(QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this);
280
281
#else
    aboutQtAction = new QAction(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this);
282
#endif
283
    aboutQtAction->setStatusTip(tr("Show information about Qt"));
284
    aboutQtAction->setMenuRole(QAction::AboutQtRole);
285
    optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
286
    optionsAction->setStatusTip(tr("Modify configuration options for Bitcoin"));
287
    optionsAction->setMenuRole(QAction::PreferencesRole);
288
289
290
291
    if (!fIsTestnet)
        toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("&Show / Hide"), this);
    else
        toggleHideAction = new QAction(QIcon(":/icons/bitcoin_testnet"), tr("&Show / Hide"), this);
292
    toggleHideAction->setStatusTip(tr("Show or hide the main Window"));
293

294
    encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this);
295
    encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet"));
296
    encryptWalletAction->setCheckable(true);
297
    backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this);
298
    backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
299
    changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this);
300
    changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
Philip Kaufmann's avatar
Philip Kaufmann committed
301
    signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this);
302
    signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
Philip Kaufmann's avatar
Philip Kaufmann committed
303
    verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this);
304
    verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
Philip Kaufmann's avatar
Philip Kaufmann committed
305

306
    openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this);
307
    openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console"));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
308

309
    usedSendingAddressesAction = new QAction(QIcon(":/icons/address-book"), tr("&Sending addresses..."), this);
310
    usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels"));
311
    usedReceivingAddressesAction = new QAction(QIcon(":/icons/address-book"), tr("&Receiving addresses..."), this);
312
    usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels"));
313

314
    openAction = new QAction(QApplication::style()->standardIcon(QStyle::SP_FileIcon), tr("Open &URI..."), this);
315
316
    openAction->setStatusTip(tr("Open a bitcoin: URI or payment request"));

317
318
319
    showHelpMessageAction = new QAction(QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation), tr("&Command-line options"), this);
    showHelpMessageAction->setStatusTip(tr("Show the Bitcoin Core help message to get a list with possible Bitcoin command-line options"));

320
321
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
    connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
322
    connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
Philip Kaufmann's avatar
Philip Kaufmann committed
323
    connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
324
    connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden()));
325
    connect(showHelpMessageAction, SIGNAL(triggered()), this, SLOT(showHelpMessageClicked()));
326
327
328
329
330
331
332
333
334
335
336
337
338
#ifdef ENABLE_WALLET
    if(walletFrame)
    {
        connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(encryptWallet(bool)));
        connect(backupWalletAction, SIGNAL(triggered()), walletFrame, SLOT(backupWallet()));
        connect(changePassphraseAction, SIGNAL(triggered()), walletFrame, SLOT(changePassphrase()));
        connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab()));
        connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab()));
        connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedSendingAddresses()));
        connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses()));
        connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked()));
    }
#endif
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
339
340
}

341
342
void BitcoinGUI::createMenuBar()
{
Philip Kaufmann's avatar
Philip Kaufmann committed
343
#ifdef Q_OS_MAC
344
345
346
347
348
349
350
351
352
    // 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"));
353
354
355
356
357
358
359
360
361
362
363
    if(walletFrame)
    {
        file->addAction(openAction);
        file->addAction(backupWalletAction);
        file->addAction(signMessageAction);
        file->addAction(verifyMessageAction);
        file->addSeparator();
        file->addAction(usedSendingAddressesAction);
        file->addAction(usedReceivingAddressesAction);
        file->addSeparator();
    }
364
365
366
    file->addAction(quitAction);

    QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
367
368
369
370
371
372
    if(walletFrame)
    {
        settings->addAction(encryptWalletAction);
        settings->addAction(changePassphraseAction);
        settings->addSeparator();
    }
373
374
375
    settings->addAction(optionsAction);

    QMenu *help = appMenuBar->addMenu(tr("&Help"));
376
377
378
379
    if(walletFrame)
    {
        help->addAction(openRPCConsoleAction);
    }
380
381
    help->addAction(showHelpMessageAction);
    help->addSeparator();
382
    help->addAction(aboutAction);
383
    help->addAction(aboutQtAction);
384
385
386
387
}

void BitcoinGUI::createToolBars()
{
388
389
390
391
392
393
394
395
396
397
    if(walletFrame)
    {
        QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
        toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
        toolbar->addAction(overviewAction);
        toolbar->addAction(sendCoinsAction);
        toolbar->addAction(receiveCoinsAction);
        toolbar->addAction(historyAction);
        overviewAction->setChecked(true);
    }
398
399
}

400
void BitcoinGUI::setClientModel(ClientModel *clientModel)
401
{
402
    this->clientModel = clientModel;
403
    if(clientModel)
404
    {
405
406
        // Create system tray menu (or setup the dock menu) that late to prevent users from calling actions,
        // while the client has not yet fully loaded
407
        createTrayIconMenu();
408

409
410
411
        // Keep up to date with client
        setNumConnections(clientModel->getNumConnections());
        connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
412

413
414
        setNumBlocks(clientModel->getNumBlocks());
        connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int)));
415

416
        // Receive and report messages from client model
417
        connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int)));
418

Cozz Lovan's avatar
Cozz Lovan committed
419
420
421
        // Show progress dialog
        connect(clientModel, SIGNAL(showProgress(QString,int)), this, SLOT(showProgress(QString,int)));

Philip Kaufmann's avatar
Philip Kaufmann committed
422
        rpcConsole->setClientModel(clientModel);
423
424
425
426
427
428
#ifdef ENABLE_WALLET
        if(walletFrame)
        {
            walletFrame->setClientModel(clientModel);
        }
#endif
429
430

        this->unitDisplayControl->setOptionsModel(clientModel->getOptionsModel());
431
    }
432
433
}

434
#ifdef ENABLE_WALLET
435
bool BitcoinGUI::addWallet(const QString& name, WalletModel *walletModel)
436
{
437
438
    if(!walletFrame)
        return false;
439
    setWalletActionsEnabled(true);
440
441
442
443
444
    return walletFrame->addWallet(name, walletModel);
}

bool BitcoinGUI::setCurrentWallet(const QString& name)
{
445
446
    if(!walletFrame)
        return false;
447
448
449
450
451
    return walletFrame->setCurrentWallet(name);
}

void BitcoinGUI::removeAllWallets()
{
452
453
    if(!walletFrame)
        return;
454
    setWalletActionsEnabled(false);
455
    walletFrame->removeAllWallets();
456
}
457
#endif
458

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
void BitcoinGUI::setWalletActionsEnabled(bool enabled)
{
    overviewAction->setEnabled(enabled);
    sendCoinsAction->setEnabled(enabled);
    receiveCoinsAction->setEnabled(enabled);
    historyAction->setEnabled(enabled);
    encryptWalletAction->setEnabled(enabled);
    backupWalletAction->setEnabled(enabled);
    changePassphraseAction->setEnabled(enabled);
    signMessageAction->setEnabled(enabled);
    verifyMessageAction->setEnabled(enabled);
    usedSendingAddressesAction->setEnabled(enabled);
    usedReceivingAddressesAction->setEnabled(enabled);
    openAction->setEnabled(enabled);
}

475
void BitcoinGUI::createTrayIcon(bool fIsTestnet)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
476
{
Philip Kaufmann's avatar
Philip Kaufmann committed
477
#ifndef Q_OS_MAC
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
478
    trayIcon = new QSystemTrayIcon(this);
479

480
481
    if (!fIsTestnet)
    {
482
        trayIcon->setToolTip(tr("Bitcoin Core client"));
ntrgn's avatar
ntrgn committed
483
        trayIcon->setIcon(QIcon(":/icons/bitcoin"));
484
485
486
    }
    else
    {
487
        trayIcon->setToolTip(tr("Bitcoin Core client") + " " + tr("[testnet]"));
ntrgn's avatar
ntrgn committed
488
        trayIcon->setIcon(QIcon(":/icons/bitcoin_testnet"));
489
490
    }

491
492
493
    trayIcon->show();
#endif

494
    notificator = new Notificator(QApplication::applicationName(), trayIcon, this);
495
496
497
498
499
500
}

void BitcoinGUI::createTrayIconMenu()
{
    QMenu *trayIconMenu;
#ifndef Q_OS_MAC
501
502
503
504
    // return if trayIcon is unset (only on non-Mac OSes)
    if (!trayIcon)
        return;

505
506
507
    trayIconMenu = new QMenu(this);
    trayIcon->setContextMenu(trayIconMenu);

508
509
    connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
510
511
512
#else
    // Note: On Mac, the dock icon is used to provide the tray's functionality.
    MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
513
    dockIconHandler->setMainWindow((QMainWindow *)this);
514
515
516
517
    trayIconMenu = dockIconHandler->dockMenu();
#endif

    // Configuration of the tray icon (or dock icon) icon menu
518
    trayIconMenu->addAction(toggleHideAction);
519
    trayIconMenu->addSeparator();
520
521
    trayIconMenu->addAction(sendCoinsAction);
    trayIconMenu->addAction(receiveCoinsAction);
522
    trayIconMenu->addSeparator();
523
    trayIconMenu->addAction(signMessageAction);
524
    trayIconMenu->addAction(verifyMessageAction);
525
526
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(optionsAction);
527
    trayIconMenu->addAction(openRPCConsoleAction);
Philip Kaufmann's avatar
Philip Kaufmann committed
528
#ifndef Q_OS_MAC // This is built-in on Mac
529
530
531
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(quitAction);
#endif
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
532
533
}

Philip Kaufmann's avatar
Philip Kaufmann committed
534
#ifndef Q_OS_MAC
535
536
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
537
    if(reason == QSystemTrayIcon::Trigger)
538
    {
539
        // Click on system tray icon triggers show/hide of the main window
540
        toggleHideAction->trigger();
541
542
    }
}
543
#endif
544

545
546
void BitcoinGUI::optionsClicked()
{
547
548
    if(!clientModel || !clientModel->getOptionsModel())
        return;
549
550

    OptionsDialog dlg(this);
551
    dlg.setModel(clientModel->getOptionsModel());
552
    dlg.exec();
553
554
}

555
void BitcoinGUI::aboutClicked()
556
{
557
558
559
    if(!clientModel)
        return;

560
    HelpMessageDialog dlg(this, true);
561
    dlg.exec();
562
563
}

564
565
void BitcoinGUI::showHelpMessageClicked()
{
566
    HelpMessageDialog *help = new HelpMessageDialog(this, false);
567
568
569
570
    help->setAttribute(Qt::WA_DeleteOnClose);
    help->show();
}

571
#ifdef ENABLE_WALLET
572
573
void BitcoinGUI::openClicked()
{
574
    OpenURIDialog dlg(this);
575
576
577
578
579
580
    if(dlg.exec())
    {
        emit receivedURI(dlg.getURI());
    }
}

581
582
void BitcoinGUI::gotoOverviewPage()
{
583
    overviewAction->setChecked(true);
584
585
586
587
588
    if (walletFrame) walletFrame->gotoOverviewPage();
}

void BitcoinGUI::gotoHistoryPage()
{
589
    historyAction->setChecked(true);
590
591
592
593
594
    if (walletFrame) walletFrame->gotoHistoryPage();
}

void BitcoinGUI::gotoReceiveCoinsPage()
{
595
    receiveCoinsAction->setChecked(true);
596
597
598
    if (walletFrame) walletFrame->gotoReceiveCoinsPage();
}

599
void BitcoinGUI::gotoSendCoinsPage(QString addr)
600
{
601
    sendCoinsAction->setChecked(true);
602
    if (walletFrame) walletFrame->gotoSendCoinsPage(addr);
603
604
605
606
607
608
609
610
611
}

void BitcoinGUI::gotoSignMessageTab(QString addr)
{
    if (walletFrame) walletFrame->gotoSignMessageTab(addr);
}

void BitcoinGUI::gotoVerifyMessageTab(QString addr)
{
612
    if (walletFrame) walletFrame->gotoVerifyMessageTab(addr);
613
}
614
#endif
615

616
617
void BitcoinGUI::setNumConnections(int count)
{
618
619
620
    QString icon;
    switch(count)
    {
621
622
623
624
625
    case 0: icon = ":/icons/connect_0"; break;
    case 1: case 2: case 3: icon = ":/icons/connect_1"; break;
    case 4: case 5: case 6: icon = ":/icons/connect_2"; break;
    case 7: case 8: case 9: icon = ":/icons/connect_3"; break;
    default: icon = ":/icons/connect_4"; break;
626
    }
627
    labelConnectionsIcon->setPixmap(QIcon(icon).pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
628
    labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count));
629
630
}

631
void BitcoinGUI::setNumBlocks(int count)
632
{
633
634
635
    // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait until chain-sync starts -> garbelled text)
    statusBar()->clearMessage();

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
    // Acquire current block source
    enum BlockSource blockSource = clientModel->getBlockSource();
    switch (blockSource) {
        case BLOCK_SOURCE_NETWORK:
            progressBarLabel->setText(tr("Synchronizing with network..."));
            break;
        case BLOCK_SOURCE_DISK:
            progressBarLabel->setText(tr("Importing blocks from disk..."));
            break;
        case BLOCK_SOURCE_REINDEX:
            progressBarLabel->setText(tr("Reindexing blocks on disk..."));
            break;
        case BLOCK_SOURCE_NONE:
            // Case: not Importing, not Reindexing and no network connection
            progressBarLabel->setText(tr("No block source available..."));
            break;
652
653
    }

654
655
    QString tooltip;

656
    QDateTime lastBlockDate = clientModel->getLastBlockDate();
657
658
    QDateTime currentDate = QDateTime::currentDateTime();
    int secs = lastBlockDate.secsTo(currentDate);
659

660
    tooltip = tr("Processed %1 blocks of transaction history.").arg(count);
661

662
    // Set icon state: spinning if catching up, tick otherwise
663
    if(secs < 90*60)
664
    {
665
        tooltip = tr("Up to date") + QString(".<br>") + tooltip;
666
        labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
667

668
669
670
671
#ifdef ENABLE_WALLET
        if(walletFrame)
            walletFrame->showOutOfSyncWarning(false);
#endif
672
673
674

        progressBarLabel->setVisible(false);
        progressBar->setVisible(false);
675
    }
676
677
    else
    {
678
679
        // Represent time from last generated block in human readable text
        QString timeBehindText;
680
681
682
683
684
        const int HOUR_IN_SECONDS = 60*60;
        const int DAY_IN_SECONDS = 24*60*60;
        const int WEEK_IN_SECONDS = 7*24*60*60;
        const int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
        if(secs < 2*DAY_IN_SECONDS)
685
        {
686
            timeBehindText = tr("%n hour(s)","",secs/HOUR_IN_SECONDS);
687
        }
688
        else if(secs < 2*WEEK_IN_SECONDS)
689
        {
690
691
692
693
694
            timeBehindText = tr("%n day(s)","",secs/DAY_IN_SECONDS);
        }
        else if(secs < YEAR_IN_SECONDS)
        {
            timeBehindText = tr("%n week(s)","",secs/WEEK_IN_SECONDS);
695
696
697
        }
        else
        {
698
699
700
            int years = secs / YEAR_IN_SECONDS;
            int remainder = secs % YEAR_IN_SECONDS;
            timeBehindText = tr("%1 and %2").arg(tr("%n year(s)", "", years)).arg(tr("%n week(s)","", remainder/WEEK_IN_SECONDS));
701
702
703
704
        }

        progressBarLabel->setVisible(true);
        progressBar->setFormat(tr("%1 behind").arg(timeBehindText));
705
706
        progressBar->setMaximum(1000000000);
        progressBar->setValue(clientModel->getVerificationProgress() * 1000000000.0 + 0.5);
707
708
        progressBar->setVisible(true);

709
        tooltip = tr("Catching up...") + QString("<br>") + tooltip;
710
        if(count != prevBlocks)
711
712
713
714
715
716
        {
            labelBlocksIcon->setPixmap(QIcon(QString(
                ":/movies/spinner-%1").arg(spinnerFrame, 3, 10, QChar('0')))
                .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
            spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES;
        }
717
        prevBlocks = count;
718

719
720
721
722
#ifdef ENABLE_WALLET
        if(walletFrame)
            walletFrame->showOutOfSyncWarning(true);
#endif
723

724
        tooltip += QString("<br>");
725
726
727
        tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText);
        tooltip += QString("<br>");
        tooltip += tr("Transactions after this will not yet be visible.");
728
    }
729

730
731
732
    // Don't word-wrap this (fixed-width) tooltip
    tooltip = QString("<nobr>") + tooltip + QString("</nobr>");

733
    labelBlocksIcon->setToolTip(tooltip);
734
735
    progressBarLabel->setToolTip(tooltip);
    progressBar->setToolTip(tooltip);
736
737
}

738
void BitcoinGUI::message(const QString &title, const QString &message, unsigned int style, bool *ret)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
739
{
740
    QString strTitle = tr("Bitcoin"); // default title
741
742
743
744
    // Default to information icon
    int nMBoxIcon = QMessageBox::Information;
    int nNotifyIcon = Notificator::Information;

745
    QString msgType;
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764

    // Prefer supplied title over style based title
    if (!title.isEmpty()) {
        msgType = title;
    }
    else {
        switch (style) {
        case CClientUIInterface::MSG_ERROR:
            msgType = tr("Error");
            break;
        case CClientUIInterface::MSG_WARNING:
            msgType = tr("Warning");
            break;
        case CClientUIInterface::MSG_INFORMATION:
            msgType = tr("Information");
            break;
        default:
            break;
        }
765
    }
766
    // Append title to "Bitcoin - "
767
768
    if (!msgType.isEmpty())
        strTitle += " - " + msgType;
769
770
771
772
773

    // Check for error/warning icon
    if (style & CClientUIInterface::ICON_ERROR) {
        nMBoxIcon = QMessageBox::Critical;
        nNotifyIcon = Notificator::Critical;
774
    }
775
776
777
778
779
780
    else if (style & CClientUIInterface::ICON_WARNING) {
        nMBoxIcon = QMessageBox::Warning;
        nNotifyIcon = Notificator::Warning;
    }

    // Display message
781
    if (style & CClientUIInterface::MODAL) {
782
783
784
785
786
        // Check for buttons, use OK as default, if none was supplied
        QMessageBox::StandardButton buttons;
        if (!(buttons = (QMessageBox::StandardButton)(style & CClientUIInterface::BTN_MASK)))
            buttons = QMessageBox::Ok;

787
        showNormalIfMinimized();
788
        QMessageBox mBox((QMessageBox::Icon)nMBoxIcon, strTitle, message, buttons, this);
789
790
791
        int r = mBox.exec();
        if (ret != NULL)
            *ret = r == QMessageBox::Ok;
792
793
794
    }
    else
        notificator->notify((Notificator::Class)nNotifyIcon, strTitle, message);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
795
}
796
797
798

void BitcoinGUI::changeEvent(QEvent *e)
{
799
    QMainWindow::changeEvent(e);
Philip Kaufmann's avatar
Philip Kaufmann committed
800
#ifndef Q_OS_MAC // Ignored on Mac
801
    if(e->type() == QEvent::WindowStateChange)
802
    {
803
        if(clientModel && clientModel->getOptionsModel()->getMinimizeToTray())
804
        {
805
            QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent*>(e);
806
            if(!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized())
807
            {
808
809
                QTimer::singleShot(0, this, SLOT(hide()));
                e->ignore();
810
811
812
            }
        }
    }
813
#endif
814
815
816
817
}

void BitcoinGUI::closeEvent(QCloseEvent *event)
{
818
    if(clientModel)
819
    {
Philip Kaufmann's avatar
Philip Kaufmann committed
820
#ifndef Q_OS_MAC // Ignored on Mac
821
822
823
        if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
           !clientModel->getOptionsModel()->getMinimizeOnClose())
        {
824
            QApplication::quit();
825
        }
826
#endif
827
    }
828
829
    QMainWindow::closeEvent(event);
}
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
830

831
#ifdef ENABLE_WALLET
832
void BitcoinGUI::incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address)
833
{
834
    // On new transaction, make an info balloon
835
836
837
838
839
840
    message((amount)<0 ? tr("Sent transaction") : tr("Incoming transaction"),
             tr("Date: %1\n"
                "Amount: %2\n"
                "Type: %3\n"
                "Address: %4\n")
                  .arg(date)
841
                  .arg(BitcoinUnits::formatWithUnit(unit, amount, true))
842
843
                  .arg(type)
                  .arg(address), CClientUIInterface::MSG_INFORMATION);
844
}
845
#endif
846

847
848
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
{
849
    // Accept only URIs
850
851
852
853
854
855
856
857
    if(event->mimeData()->hasUrls())
        event->acceptProposedAction();
}

void BitcoinGUI::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls())
    {
858
        foreach(const QUrl &uri, event->mimeData()->urls())
859
        {
860
            emit receivedURI(uri.toString());
861
862
863
864
865
        }
    }
    event->acceptProposedAction();
}

866
867
868
869
870
871
bool BitcoinGUI::eventFilter(QObject *object, QEvent *event)
{
    // Catch status tip events
    if (event->type() == QEvent::StatusTip)
    {
        // Prevent adding text from setStatusTip(), if we currently use the status bar for displaying other stuff
872
        if (progressBarLabel->isVisible() || progressBar->isVisible())
873
874
875
876
877
            return true;
    }
    return QMainWindow::eventFilter(object, event);
}

878
#ifdef ENABLE_WALLET
879
bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient)
880
{
881
    // URI has to be valid
882
    if (walletFrame && walletFrame->handlePaymentRequest(recipient))
883
884
885
886
887
888
889
    {
        showNormalIfMinimized();
        gotoSendCoinsPage();
        return true;
    }
    else
        return false;
890
891
}

892
893
894
895
896
897
void BitcoinGUI::setEncryptionStatus(int status)
{
    switch(status)
    {
    case WalletModel::Unencrypted:
        labelEncryptionIcon->hide();
898
899
900
        encryptWalletAction->setChecked(false);
        changePassphraseAction->setEnabled(false);
        encryptWalletAction->setEnabled(true);
901
902
903
904
905
        break;
    case WalletModel::Unlocked:
        labelEncryptionIcon->show();
        labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_open").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
        labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
906
907
908
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
909
910
911
912
913
        break;
    case WalletModel::Locked:
        labelEncryptionIcon->show();
        labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_closed").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
        labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
914
915
916
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
917
918
919
        break;
    }
}
920
#endif
921

922
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
923
{
924
925
    if(!clientModel)
        return;
926
927
928
    // activateWindow() (sometimes) helps with keyboard focus on Windows
    if (isHidden())
    {
929
        show();
930
931
932
933
        activateWindow();
    }
    else if (isMinimized())
    {
934
        showNormal();
935
936
937
938
939
940
941
942
943
944
945
946
947
948
        activateWindow();
    }
    else if (GUIUtil::isObscured(this))
    {
        raise();
        activateWindow();
    }
    else if(fToggleHidden)
        hide();
}

void BitcoinGUI::toggleHidden()
{
    showNormalIfMinimized(true);
949
}
950
951
952
953

void BitcoinGUI::detectShutdown()
{
    if (ShutdownRequested())
954
955
956
957
958
    {
        if(rpcConsole)
            rpcConsole->hide();
        qApp->quit();
    }
959
}
960

Cozz Lovan's avatar
Cozz Lovan committed
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
void BitcoinGUI::showProgress(const QString &title, int nProgress)
{
    if (nProgress == 0)
    {
        progressDialog = new QProgressDialog(title, "", 0, 100);
        progressDialog->setWindowModality(Qt::ApplicationModal);
        progressDialog->setMinimumDuration(0);
        progressDialog->setCancelButton(0);
        progressDialog->setAutoClose(false);
        progressDialog->setValue(0);
    }
    else if (nProgress == 100)
    {
        if (progressDialog)
        {
            progressDialog->close();
            progressDialog->deleteLater();
        }
    }
    else if (progressDialog)
        progressDialog->setValue(nProgress);
}

984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string& message, const std::string& caption, unsigned int style)
{
    bool modal = (style & CClientUIInterface::MODAL);
    bool ret = false;
    // In case of modal message, use blocking connection to wait for user to click a button
    QMetaObject::invokeMethod(gui, "message",
                               modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection,
                               Q_ARG(QString, QString::fromStdString(caption)),
                               Q_ARG(QString, QString::fromStdString(message)),
                               Q_ARG(unsigned int, style),
                               Q_ARG(bool*, &ret));
    return ret;
}

void BitcoinGUI::subscribeToCoreSignals()
{
    // Connect signals to client
    uiInterface.ThreadSafeMessageBox.connect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3));
}

void BitcoinGUI::unsubscribeFromCoreSignals()
{
    // Disconnect signals from client
    uiInterface.ThreadSafeMessageBox.disconnect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3));
}
1009
1010
1011
1012
1013
1014
1015
1016

UnitDisplayStatusBarControl::UnitDisplayStatusBarControl():QLabel()
{
    optionsModel = 0;
    createContextMenu();
    setToolTip(tr("Unit to show amounts in. Click to select another unit."));
}

1017
/** So that it responds to button clicks */
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event)
{
    onDisplayUnitsClicked(event->pos());
}

/** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */
void UnitDisplayStatusBarControl::createContextMenu()
{
    menu = new QMenu();
    foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
    {
        QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this);
        menuAction->setData(QVariant(u));
        menu->addAction(menuAction);
    }
    connect(menu,SIGNAL(triggered(QAction*)),this,SLOT(onMenuSelection(QAction*)));
}

/** Lets the control know about the Options Model (and its signals) */
void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *optionsModel)
{
    if (optionsModel)
    {
        this->optionsModel = optionsModel;

        // be aware of a display unit change reported by the OptionsModel object.
        connect(optionsModel,SIGNAL(displayUnitChanged(int)),this,SLOT(updateDisplayUnit(int)));

        // initialize the display units label with the current value in the model.
        updateDisplayUnit(optionsModel->getDisplayUnit());
    }
}

/** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */
void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits)
{
1054
    setPixmap(QIcon(":/icons/unit_" + BitcoinUnits::id(newUnits)).pixmap(31,STATUSBAR_ICONSIZE));
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
}

/** Shows context menu with Display Unit options by the mouse coordinates */
void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint& point)
{
    QPoint globalPos = mapToGlobal(point);
    menu->exec(globalPos);
}

/** Tells underlying optionsModel to update its current display unit. */
void UnitDisplayStatusBarControl::onMenuSelection(QAction* action)
{
    if (action)
    {
        optionsModel->setDisplayUnit(action->data());
    }
}