bitcoingui.cpp 34.2 KB
Newer Older
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
1
/*
2
3
 * Qt4 bitcoin GUI.
 *
Fordy's avatar
Fordy committed
4
 * W.J. van der Laan 2011-2012
Fordy's avatar
Fordy committed
5
 * The Bitcoin Developers 2011-2012
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
6
 */
7
8
9

#include <QApplication>

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
10
#include "bitcoingui.h"
11

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
12
#include "transactiontablemodel.h"
13
#include "addressbookpage.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
14
#include "sendcoinsdialog.h"
15
#include "signverifymessagedialog.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
16
17
#include "optionsdialog.h"
#include "aboutdialog.h"
18
#include "clientmodel.h"
19
#include "walletmodel.h"
20
#include "editaddressdialog.h"
21
#include "optionsmodel.h"
22
#include "transactiondescdialog.h"
23
#include "addresstablemodel.h"
24
#include "transactionview.h"
25
#include "overviewpage.h"
26
#include "bitcoinunits.h"
27
#include "guiconstants.h"
28
#include "askpassphrasedialog.h"
29
#include "notificator.h"
30
#include "guiutil.h"
31
#include "rpcconsole.h"
32
#include "ui_interface.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
33

Philip Kaufmann's avatar
Philip Kaufmann committed
34
#ifdef Q_OS_MAC
35
36
37
#include "macdockiconhandler.h"
#endif

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
38
39
40
41
42
43
44
#include <QMenuBar>
#include <QMenu>
#include <QIcon>
#include <QVBoxLayout>
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
45
#include <QMessageBox>
46
#include <QProgressBar>
47
#include <QStackedWidget>
48
#include <QDateTime>
49
#include <QMovie>
sje397's avatar
sje397 committed
50
51
#include <QFileDialog>
#include <QDesktopServices>
52
#include <QTimer>
53
54
#include <QDragEnterEvent>
#include <QUrl>
55
#include <QMimeData>
56
#include <QStyle>
57

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
58
59
60
#include <iostream>

BitcoinGUI::BitcoinGUI(QWidget *parent):
61
62
63
    QMainWindow(parent),
    clientModel(0),
    walletModel(0),
64
65
    encryptWalletAction(0),
    changePassphraseAction(0),
66
    aboutQtAction(0),
67
    trayIcon(0),
68
    notificator(0),
69
70
    rpcConsole(0),
    prevBlocks(0)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
71
72
{
    resize(850, 550);
73
    setWindowTitle(tr("Bitcoin") + " - " + tr("Wallet"));
Philip Kaufmann's avatar
Philip Kaufmann committed
74
#ifndef Q_OS_MAC
75
    qApp->setWindowIcon(QIcon(":icons/bitcoin"));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
76
    setWindowIcon(QIcon(":icons/bitcoin"));
77
78
79
80
#else
    setUnifiedTitleAndToolBarOnMac(true);
    QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
81
82
    // Accept D&D of URIs
    setAcceptDrops(true);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
83

84
    // Create actions for the toolbar, menu bar and tray/dock icon
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
85
    createActions();
86

87
88
    // Create application menu bar
    createMenuBar();
89

90
91
    // Create the toolbars
    createToolBars();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
92

93
    // Create system tray icon and notification
94
    createTrayIcon();
95

96
    // Create tabs
97
    overviewPage = new OverviewPage();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
98

99
100
    transactionsPage = new QWidget(this);
    QVBoxLayout *vbox = new QVBoxLayout();
101
102
    transactionView = new TransactionView(this);
    vbox->addWidget(transactionView);
103
104
    transactionsPage->setLayout(vbox);

105
106
107
108
109
110
    addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);

    receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);

    sendCoinsPage = new SendCoinsDialog(this);

111
    signVerifyMessageDialog = new SignVerifyMessageDialog(this);
112

113
114
115
    centralWidget = new QStackedWidget(this);
    centralWidget->addWidget(overviewPage);
    centralWidget->addWidget(transactionsPage);
116
117
118
    centralWidget->addWidget(addressBookPage);
    centralWidget->addWidget(receiveCoinsPage);
    centralWidget->addWidget(sendCoinsPage);
119
    setCentralWidget(centralWidget);
120

121
    // Create status bar
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
122
    statusBar();
123

124
    // Status bar notification icons
125
    QFrame *frameBlocks = new QFrame();
