bitcoingui.cpp 30.3 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
 */
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
7
8
#include "bitcoingui.h"
#include "transactiontablemodel.h"
9
#include "addressbookpage.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
10
#include "sendcoinsdialog.h"
11
#include "messagepage.h"
12
#include "verifymessagedialog.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
13
14
#include "optionsdialog.h"
#include "aboutdialog.h"
15
#include "clientmodel.h"
16
#include "walletmodel.h"
17
#include "editaddressdialog.h"
18
#include "optionsmodel.h"
19
#include "transactiondescdialog.h"
20
#include "addresstablemodel.h"
21
#include "transactionview.h"
22
#include "overviewpage.h"
23
#include "bitcoinunits.h"
24
#include "guiconstants.h"
25
#include "askpassphrasedialog.h"
26
#include "notificator.h"
27
#include "guiutil.h"
28
#include "rpcconsole.h"
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
29

30
31
32
33
#ifdef Q_WS_MAC
#include "macdockiconhandler.h"
#endif

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
34
35
36
37
38
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QIcon>
39
#include <QTabWidget>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
40
41
42
43
44
45
#include <QVBoxLayout>
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
46
#include <QLocale>
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
47
#include <QMessageBox>
48
#include <QProgressBar>
49
#include <QStackedWidget>
50
#include <QDateTime>
51
#include <QMovie>
sje397's avatar
sje397 committed
52
53
#include <QFileDialog>
#include <QDesktopServices>
54
#include <QTimer>
55

56
57
58
#include <QDragEnterEvent>
#include <QUrl>

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

BitcoinGUI::BitcoinGUI(QWidget *parent):
62
63
64
    QMainWindow(parent),
    clientModel(0),
    walletModel(0),
65
66
    encryptWalletAction(0),
    changePassphraseAction(0),
67
    aboutQtAction(0),
68
    trayIcon(0),
69
70
    notificator(0),
    rpcConsole(0)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
71
72
{
    resize(850, 550);
73
    setWindowTitle(tr("Bitcoin Wallet"));
74
#ifndef Q_WS_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
94
    // Create the tray icon (or setup the dock icon)
    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
112
    messagePage = new MessagePage(this);

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
120
121
#ifdef FIRST_CLASS_MESSAGING
    centralWidget->addWidget(messagePage);
#endif
122
    setCentralWidget(centralWidget);
123

124
    // Create status bar
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
125
    statusBar();
126

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

146
147
    // Progress bar and label for blocks download
    progressBarLabel = new QLabel();
148
149
    progressBarLabel->setVisible(false);
    progressBar = new QProgressBar();
150
    progressBar->setAlignment(Qt::AlignCenter);
151
152
153
154
    progressBar->setVisible(false);

    statusBar()->addWidget(progressBarLabel);
    statusBar()->addWidget(progressBar);
155
    statusBar()->addPermanentWidget(frameBlocks);
156

157
158
    syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this);

159
    // Clicking on a transaction on the overview page simply sends you to transaction history page
160
    connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
161
    connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
162

163
164
    // Doubleclicking on a transaction on the transaction history page shows details
    connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
165

166
167
168
    rpcConsole = new RPCConsole(this);
    connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show()));

169
    gotoOverviewPage();
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
170
171
}

172
173
BitcoinGUI::~BitcoinGUI()
{
174
175
    if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
        trayIcon->hide();
176
177
178
179
180
#ifdef Q_WS_MAC
    delete appMenuBar;
#endif
}

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
181
182
void BitcoinGUI::createActions()
{
183
    QActionGroup *tabGroup = new QActionGroup(this);
184

185
    overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
186
    overviewAction->setToolTip(tr("Show general overview of wallet"));
187
    overviewAction->setCheckable(true);
188
    overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
189
    tabGroup->addAction(overviewAction);
190

191
    historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
192
    historyAction->setToolTip(tr("Browse transaction history"));
193
    historyAction->setCheckable(true);
194
    historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
195
196
    tabGroup->addAction(historyAction);

197
198
199
    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);
200
    addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5));
201
202
203
204
205
    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);
206
    receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
207
208
209
    tabGroup->addAction(receiveCoinsAction);

    sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
210
    sendCoinsAction->setToolTip(tr("Send coins to a Bitcoin address"));
211
    sendCoinsAction->setCheckable(true);
212
    sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
213
214
    tabGroup->addAction(sendCoinsAction);

215
    messageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this);
216
217
218
219
220
221
    messageAction->setToolTip(tr("Prove you control an address"));
#ifdef FIRST_CLASS_MESSAGING
    messageAction->setCheckable(true);
#endif
    tabGroup->addAction(messageAction);

222
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
223
    connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
224
    connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
225
    connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
226
    connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
227
    connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
228
    connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
229
    connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
230
    connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
231
    connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
232
    connect(messageAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
233
    connect(messageAction, SIGNAL(triggered()), this, SLOT(gotoMessagePage()));
234

235
    quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
236
    quitAction->setToolTip(tr("Quit application"));
237
    quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
238
239
    quitAction->setMenuRole(QAction::QuitRole);
    aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About %1").arg(qApp->applicationName()), this);
240
    aboutAction->setToolTip(tr("Show information about Bitcoin"));
241
242
243
244
    aboutAction->setMenuRole(QAction::AboutRole);
    aboutQtAction = new QAction(tr("About &Qt"), this);
    aboutQtAction->setToolTip(tr("Show information about Qt"));
    aboutQtAction->setMenuRole(QAction::AboutQtRole);
245
    optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
246
    optionsAction->setToolTip(tr("Modify configuration options for Bitcoin"));
247
    optionsAction->setMenuRole(QAction::PreferencesRole);
248
    toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("Show/Hide &Bitcoin"), this);
249
    toggleHideAction->setToolTip(tr("Show or hide the Bitcoin window"));
250
    exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
251
    exportAction->setToolTip(tr("Export the data in the current tab to a file"));
252
    encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this);
253
254
    encryptWalletAction->setToolTip(tr("Encrypt or decrypt wallet"));
    encryptWalletAction->setCheckable(true);
255
    backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this);
sje397's avatar
sje397 committed
256
    backupWalletAction->setToolTip(tr("Backup wallet to another location"));
257
    changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this);
258
    changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption"));
259
    openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this);
260
    openRPCConsoleAction->setToolTip(tr("Open debugging and diagnostic console"));
261
262
    verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this);
    verifyMessageAction->setToolTip(tr("Verify a message signature"));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
263

264
265
266
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
    connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
    connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
267
    connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
268
    connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden()));
269
    connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
sje397's avatar
sje397 committed
270
    connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
271
    connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
272
    connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(verifyMessage()));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
273
274
}

275
276
277
278
279
280
281
282
283
284
285
286
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"));
287
    file->addAction(backupWalletAction);
288
    file->addAction(exportAction);
289
290
291
#ifndef FIRST_CLASS_MESSAGING
    file->addAction(messageAction);
#endif
292
    file->addAction(verifyMessageAction);
293
    file->addSeparator();
294
295
296
297
298
299
300
301
302
    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"));
303
304
    help->addAction(openRPCConsoleAction);
    help->addSeparator();
305
    help->addAction(aboutAction);
306
    help->addAction(aboutQtAction);
307
308
309
310
311
312
313
314
315
316
317
}

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);
318
319
320
#ifdef FIRST_CLASS_MESSAGING
    toolbar->addAction(messageAction);
#endif
321
322
323
324
325
326

    QToolBar *toolbar2 = addToolBar(tr("Actions toolbar"));
    toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolbar2->addAction(exportAction);
}