126
127
128
    frameBlocks->setContentsMargins(0,0,0,0);
    frameBlocks->setMinimumWidth(56);
    frameBlocks->setMaximumWidth(56);
129
130
131
    QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
    frameBlocksLayout->setContentsMargins(3,0,3,0);
    frameBlocksLayout->setSpacing(3);
132
    labelEncryptionIcon = new QLabel();
133
    labelConnectionsIcon = new QLabel();
134
    labelBlocksIcon = new QLabel();
135
    frameBlocksLayout->addStretch();
136
137
    frameBlocksLayout->addWidget(labelEncryptionIcon);
    frameBlocksLayout->addStretch();
138
139
    frameBlocksLayout->addWidget(labelConnectionsIcon);
    frameBlocksLayout->addStretch();
140
141
    frameBlocksLayout->addWidget(labelBlocksIcon);
    frameBlocksLayout->addStretch();
142

143
144
    // Progress bar and label for blocks download
    progressBarLabel = new QLabel();
145
146
    progressBarLabel->setVisible(false);
    progressBar = new QProgressBar();
147
    progressBar->setAlignment(Qt::AlignCenter);
148
149
    progressBar->setVisible(false);

150
151
152
153
154
155
156
157
158
    // 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
    QString curStyle = qApp->style()->metaObject()->className();
    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; }");
    }

159
160
    statusBar()->addWidget(progressBarLabel);
    statusBar()->addWidget(progressBar);
161
    statusBar()->addPermanentWidget(frameBlocks);
162

163
164
    syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this);

165
    // Clicking on a transaction on the overview page simply sends you to transaction history page
166
    connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
167
    connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
168

169
    // Double-clicking on a transaction on the transaction history page shows details
170
    connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
171

172
173
174
    rpcConsole = new RPCConsole(this);
    connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show()));

175
176
177
    // Clicking on "Send Coins" in the address book sends you to the send coins tab
    connect(addressBookPage, SIGNAL(sendCoins(QString)), this, SLOT(gotoSendCoinsPage(QString)));
    // Clicking on "Verify Message" in the address book opens the verify message tab in the Sign/Verify Message dialog
178
    connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString)));
179
    // Clicking on "Sign Message" in the receive coins page opens the sign message tab in the Sign/Verify Message dialog
180
181
    connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString)));

182
183
184
    // Install event filter to be able to catch status tip events (QEvent::StatusTip)
    this->installEventFilter(this);

185
    gotoOverviewPage();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
186
187
}

188
189
BitcoinGUI::~BitcoinGUI()
{
190
191
    if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
        trayIcon->hide();
Philip Kaufmann's avatar
Philip Kaufmann committed
192
#ifdef Q_OS_MAC
193
194
195
196
    delete appMenuBar;
#endif
}

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
197
198
void BitcoinGUI::createActions()
{
199
    QActionGroup *tabGroup = new QActionGroup(this);
200

201
    overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
202
203
    overviewAction->setStatusTip(tr("Show general overview of wallet"));
    overviewAction->setToolTip(overviewAction->statusTip());
204
    overviewAction->setCheckable(true);
205
    overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
206
    tabGroup->addAction(overviewAction);
207

Philip Kaufmann's avatar
Philip Kaufmann committed
208
    sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
209
210
    sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
    sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
Philip Kaufmann's avatar
Philip Kaufmann committed
211
212
213
214
215
    sendCoinsAction->setCheckable(true);
    sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
    tabGroup->addAction(sendCoinsAction);

    receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
216
217
    receiveCoinsAction->setStatusTip(tr("Show the list of addresses for receiving payments"));
    receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
Philip Kaufmann's avatar
Philip Kaufmann committed
218
219
220
221
    receiveCoinsAction->setCheckable(true);
    receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
    tabGroup->addAction(receiveCoinsAction);

222
    historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
223
224
    historyAction->setStatusTip(tr("Browse transaction history"));
    historyAction->setToolTip(historyAction->statusTip());
225
    historyAction->setCheckable(true);
226
    historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
227
228
    tabGroup->addAction(historyAction);

229
    addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
230
231
    addressBookAction->setStatusTip(tr("Edit the list of stored addresses and labels"));
    addressBookAction->setToolTip(addressBookAction->statusTip());
232
    addressBookAction->setCheckable(true);
233
    addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5));
234
235
    tabGroup->addAction(addressBookAction);

236
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
237
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
Philip Kaufmann's avatar
Philip Kaufmann committed
238
239
240
241
    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()));