327
void BitcoinGUI::setClientModel(ClientModel *clientModel)
328
{
329
    this->clientModel = clientModel;
330
    if(clientModel)
331
    {
332
333
        if(clientModel->isTestNet())
        {
334
            setWindowTitle(windowTitle() + QString(" ") + tr("[testnet]"));
335
#ifndef Q_WS_MAC
336
            qApp->setWindowIcon(QIcon(":icons/bitcoin_testnet"));
337
            setWindowIcon(QIcon(":icons/bitcoin_testnet"));
338
#else
339
            MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet"));
340
#endif
341
342
            if(trayIcon)
            {
343
                trayIcon->setToolTip(tr("Bitcoin client") + QString(" ") + tr("[testnet]"));
344
                trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
345
                toggleHideAction->setIcon(QIcon(":/icons/toolbar_testnet"));
346
            }
347
348
        }

349
350
351
        // Keep up to date with client
        setNumConnections(clientModel->getNumConnections());
        connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
352

353
354
        setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers());
        connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int)));
355

356
        // Report errors from network/worker thread
357
        connect(clientModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool)));
358
359

        rpcConsole->setClientModel(clientModel);
360
    }
361
362
363
364
365
}

void BitcoinGUI::setWalletModel(WalletModel *walletModel)
{
    this->walletModel = walletModel;
366
367
368
    if(walletModel)
    {
        // Report errors from wallet thread
369
        connect(walletModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool)));
370

371
372
        // Put transaction list in tabs
        transactionView->setModel(walletModel);
373

374
375
376
377
        overviewPage->setModel(walletModel);
        addressBookPage->setModel(walletModel->getAddressTableModel());
        receiveCoinsPage->setModel(walletModel->getAddressTableModel());
        sendCoinsPage->setModel(walletModel);
378
        messagePage->setModel(walletModel);
379

380
381
        setEncryptionStatus(walletModel->getEncryptionStatus());
        connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
382

383
384
385
        // Balloon popup for new transaction
        connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
                this, SLOT(incomingTransaction(QModelIndex,int,int)));
386

387
388
389
        // Ask for passphrase if needed
        connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
    }
390
391
}

Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
392
393
void BitcoinGUI::createTrayIcon()
{
394
395
    QMenu *trayIconMenu;
#ifndef Q_WS_MAC
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
396
    trayIcon = new QSystemTrayIcon(this);
397
    trayIconMenu = new QMenu(this);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
398
    trayIcon->setContextMenu(trayIconMenu);
399
    trayIcon->setToolTip(tr("Bitcoin client"));
400
    trayIcon->setIcon(QIcon(":/icons/toolbar"));
401
402
    connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
403
    trayIcon->show();
404
405
406
407
408
409
410
#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
411
    trayIconMenu->addAction(toggleHideAction);
412
    trayIconMenu->addAction(openRPCConsoleAction);
413
    trayIconMenu->addSeparator();
414
    trayIconMenu->addAction(messageAction);
415
    trayIconMenu->addAction(verifyMessageAction);
416
417
418
#ifndef FIRST_CLASS_MESSAGING
    trayIconMenu->addSeparator();
#endif
419
    trayIconMenu->addAction(sendCoinsAction);
420
    trayIconMenu->addAction(receiveCoinsAction);
421
422
423
424
425
426
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(optionsAction);
#ifndef Q_WS_MAC // This is built-in on Mac
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(quitAction);
#endif
427

428
    notificator = new Notificator(qApp->applicationName(), trayIcon);
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
429
430
}

431
#ifndef Q_WS_MAC
432
433
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
434
    if(reason == QSystemTrayIcon::Trigger)
435
    {
436
        // Click on system tray icon triggers "show/hide Bitcoin"
437
        toggleHideAction->trigger();
438
439
    }
}
440
#endif
441

442
443
void BitcoinGUI::optionsClicked()
{
444
445
    if(!clientModel || !clientModel->getOptionsModel())
        return;
446
    OptionsDialog dlg;
447
    dlg.setModel(clientModel->getOptionsModel());
448
    dlg.exec();
449
450
}

451
void BitcoinGUI::aboutClicked()
452
{
453
    AboutDialog dlg;
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
454
    dlg.setModel(clientModel);
455
    dlg.exec();
456
457
}

458
459
void BitcoinGUI::setNumConnections(int count)
{
460
461
462
    QString icon;
    switch(count)
    {
463
464
465
466
467
    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;
468
    }
469
    labelConnectionsIcon->setPixmap(QIcon(icon).pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
470
    labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count));
471
472
}

473
void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
474
{
475
476
477
478
479
480
    // don't show / hide progressBar and it's label if we have no connection(s) to the network
    if (!clientModel || clientModel->getNumConnections() == 0)
    {
        progressBarLabel->setVisible(false);
        progressBar->setVisible(false);

481
        return;
482
483
    }

484
485
    QString tooltip;

486
    if(count < nTotalBlocks)
487
    {
488
        int nRemainingBlocks = nTotalBlocks - count;
489
        float nPercentageDone = count / (nTotalBlocks * 0.01f);
490

491
492
493
        if (clientModel->getStatusBarWarnings() == "")
        {
            progressBarLabel->setText(tr("Synchronizing with network..."));
494
            progressBarLabel->setVisible(true);
495
            progressBar->setFormat(tr("~%n block(s) remaining", "", nRemainingBlocks));
496
            progressBar->setMaximum(nTotalBlocks);
497
            progressBar->setValue(count);
498
            progressBar->setVisible(true);
499
500
501
502
503
504
505
        }
        else
        {
            progressBarLabel->setText(clientModel->getStatusBarWarnings());
            progressBarLabel->setVisible(true);
            progressBar->setVisible(false);
        }
506
        tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2);
507
508
509
    }
    else
    {
510
511
512
513
514
515
516
        if (clientModel->getStatusBarWarnings() == "")
            progressBarLabel->setVisible(false);
        else
        {
            progressBarLabel->setText(clientModel->getStatusBarWarnings());
            progressBarLabel->setVisible(true);
        }
517
        progressBar->setVisible(false);
518
        tooltip = tr("Downloaded %1 blocks of transaction history.").arg(count);
519
520
    }

521
522
523
524
525
    QDateTime now = QDateTime::currentDateTime();
    QDateTime lastBlockDate = clientModel->getLastBlockDate();
    int secs = lastBlockDate.secsTo(now);
    QString text;

526
    // Represent time from last generated block in human readable text
527
528
529
530
531
    if(secs <= 0)
    {
        // Fully up to date. Leave text empty.
    }
    else if(secs < 60)
532
    {
533
        text = tr("%n second(s) ago","",secs);
534
535
536
537
538
539
540
541
542
543
544
545
546
    }
    else if(secs < 60*60)
    {
        text = tr("%n minute(s) ago","",secs/60);
    }
    else if(secs < 24*60*60)
    {
        text = tr("%n hour(s) ago","",secs/(60*60));
    }
    else
    {
        text = tr("%n day(s) ago","",secs/(60*60*24));
    }
547

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

        overviewPage->showOutOfSyncWarning(false);
555
    }
556
557
    else
    {
558
        tooltip = tr("Catching up...") + QString("<br>") + tooltip;
559
560
        labelBlocksIcon->setMovie(syncIconMovie);
        syncIconMovie->start();
561
562

        overviewPage->showOutOfSyncWarning(true);
563
    }
564

565
566
    if(!text.isEmpty())
    {
567
        tooltip += QString("<br>");
568
569
        tooltip += tr("Last received block was generated %1.").arg(text);
    }
570

571
572
573
    // Don't word-wrap this (fixed-width) tooltip
    tooltip = QString("<nobr>") + tooltip + QString("</nobr>");

574
    labelBlocksIcon->setToolTip(tooltip);
575
576
    progressBarLabel->setToolTip(tooltip);
    progressBar->setToolTip(tooltip);
577
578
}

579
void BitcoinGUI::error(const QString &title, const QString &message, bool modal)
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
580
{
581
    // Report errors from network/worker thread
582
583
584
585
586
587
    if(modal)
    {
        QMessageBox::critical(this, title, message, QMessageBox::Ok, QMessageBox::Ok);
    } else {
        notificator->notify(Notificator::Critical, title, message);
    }
Wladimir J. van der Laan's avatar
Wladimir J. van der Laan committed
588
}
589
590
591