242
    connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
243
    connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
244
    connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
245
246
    connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));

247
    quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
248
    quitAction->setStatusTip(tr("Quit application"));
249
    quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
250
    quitAction->setMenuRole(QAction::QuitRole);
251
    aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About Bitcoin"), this);
252
    aboutAction->setStatusTip(tr("Show information about Bitcoin"));
253
    aboutAction->setMenuRole(QAction::AboutRole);
254
    aboutQtAction = new QAction(QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this);
255
    aboutQtAction->setStatusTip(tr("Show information about Qt"));
256
    aboutQtAction->setMenuRole(QAction::AboutQtRole);
257
    optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
258
    optionsAction->setStatusTip(tr("Modify configuration options for Bitcoin"));
259
    optionsAction->setMenuRole(QAction::PreferencesRole);
260
    toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("&Show / Hide"), this);
261
    toggleHideAction->setStatusTip(tr("Show or hide the main Window"));
262
    encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this);
263
    encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet"));
264
    encryptWalletAction->setCheckable(true);
265
    backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this);
266
    backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
267
    changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this);
268
    changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
Philip Kaufmann's avatar
Philip Kaufmann committed
269
    signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this);
270
    signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
Philip Kaufmann's avatar
Philip Kaufmann committed
271
    verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this);
272
    verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
Philip Kaufmann's avatar
Philip Kaufmann committed
273
274

    exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
275
276
    exportAction->setStatusTip(tr("Export the data in the current tab to a file"));
    exportAction->setToolTip(exportAction->statusTip());
277
    openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this);
278
    openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console"));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
279

280
281
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
    connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
282
    connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
Philip Kaufmann's avatar
Philip Kaufmann committed
283
    connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
284
    connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden()));
285
    connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
sje397's avatar
sje397 committed
286
    connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
287
    connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
Philip Kaufmann's avatar
Philip Kaufmann committed
288
289
    connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab()));
    connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab()));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
290
291
}

292
293
void BitcoinGUI::createMenuBar()
{
Philip Kaufmann's avatar
Philip Kaufmann committed
294
#ifdef Q_OS_MAC
295
296
297
298
299
300
301
302
303
    // 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"));
304
    file->addAction(backupWalletAction);
305
    file->addAction(exportAction);
306
    file->addAction(signMessageAction);
307
    file->addAction(verifyMessageAction);
308
    file->addSeparator();
309
310
311
312
313
314
315
316
317
    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"));
318
319
    help->addAction(openRPCConsoleAction);
    help->addSeparator();
320
    help->addAction(aboutAction);
321
    help->addAction(aboutQtAction);
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
}

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);
}

339
void BitcoinGUI::setClientModel(ClientModel *clientModel)
340
{
341
    this->clientModel = clientModel;
342
    if(clientModel)
343
    {
344
        // Replace some strings and icons, when using the testnet
345
346
        if(clientModel->isTestNet())
        {
347
            setWindowTitle(windowTitle() + QString(" ") + tr("[testnet]"));
Philip Kaufmann's avatar
Philip Kaufmann committed
348
#ifndef Q_OS_MAC
349
            qApp->setWindowIcon(QIcon(":icons/bitcoin_testnet"));
350
            setWindowIcon(QIcon(":icons/bitcoin_testnet"));
351
#else
352
            MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet"));
353
#endif
354
355
            if(trayIcon)
            {
356
357
                // Just attach " [testnet]" to the existing tooltip
                trayIcon->setToolTip(trayIcon->toolTip() + QString(" ") + tr("[testnet]"));
358
359
                trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
            }
360

361
            toggleHideAction->setIcon(QIcon(":/icons/toolbar_testnet"));
362
            aboutAction->setIcon(QIcon(":/icons/toolbar_testnet"));
363
364
        }

365
366
367
368
369
        // 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
        if(trayIcon)
            createTrayIconMenu();

370
371
372
        // Keep up to date with client
        setNumConnections(clientModel->getNumConnections());
        connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
373

374
375
        setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers());
        connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int)));
376

377
        // Receive and report messages from network/worker thread
378
        connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int)));
379

380
        overviewPage->setClientModel(clientModel);
381
        rpcConsole->setClientModel(clientModel);
Philip Kaufmann's avatar
Philip Kaufmann committed
382
383
        addressBookPage->setOptionsModel(clientModel->getOptionsModel());
        receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
384
    }
385
386
387
388
389
}

void BitcoinGUI::setWalletModel(WalletModel *walletModel)
{
    this->walletModel = walletModel;
390
391
    if(walletModel)
    {
392
        // Receive and report messages from wallet thread
393
        connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int)));
394

395
396
        // Put transaction list in tabs
        transactionView->setModel(walletModel);
397
        overviewPage->setWalletModel(walletModel);
398
399
400
        addressBookPage->setModel(walletModel->getAddressTableModel());
        receiveCoinsPage->setModel(walletModel->getAddressTableModel());
        sendCoinsPage->setModel(walletModel);
401
        signVerifyMessageDialog->setModel(walletModel);
402

403
404
        setEncryptionStatus(walletModel->getEncryptionStatus());
        connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
405

406
        // Balloon pop-up for new transaction
407
408
        connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
                this, SLOT(incomingTransaction(QModelIndex,int,int)));
409

410
411
412
        // Ask for passphrase if needed
        connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
    }
413
414
}

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
415
416
void BitcoinGUI::createTrayIcon()
{
Philip Kaufmann's avatar
Philip Kaufmann committed
417
#ifndef Q_OS_MAC
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
418
    trayIcon = new QSystemTrayIcon(this);
419

420
    trayIcon->setToolTip(tr("Bitcoin client"));
421
    trayIcon->setIcon(QIcon(":/icons/toolbar"));
422
423
424
425
426
427
428
429
430
431
432
433
434
    trayIcon->show();
#endif

    notificator = new Notificator(qApp->applicationName(), trayIcon);
}

void BitcoinGUI::createTrayIconMenu()
{
    QMenu *trayIconMenu;
#ifndef Q_OS_MAC
    trayIconMenu = new QMenu(this);
    trayIcon->setContextMenu(trayIconMenu);

435
436
    connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
437
438
439
440
441
442
443
#else
    // Note: On Mac, the dock icon is used to provide the tray's functionality.
    MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
    trayIconMenu = dockIconHandler->dockMenu();
#endif

    // Configuration of the tray icon (or dock icon) icon menu
444
    trayIconMenu->addAction(toggleHideAction);
445
    trayIconMenu->addSeparator();
446
447
    trayIconMenu->addAction(sendCoinsAction);
    trayIconMenu->addAction(receiveCoinsAction);
448
    trayIconMenu->addSeparator();
449
    trayIconMenu->addAction(signMessageAction);
450
    trayIconMenu->addAction(verifyMessageAction);
451
452
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(optionsAction);
453
    trayIconMenu->addAction(openRPCConsoleAction);
Philip Kaufmann's avatar
Philip Kaufmann committed
454
#ifndef Q_OS_MAC // This is built-in on Mac
455
456
457
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(quitAction);
#endif
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
458
459
}

Philip Kaufmann's avatar
Philip Kaufmann committed
460
#ifndef Q_OS_MAC
461
462
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
463
    if(reason == QSystemTrayIcon::Trigger)
464
    {
465
        // Click on system tray icon triggers show/hide of the main window
466
        toggleHideAction->trigger();
467
468
    }
}
469
#endif
470

471
472
void BitcoinGUI::optionsClicked()
{
473
474
    if(!clientModel || !clientModel->getOptionsModel())
        return;
475
    OptionsDialog dlg;
476
    dlg.setModel(clientModel->getOptionsModel());
477
    dlg.exec();
478
479
}

480
void BitcoinGUI::aboutClicked()
481
{
482
    AboutDialog dlg;
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
483
    dlg.setModel(clientModel);
484
    dlg.exec();
485
486
}

487
488
void BitcoinGUI::setNumConnections(int count)
{
489
490
491
    QString icon;
    switch(count)
    {
492
493
494
495
496
    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;
497
    }
498
    labelConnectionsIcon->setPixmap(QIcon(icon).pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
499
    labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count));
500
501
}