void BitcoinGUI::changeEvent(QEvent *e)
{
592
    QMainWindow::changeEvent(e);
593
#ifndef Q_WS_MAC // Ignored on Mac
594
    if(e->type() == QEvent::WindowStateChange)
595
    {
596
        if(clientModel && clientModel->getOptionsModel()->getMinimizeToTray())
597
        {
598
            QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent*>(e);
599
            if(!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized())
600
            {
601
602
                QTimer::singleShot(0, this, SLOT(hide()));
                e->ignore();
603
604
605
            }
        }
    }
606
#endif
607
608
609
610
}

void BitcoinGUI::closeEvent(QCloseEvent *event)
{
611
    if(clientModel)
612
    {
613
614
615
616
617
618
#ifndef Q_WS_MAC // Ignored on Mac
        if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
           !clientModel->getOptionsModel()->getMinimizeOnClose())
        {
            qApp->quit();
        }
619
#endif
620
    }
621
622
    QMainWindow::closeEvent(event);
}
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
623
624
625
626
627
628

void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
{
    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.  "
629
630
          "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
631
    QMessageBox::StandardButton retval = QMessageBox::question(
632
          this, tr("Confirm transaction fee"), strMessage,
Wladimir J. van der Laan's avatar
ask fee    
Wladimir J. van der Laan committed
633
634
635
          QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes);
    *payFee = (retval == QMessageBox::Yes);
}
636

637
638
void BitcoinGUI::incomingTransaction(const QModelIndex & parent, int start, int end)
{
639
640
    if(!walletModel || !clientModel)
        return;
641
    TransactionTableModel *ttm = walletModel->getTransactionTableModel();
642
    qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
643
                    .data(Qt::EditRole).toULongLong();
644
    if(!clientModel->inInitialBlockDownload())
645
    {
646
        // On new transaction, make an info balloon
647
        // Unless the initial block download is in progress, to prevent balloon-spam
648
649
        QString date = ttm->index(start, TransactionTableModel::Date, parent)
                        .data().toString();
650
651
652
        QString type = ttm->index(start, TransactionTableModel::Type, parent)
                        .data().toString();
        QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
653
                        .data().toString();
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
        QIcon icon = qvariant_cast<QIcon>(ttm->index(start,
                            TransactionTableModel::ToAddress, parent)
                        .data(Qt::DecorationRole));

        notificator->notify(Notificator::Information,
                            (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), icon);
669
670
    }
}
671

672
void BitcoinGUI::gotoOverviewPage()
673
674
675
{
    overviewAction->setChecked(true);
    centralWidget->setCurrentWidget(overviewPage);
676

677
    exportAction->setEnabled(false);
678
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
679
680
}

681
void BitcoinGUI::gotoHistoryPage()
682
683
684
{
    historyAction->setChecked(true);
    centralWidget->setCurrentWidget(transactionsPage);
685

686
    exportAction->setEnabled(true);
687
688
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
689
690
}

691
692
693
694
void BitcoinGUI::gotoAddressBookPage()
{
    addressBookAction->setChecked(true);
    centralWidget->setCurrentWidget(addressBookPage);
695
696
697
698

    exportAction->setEnabled(true);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
699
700
701
702
703
704
}

void BitcoinGUI::gotoReceiveCoinsPage()
{
    receiveCoinsAction->setChecked(true);
    centralWidget->setCurrentWidget(receiveCoinsPage);
705
706
707
708

    exportAction->setEnabled(true);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
    connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
709
710
711
712
713
714
715
}

void BitcoinGUI::gotoSendCoinsPage()
{
    sendCoinsAction->setChecked(true);
    centralWidget->setCurrentWidget(sendCoinsPage);

716
717
    exportAction->setEnabled(false);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
718
}
719