502
void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
503
{
504
505
506
    // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait until chain-sync starts -> garbelled text)
    statusBar()->clearMessage();

507
    // don't show / hide progress bar and its label if we have no connection to the network
508
509
    enum BlockSource blockSource = clientModel ? clientModel->getBlockSource() : BLOCK_SOURCE_NONE;
    if (blockSource == BLOCK_SOURCE_NONE || (blockSource == BLOCK_SOURCE_NETWORK && clientModel->getNumConnections() == 0))
510
511
512
513
    {
        progressBarLabel->setVisible(false);
        progressBar->setVisible(false);

514
        return;
515
516
    }

517
518
    QString tooltip;

519
520
521
522
523
    QString importText;
    switch (blockSource) {
    case BLOCK_SOURCE_NONE:
    case BLOCK_SOURCE_NETWORK:
        importText = tr("Synchronizing with network...");
Pieter Wuille's avatar
Pieter Wuille committed
524
        break;
525
526
    case BLOCK_SOURCE_DISK:
        importText = tr("Importing blocks from disk...");
Pieter Wuille's avatar
Pieter Wuille committed
527
        break;
528
529
530
531
    case BLOCK_SOURCE_REINDEX:
        importText = tr("Reindexing blocks on disk...");
    }

532
    QDateTime lastBlockDate = clientModel->getLastBlockDate();
533
534
    QDateTime currentDate = QDateTime::currentDateTime();
    int secs = lastBlockDate.secsTo(currentDate);
535

536
    if(count < nTotalBlocks)
537
    {
538
        tooltip = tr("Processed %1 of %2 (estimated) blocks of transaction history.").arg(count).arg(nTotalBlocks);
539
540
541
    }
    else
    {
542
        tooltip = tr("Processed %1 blocks of transaction history.").arg(count);
543
    }
544

545
    // Set icon state: spinning if catching up, tick otherwise
546
    if(secs < 90*60 && count >= nTotalBlocks)
547
    {
548
        tooltip = tr("Up to date") + QString(".<br>") + tooltip;
549
        labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
550
551

        overviewPage->showOutOfSyncWarning(false);
552
553
554

        progressBarLabel->setVisible(false);
        progressBar->setVisible(false);
555
    }
556
557
    else
    {
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
        // Represent time from last generated block in human readable text
        QString timeBehindText;
        if(secs < 48*60*60)
        {
            timeBehindText = tr("%n hour(s)","",secs/(60*60));
        }
        else if(secs < 14*24*60*60)
        {
            timeBehindText = tr("%n day(s)","",secs/(24*60*60));
        }
        else
        {
            timeBehindText = tr("%n week(s)","",secs/(7*24*60*60));
        }

        progressBarLabel->setText(importText);
        progressBarLabel->setVisible(true);
        progressBar->setFormat(tr("%1 behind").arg(timeBehindText));
576
577
        progressBar->setMaximum(1000000000);
        progressBar->setValue(clientModel->getVerificationProgress() * 1000000000.0 + 0.5);
578
579
        progressBar->setVisible(true);

580
        tooltip = tr("Catching up...") + QString("<br>") + tooltip;
581
        labelBlocksIcon->setMovie(syncIconMovie);
582
583
584
        if(count != prevBlocks)
            syncIconMovie->jumpToNextFrame();
        prevBlocks = count;
585
586

        overviewPage->showOutOfSyncWarning(true);
587

588
        tooltip += QString("<br>");
589
590
591
        tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText);
        tooltip += QString("<br>");
        tooltip += tr("Transactions after this will not yet be visible.");
592
    }
593

594
595
596
    // Don't word-wrap this (fixed-width) tooltip
    tooltip = QString("<nobr>") + tooltip + QString("</nobr>");

597
    labelBlocksIcon->setToolTip(tooltip);
598
599
    progressBarLabel->setToolTip(tooltip);
    progressBar->setToolTip(tooltip);
600
601
}

602
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
603
{
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
    QString strTitle = tr("Bitcoin") + " - ";
    // Default to information icon
    int nMBoxIcon = QMessageBox::Information;
    int nNotifyIcon = Notificator::Information;

    // Check for usage of predefined title
    switch (style) {
    case CClientUIInterface::MSG_ERROR:
        strTitle += tr("Error");
        break;
    case CClientUIInterface::MSG_WARNING:
        strTitle += tr("Warning");
        break;
    case CClientUIInterface::MSG_INFORMATION:
        strTitle += tr("Information");
        break;
    default:
        strTitle += title; // Use supplied title
    }

    // Check for error/warning icon
    if (style & CClientUIInterface::ICON_ERROR) {
        nMBoxIcon = QMessageBox::Critical;
        nNotifyIcon = Notificator::Critical;
628
    }
629
630
631
632
633
634
    else if (style & CClientUIInterface::ICON_WARNING) {
        nMBoxIcon = QMessageBox::Warning;
        nNotifyIcon = Notificator::Warning;
    }

    // Display message
635
    if (style & CClientUIInterface::MODAL) {
636
637
638
639
640
641
        // 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;

        QMessageBox mBox((QMessageBox::Icon)nMBoxIcon, strTitle, message, buttons);
642
643
644
        int r = mBox.exec();
        if (ret != NULL)
            *ret = r == QMessageBox::Ok;
645
646
647
    }
    else
        notificator->notify((Notificator::Class)nNotifyIcon, strTitle, message);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
648
}
649
650
651

void BitcoinGUI::changeEvent(QEvent *e)
{
652
    QMainWindow::changeEvent(e);
Philip Kaufmann's avatar
Philip Kaufmann committed
653
#ifndef Q_OS_MAC // Ignored on Mac
654
    if(e->type() == QEvent::WindowStateChange)
655
    {
656
        if(clientModel && clientModel->getOptionsModel()->getMinimizeToTray())
657
        {
658
            QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent*>(e);
659
            if(!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized())
660
            {
661
662
                QTimer::singleShot(0, this, SLOT(hide()));
                e->ignore();
663
664
665
            }
        }
    }
666
#endif
667
668
669
670
}

void BitcoinGUI::closeEvent(QCloseEvent *event)
{
671
    if(clientModel)
672
    {
Philip Kaufmann's avatar
Philip Kaufmann committed
673
#ifndef Q_OS_MAC // Ignored on Mac
674
675
676
677
678
        if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
           !clientModel->getOptionsModel()->getMinimizeOnClose())
        {
            qApp->quit();
        }
679
#endif
680
    }
681
682
    QMainWindow::closeEvent(event);
}
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
683
684
685

void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
{
686
687
688
    QString strMessage = tr("This transaction is over the size limit. You can still send it for a fee of %1, "
        "which goes to the nodes that process your transaction and helps to support the network. "
        "Do you want to pay the fee?").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nFeeRequired));
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
689
    QMessageBox::StandardButton retval = QMessageBox::question(
690
          this, tr("Confirm transaction fee"), strMessage,
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
691
692
693
          QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes);
    *payFee = (retval == QMessageBox::Yes);
}
694

695
void BitcoinGUI::incomingTransaction(const QModelIndex& parent, int start, int /*end*/)
696
{
697
698
    // Prevent balloon-spam when initial block download is in progress
    if(!walletModel || !clientModel || clientModel->inInitialBlockDownload())
699
        return;
700

701
    TransactionTableModel *ttm = walletModel->getTransactionTableModel();
702
703
704

    QString date = ttm->index(start, TransactionTableModel::Date, parent)
                     .data().toString();
705
    qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
706
707
708
709
                      .data(Qt::EditRole).toULongLong();
    QString type = ttm->index(start, TransactionTableModel::Type, parent)
                     .data().toString();
    QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
710
                        .data().toString();
711

712
713
714
715
716
717
718
719
720
721
    // On new transaction, make an info balloon
    message((amount)<0 ? tr("Sent transaction") : tr("Incoming transaction"),
             tr("Date: %1\n"
                "Amount: %2\n"
                "Type: %3\n"
                "Address: %4\n")
                  .arg(date)
                  .arg(BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), amount, true))
                  .arg(type)
                  .arg(address), CClientUIInterface::MSG_INFORMATION);
722
}
723

724
void BitcoinGUI::gotoOverviewPage()
725
726
727
{
    overviewAction->setChecked(true);
    centralWidget->setCurrentWidget(overviewPage);
728

729
    exportAction->setEnabled(false);
730
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
731
732
}

733
void BitcoinGUI::gotoHistoryPage()
734
735
736
{
    historyAction->setChecked(true);
    centralWidget->setCurrentWidget(transactionsPage);
737

738
    exportAction->setEnabled(true);
739
740
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
741
742
}

743
744
745
746
void BitcoinGUI::gotoAddressBookPage()
{
    addressBookAction->setChecked(true);
    centralWidget->setCurrentWidget(addressBookPage);
747
748
749
750

    exportAction->setEnabled(true);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
751
752
753
754
755
756
}

void BitcoinGUI::gotoReceiveCoinsPage()
{
    receiveCoinsAction->setChecked(true);
    centralWidget->setCurrentWidget(receiveCoinsPage);
757
758
759
760

    exportAction->setEnabled(true);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
761
762
}