720
void BitcoinGUI::gotoMessagePage(QString addr)
721
{
722
723
724
    if(!addr.isEmpty())
        messagePage->setAddress(addr);

725
726
727
728
729
730
731
732
733
734
735
#ifdef FIRST_CLASS_MESSAGING
    messageAction->setChecked(true);
    centralWidget->setCurrentWidget(messagePage);

    exportAction->setEnabled(false);
    disconnect(exportAction, SIGNAL(triggered()), 0, 0);
#else
    messagePage->show();
#endif
}

736
737
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
{
738
    // Accept only URIs
739
740
741
742
743
744
745
746
    if(event->mimeData()->hasUrls())
        event->acceptProposedAction();
}

void BitcoinGUI::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls())
    {
747
        int nValidUrisFound = 0;
748
749
        QList<QUrl> uris = event->mimeData()->urls();
        foreach(const QUrl &uri, uris)
750
        {
751
752
            if (sendCoinsPage->handleURI(uri.toString()))
                nValidUrisFound++;
753
        }
754
755
756
757
758
759

        // if valid URIs were found
        if (nValidUrisFound)
            gotoSendCoinsPage();
        else
            notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."));
760
761
762
763
764
    }

    event->acceptProposedAction();
}

765
void BitcoinGUI::handleURI(QString strURI)
766
{
767
768
769
770
771
772
773
774
    // URI has to be valid
    if (sendCoinsPage->handleURI(strURI))
    {
        showNormalIfMinimized();
        gotoSendCoinsPage();
    }
    else
        notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."));
775
776
}

777
778
779
780
781
782
void BitcoinGUI::setEncryptionStatus(int status)
{
    switch(status)
    {
    case WalletModel::Unencrypted:
        labelEncryptionIcon->hide();
783
784
785
        encryptWalletAction->setChecked(false);
        changePassphraseAction->setEnabled(false);
        encryptWalletAction->setEnabled(true);
786
787
788
789
790
        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>"));
791
792
793
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
794
795
796
797
798
        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>"));
799
800
801
        encryptWalletAction->setChecked(true);
        changePassphraseAction->setEnabled(true);
        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
802
803
804
        break;
    }
}
805
806
807

void BitcoinGUI::encryptWallet(bool status)
{
808
809
    if(!walletModel)
        return;
810
811
812
813
814
815
816
817
    AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
                                     AskPassphraseDialog::Decrypt, this);
    dlg.setModel(walletModel);
    dlg.exec();

    setEncryptionStatus(walletModel->getEncryptionStatus());
}

sje397's avatar
sje397 committed
818
819
820
821
822
823
824
825
826
827
828
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)) {
            QMessageBox::warning(this, tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."));
        }
    }
}

829
830
831
832
833
834
835
void BitcoinGUI::changePassphrase()
{
    AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
    dlg.setModel(walletModel);
    dlg.exec();
}

836
837
void BitcoinGUI::verifyMessage()
{
838
    VerifyMessageDialog *dlg = new VerifyMessageDialog(this);
839
840
841
842
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->show();
}

843
844
void BitcoinGUI::unlockWallet()
{
845
846
    if(!walletModel)
        return;
847
    // Unlock wallet when requested by wallet model
848
849
850
851
852
853
854
    if(walletModel->getEncryptionStatus() == WalletModel::Locked)
    {
        AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
        dlg.setModel(walletModel);
        dlg.exec();
    }
}
855

856
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
857
{
858
859
860
    // activateWindow() (sometimes) helps with keyboard focus on Windows
    if (isHidden())
    {
861
        show();
862
863
864
865
        activateWindow();
    }
    else if (isMinimized())
    {
866
        showNormal();
867
868
869
870
871
872
873
874
875
876
877
878
879
880
        activateWindow();
    }
    else if (GUIUtil::isObscured(this))
    {
        raise();
        activateWindow();
    }
    else if(fToggleHidden)
        hide();
}

void BitcoinGUI::toggleHidden()
{
    showNormalIfMinimized(true);
881
}