763
void BitcoinGUI::gotoSendCoinsPage(QString addr)
764
765
766
767
{
    sendCoinsAction->setChecked(true);
    centralWidget->setCurrentWidget(sendCoinsPage);

768
769
    exportAction->setEnabled(false);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
770
771
772

    if(!addr.isEmpty())
        sendCoinsPage->setAddress(addr);
773
}
774

775
void BitcoinGUI::gotoSignMessageTab(QString addr)
776
{
777
778
779
    // call show() in showTab_SM()
    signVerifyMessageDialog->showTab_SM(true);

780
    if(!addr.isEmpty())
781
782
        signVerifyMessageDialog->setAddress_SM(addr);
}
783

784
785
786
787
788
789
790
void BitcoinGUI::gotoVerifyMessageTab(QString addr)
{
    // call show() in showTab_VM()
    signVerifyMessageDialog->showTab_VM(true);

    if(!addr.isEmpty())
        signVerifyMessageDialog->setAddress_VM(addr);
791
792
}

793
794
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
{
795
    // Accept only URIs
796
797
798
799
800
801
802
803
    if(event->mimeData()->hasUrls())
        event->acceptProposedAction();
}

void BitcoinGUI::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls())
    {
804
        int nValidUrisFound = 0;
805
806
        QList<QUrl> uris = event->mimeData()->urls();
        foreach(const QUrl &uri, uris)
807
        {
808
809
            if (sendCoinsPage->handleURI(uri.toString()))
                nValidUrisFound++;
810
        }
811
812
813
814
815

        // if valid URIs were found
        if (nValidUrisFound)
            gotoSendCoinsPage();
        else
816
817
            message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
                      CClientUIInterface::ICON_WARNING);
818
819
820
821
822
    }

    event->acceptProposedAction();
}

823
824
825
826
827
828
829
830
831
832
833
834
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
        if (progressBarLabel->isVisible() && progressBar->isVisible())
            return true;
    }
    return QMainWindow::eventFilter(object, event);
}

835
void BitcoinGUI::handleURI(QString strURI)
836
{
837
838
839
840
841
842
843
    // URI has to be valid
    if (sendCoinsPage->handleURI(strURI))
    {
        showNormalIfMinimized();
        gotoSendCoinsPage();
    }
    else
844
845
        message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
                  CClientUIInterface::ICON_WARNING);
846
847
}

848
849
850
851
852
853
void BitcoinGUI::setEncryptionStatus(int status)
{
    switch(status)
    {
    case WalletModel::Unencrypted:
        labelEncryptionIcon->hide();
854
855
856
        encryptWalletAction->setChecked(false);
        changePassphraseAction->setEnabled(false);
        encryptWalletAction->setEnabled(true);
857
858
859
860
861
        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>"));
862
863
864
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
865
866
867
868
869
        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>"));
870
871
872
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
873
874
875
        break;
    }
}
876
877
878

void BitcoinGUI::encryptWallet(bool status)
{
879
880
    if(!walletModel)
        return;
881
882
883
884
885
886
887
888
    AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
                                     AskPassphraseDialog::Decrypt, this);
    dlg.setModel(walletModel);
    dlg.exec();

    setEncryptionStatus(walletModel->getEncryptionStatus());
}

sje397's avatar
sje397 committed
889
890
891
892
893
894
void BitcoinGUI::backupWallet()
{
    QString saveDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
    QString filename = QFileDialog::getSaveFileName(this, tr("Backup Wallet"), saveDir, tr("Wallet Data (*.dat)"));
    if(!filename.isEmpty()) {
        if(!walletModel->backupWallet(filename)) {
895
896
            message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."),
                      CClientUIInterface::MSG_ERROR);
sje397's avatar
sje397 committed
897
        }
898
899
900
        else
            message(tr("Backup Successful"), tr("The wallet data was successfully saved to the new location."),
                      CClientUIInterface::MSG_INFORMATION);
sje397's avatar
sje397 committed
901
902
903
    }
}

904
905
906
907
908
909
910
911
912
void BitcoinGUI::changePassphrase()
{
    AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
    dlg.setModel(walletModel);
    dlg.exec();
}

void BitcoinGUI::unlockWallet()
{
913
914
    if(!walletModel)
        return;
915
    // Unlock wallet when requested by wallet model
916
917
918
919
920
921
922
    if(walletModel->getEncryptionStatus() == WalletModel::Locked)
    {
        AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
        dlg.setModel(walletModel);
        dlg.exec();
    }
}
923

924
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
925
{
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
}