update to 0.4 preview
[novacoin.git] / src / qt / bitcoingui.cpp
1 /*
2  * Qt4 bitcoin GUI.
3  *
4  * W.J. van der Laan 2011-2012
5  * The Bitcoin Developers 2011-2012
6  */
7 #include "bitcoingui.h"
8 #include "transactiontablemodel.h"
9 #include "addressbookpage.h"
10 #include "sendcoinsdialog.h"
11 #include "signverifymessagedialog.h"
12 #include "optionsdialog.h"
13 #include "aboutdialog.h"
14 #include "clientmodel.h"
15 #include "walletmodel.h"
16 #include "editaddressdialog.h"
17 #include "optionsmodel.h"
18 #include "transactiondescdialog.h"
19 #include "addresstablemodel.h"
20 #include "transactionview.h"
21 #include "overviewpage.h"
22 #include "bitcoinunits.h"
23 #include "guiconstants.h"
24 #include "askpassphrasedialog.h"
25 #include "notificator.h"
26 #include "guiutil.h"
27 #include "rpcconsole.h"
28
29 #ifdef Q_OS_MAC
30 #include "macdockiconhandler.h"
31 #endif
32
33 #include <QApplication>
34 #include <QMainWindow>
35 #include <QMenuBar>
36 #include <QMenu>
37 #include <QIcon>
38 #include <QTabWidget>
39 #include <QVBoxLayout>
40 #include <QToolBar>
41 #include <QStatusBar>
42 #include <QLabel>
43 #include <QLineEdit>
44 #include <QPushButton>
45 #include <QLocale>
46 #include <QMessageBox>
47 #include <QProgressBar>
48 #include <QStackedWidget>
49 #include <QDateTime>
50 #include <QMovie>
51 #include <QFileDialog>
52 #include <QDesktopServices>
53 #include <QTimer>
54 #include <QDragEnterEvent>
55 #include <QUrl>
56 #include <QStyle>
57
58 #include <iostream>
59
60 BitcoinGUI::BitcoinGUI(QWidget *parent):
61     QMainWindow(parent),
62     clientModel(0),
63     walletModel(0),
64     encryptWalletAction(0),
65     changePassphraseAction(0),
66     aboutQtAction(0),
67     trayIcon(0),
68     notificator(0),
69     rpcConsole(0)
70 {
71     resize(850, 550);
72     setWindowTitle(tr("NovaCoin") + " - " + tr("Wallet"));
73 #ifndef Q_OS_MAC
74     qApp->setWindowIcon(QIcon(":icons/bitcoin"));
75     setWindowIcon(QIcon(":icons/bitcoin"));
76 #else
77     setUnifiedTitleAndToolBarOnMac(true);
78     QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
79 #endif
80     // Accept D&D of URIs
81     setAcceptDrops(true);
82
83     // Create actions for the toolbar, menu bar and tray/dock icon
84     createActions();
85
86     // Create application menu bar
87     createMenuBar();
88
89     // Create the toolbars
90     createToolBars();
91
92     // Create the tray icon (or setup the dock icon)
93     createTrayIcon();
94
95     // Create tabs
96     overviewPage = new OverviewPage();
97
98     transactionsPage = new QWidget(this);
99     QVBoxLayout *vbox = new QVBoxLayout();
100     transactionView = new TransactionView(this);
101     vbox->addWidget(transactionView);
102     transactionsPage->setLayout(vbox);
103
104     addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
105
106     receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
107
108     sendCoinsPage = new SendCoinsDialog(this);
109
110     signVerifyMessageDialog = new SignVerifyMessageDialog(this);
111
112     centralWidget = new QStackedWidget(this);
113     centralWidget->addWidget(overviewPage);
114     centralWidget->addWidget(transactionsPage);
115     centralWidget->addWidget(addressBookPage);
116     centralWidget->addWidget(receiveCoinsPage);
117     centralWidget->addWidget(sendCoinsPage);
118     setCentralWidget(centralWidget);
119
120     // Create status bar
121     statusBar();
122
123     // Status bar notification icons
124     QFrame *frameBlocks = new QFrame();
125     frameBlocks->setContentsMargins(0,0,0,0);
126     frameBlocks->setMinimumWidth(56);
127     frameBlocks->setMaximumWidth(56);
128     QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
129     frameBlocksLayout->setContentsMargins(3,0,3,0);
130     frameBlocksLayout->setSpacing(3);
131     labelEncryptionIcon = new QLabel();
132     labelConnectionsIcon = new QLabel();
133     labelBlocksIcon = new QLabel();
134     frameBlocksLayout->addStretch();
135     frameBlocksLayout->addWidget(labelEncryptionIcon);
136     frameBlocksLayout->addStretch();
137     frameBlocksLayout->addWidget(labelConnectionsIcon);
138     frameBlocksLayout->addStretch();
139     frameBlocksLayout->addWidget(labelBlocksIcon);
140     frameBlocksLayout->addStretch();
141
142     // Progress bar and label for blocks download
143     progressBarLabel = new QLabel();
144     progressBarLabel->setVisible(false);
145     progressBar = new QProgressBar();
146     progressBar->setAlignment(Qt::AlignCenter);
147     progressBar->setVisible(false);
148
149     // Override style sheet for progress bar for styles that have a segmented progress bar,
150     // as they make the text unreadable (workaround for issue #1071)
151     // See https://qt-project.org/doc/qt-4.8/gallery.html
152     QString curStyle = qApp->style()->metaObject()->className();
153     if(curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle")
154     {
155         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; }");
156     }
157
158     statusBar()->addWidget(progressBarLabel);
159     statusBar()->addWidget(progressBar);
160     statusBar()->addPermanentWidget(frameBlocks);
161
162     syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this);
163
164     // Clicking on a transaction on the overview page simply sends you to transaction history page
165     connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
166     connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
167
168     // Double-clicking on a transaction on the transaction history page shows details
169     connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
170
171     rpcConsole = new RPCConsole(this);
172     connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show()));
173
174     // Clicking on "Verify Message" in the address book sends you to the verify message tab
175     connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString)));
176     // Clicking on "Sign Message" in the receive coins page sends you to the sign message tab
177     connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString)));
178
179     gotoOverviewPage();
180 }
181
182 BitcoinGUI::~BitcoinGUI()
183 {
184     if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
185         trayIcon->hide();
186 #ifdef Q_OS_MAC
187     delete appMenuBar;
188 #endif
189 }
190
191 void BitcoinGUI::createActions()
192 {
193     QActionGroup *tabGroup = new QActionGroup(this);
194
195     overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
196     overviewAction->setToolTip(tr("Show general overview of wallet"));
197     overviewAction->setCheckable(true);
198     overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
199     tabGroup->addAction(overviewAction);
200
201     sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
202     sendCoinsAction->setToolTip(tr("Send coins to a NovaCoin address"));
203     sendCoinsAction->setCheckable(true);
204     sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
205     tabGroup->addAction(sendCoinsAction);
206
207     receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
208     receiveCoinsAction->setToolTip(tr("Show the list of addresses for receiving payments"));
209     receiveCoinsAction->setCheckable(true);
210     receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
211     tabGroup->addAction(receiveCoinsAction);
212
213     historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
214     historyAction->setToolTip(tr("Browse transaction history"));
215     historyAction->setCheckable(true);
216     historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
217     tabGroup->addAction(historyAction);
218
219     addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
220     addressBookAction->setToolTip(tr("Edit the list of stored addresses and labels"));
221     addressBookAction->setCheckable(true);
222     addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5));
223     tabGroup->addAction(addressBookAction);
224
225     connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
226     connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
227     connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
228     connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
229     connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
230     connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
231     connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
232     connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
233     connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
234     connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
235
236     quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
237     quitAction->setToolTip(tr("Quit application"));
238     quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
239     quitAction->setMenuRole(QAction::QuitRole);
240     aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About NovaCoin"), this);
241     aboutAction->setToolTip(tr("Show information about NovaCoin"));
242     aboutAction->setMenuRole(QAction::AboutRole);
243     aboutQtAction = new QAction(QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this);
244     aboutQtAction->setToolTip(tr("Show information about Qt"));
245     aboutQtAction->setMenuRole(QAction::AboutQtRole);
246     optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
247     optionsAction->setToolTip(tr("Modify configuration options for NovaCoin"));
248     optionsAction->setMenuRole(QAction::PreferencesRole);
249     toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("&Show / Hide"), this);
250     encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this);
251     encryptWalletAction->setToolTip(tr("Encrypt or decrypt wallet"));
252     encryptWalletAction->setCheckable(true);
253     backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this);
254     backupWalletAction->setToolTip(tr("Backup wallet to another location"));
255     changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this);
256     changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption"));
257     signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this);
258     verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this);
259
260     exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
261     exportAction->setToolTip(tr("Export the data in the current tab to a file"));
262     openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this);
263     openRPCConsoleAction->setToolTip(tr("Open debugging and diagnostic console"));
264
265     connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
266     connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
267     connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
268     connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
269     connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden()));
270     connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
271     connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
272     connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
273     connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab()));
274     connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab()));
275 }
276
277 void BitcoinGUI::createMenuBar()
278 {
279 #ifdef Q_OS_MAC
280     // Create a decoupled menu bar on Mac which stays even if the window is closed
281     appMenuBar = new QMenuBar();
282 #else
283     // Get the main window's menu bar on other platforms
284     appMenuBar = menuBar();
285 #endif
286
287     // Configure the menus
288     QMenu *file = appMenuBar->addMenu(tr("&File"));
289     file->addAction(backupWalletAction);
290     file->addAction(exportAction);
291     file->addAction(signMessageAction);
292     file->addAction(verifyMessageAction);
293     file->addSeparator();
294     file->addAction(quitAction);
295
296     QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
297     settings->addAction(encryptWalletAction);
298     settings->addAction(changePassphraseAction);
299     settings->addSeparator();
300     settings->addAction(optionsAction);
301
302     QMenu *help = appMenuBar->addMenu(tr("&Help"));
303     help->addAction(openRPCConsoleAction);
304     help->addSeparator();
305     help->addAction(aboutAction);
306     help->addAction(aboutQtAction);
307 }
308
309 void BitcoinGUI::createToolBars()
310 {
311     QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
312     toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
313     toolbar->addAction(overviewAction);
314     toolbar->addAction(sendCoinsAction);
315     toolbar->addAction(receiveCoinsAction);
316     toolbar->addAction(historyAction);
317     toolbar->addAction(addressBookAction);
318
319     QToolBar *toolbar2 = addToolBar(tr("Actions toolbar"));
320     toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
321     toolbar2->addAction(exportAction);
322 }
323
324 void BitcoinGUI::setClientModel(ClientModel *clientModel)
325 {
326     this->clientModel = clientModel;
327     if(clientModel)
328     {
329         // Replace some strings and icons, when using the testnet
330         if(clientModel->isTestNet())
331         {
332             setWindowTitle(windowTitle() + QString(" ") + tr("[testnet]"));
333 #ifndef Q_OS_MAC
334             qApp->setWindowIcon(QIcon(":icons/bitcoin_testnet"));
335             setWindowIcon(QIcon(":icons/bitcoin_testnet"));
336 #else
337             MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet"));
338 #endif
339             if(trayIcon)
340             {
341                 trayIcon->setToolTip(tr("NovaCoin client") + QString(" ") + tr("[testnet]"));
342                 trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
343                 toggleHideAction->setIcon(QIcon(":/icons/toolbar_testnet"));
344             }
345
346             aboutAction->setIcon(QIcon(":/icons/toolbar_testnet"));
347         }
348
349         // Keep up to date with client
350         setNumConnections(clientModel->getNumConnections());
351         connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
352
353         setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers());
354         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         addressBookPage->setOptionsModel(clientModel->getOptionsModel());
361         receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
362     }
363 }
364
365 void BitcoinGUI::setWalletModel(WalletModel *walletModel)
366 {
367     this->walletModel = walletModel;
368     if(walletModel)
369     {
370         // Report errors from wallet thread
371         connect(walletModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool)));
372
373         // Put transaction list in tabs
374         transactionView->setModel(walletModel);
375
376         overviewPage->setModel(walletModel);
377         addressBookPage->setModel(walletModel->getAddressTableModel());
378         receiveCoinsPage->setModel(walletModel->getAddressTableModel());
379         sendCoinsPage->setModel(walletModel);
380         signVerifyMessageDialog->setModel(walletModel);
381
382         setEncryptionStatus(walletModel->getEncryptionStatus());
383         connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
384
385         // Balloon pop-up for new transaction
386         connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
387                 this, SLOT(incomingTransaction(QModelIndex,int,int)));
388
389         // Ask for passphrase if needed
390         connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
391     }
392 }
393
394 void BitcoinGUI::createTrayIcon()
395 {
396     QMenu *trayIconMenu;
397 #ifndef Q_OS_MAC
398     trayIcon = new QSystemTrayIcon(this);
399     trayIconMenu = new QMenu(this);
400     trayIcon->setContextMenu(trayIconMenu);
401     trayIcon->setToolTip(tr("NovaCoin client"));
402     trayIcon->setIcon(QIcon(":/icons/toolbar"));
403     connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
404             this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
405     trayIcon->show();
406 #else
407     // Note: On Mac, the dock icon is used to provide the tray's functionality.
408     MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
409     trayIconMenu = dockIconHandler->dockMenu();
410 #endif
411
412     // Configuration of the tray icon (or dock icon) icon menu
413     trayIconMenu->addAction(toggleHideAction);
414     trayIconMenu->addSeparator();
415     trayIconMenu->addAction(sendCoinsAction);
416     trayIconMenu->addAction(receiveCoinsAction);
417     trayIconMenu->addSeparator();
418     trayIconMenu->addAction(signMessageAction);
419     trayIconMenu->addAction(verifyMessageAction);
420     trayIconMenu->addSeparator();
421     trayIconMenu->addAction(optionsAction);
422     trayIconMenu->addAction(openRPCConsoleAction);
423 #ifndef Q_OS_MAC // This is built-in on Mac
424     trayIconMenu->addSeparator();
425     trayIconMenu->addAction(quitAction);
426 #endif
427
428     notificator = new Notificator(qApp->applicationName(), trayIcon);
429 }
430
431 #ifndef Q_OS_MAC
432 void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
433 {
434     if(reason == QSystemTrayIcon::Trigger)
435     {
436         // Click on system tray icon triggers show/hide of the main window
437         toggleHideAction->trigger();
438     }
439 }
440 #endif
441
442 void BitcoinGUI::optionsClicked()
443 {
444     if(!clientModel || !clientModel->getOptionsModel())
445         return;
446     OptionsDialog dlg;
447     dlg.setModel(clientModel->getOptionsModel());
448     dlg.exec();
449 }
450
451 void BitcoinGUI::aboutClicked()
452 {
453     AboutDialog dlg;
454     dlg.setModel(clientModel);
455     dlg.exec();
456 }
457
458 void BitcoinGUI::setNumConnections(int count)
459 {
460     QString icon;
461     switch(count)
462     {
463     case 0: icon = ":/icons/connect_0"; break;
464     case 1: case 2: case 3: icon = ":/icons/connect_1"; break;
465     case 4: case 5: case 6: icon = ":/icons/connect_2"; break;
466     case 7: case 8: case 9: icon = ":/icons/connect_3"; break;
467     default: icon = ":/icons/connect_4"; break;
468     }
469     labelConnectionsIcon->setPixmap(QIcon(icon).pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
470     labelConnectionsIcon->setToolTip(tr("%n active connection(s) to NovaCoin network", "", count));
471 }
472
473 void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
474 {
475     // don't show / hide progress bar and its label if we have no connection to the network
476     if (!clientModel || clientModel->getNumConnections() == 0)
477     {
478         progressBarLabel->setVisible(false);
479         progressBar->setVisible(false);
480
481         return;
482     }
483
484     QString strStatusBarWarnings = clientModel->getStatusBarWarnings();
485     QString tooltip;
486
487     if(count < nTotalBlocks)
488     {
489         int nRemainingBlocks = nTotalBlocks - count;
490         float nPercentageDone = count / (nTotalBlocks * 0.01f);
491
492         if (strStatusBarWarnings.isEmpty())
493         {
494             progressBarLabel->setText(tr("Synchronizing with network..."));
495             progressBarLabel->setVisible(true);
496             progressBar->setFormat(tr("~%n block(s) remaining", "", nRemainingBlocks));
497             progressBar->setMaximum(nTotalBlocks);
498             progressBar->setValue(count);
499             progressBar->setVisible(true);
500         }
501
502         tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2);
503     }
504     else
505     {
506         if (strStatusBarWarnings.isEmpty())
507             progressBarLabel->setVisible(false);
508
509         progressBar->setVisible(false);
510         tooltip = tr("Downloaded %1 blocks of transaction history.").arg(count);
511     }
512
513     // Override progressBarLabel text and hide progress bar, when we have warnings to display
514     if (!strStatusBarWarnings.isEmpty())
515     {
516         progressBarLabel->setText(strStatusBarWarnings);
517         progressBarLabel->setVisible(true);
518         progressBar->setVisible(false);
519     }
520
521     QDateTime lastBlockDate = clientModel->getLastBlockDate();
522     int secs = lastBlockDate.secsTo(QDateTime::currentDateTime());
523     QString text;
524
525     // Represent time from last generated block in human readable text
526     if(secs <= 0)
527     {
528         // Fully up to date. Leave text empty.
529     }
530     else if(secs < 60)
531     {
532         text = tr("%n second(s) ago","",secs);
533     }
534     else if(secs < 60*60)
535     {
536         text = tr("%n minute(s) ago","",secs/60);
537     }
538     else if(secs < 24*60*60)
539     {
540         text = tr("%n hour(s) ago","",secs/(60*60));
541     }
542     else
543     {
544         text = tr("%n day(s) ago","",secs/(60*60*24));
545     }
546
547     // Set icon state: spinning if catching up, tick otherwise
548     if(secs < 90*60 && count >= nTotalBlocks)
549     {
550         tooltip = tr("Up to date") + QString(".<br>") + tooltip;
551         labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
552
553         overviewPage->showOutOfSyncWarning(false);
554     }
555     else
556     {
557         tooltip = tr("Catching up...") + QString("<br>") + tooltip;
558         labelBlocksIcon->setMovie(syncIconMovie);
559         syncIconMovie->start();
560
561         overviewPage->showOutOfSyncWarning(true);
562     }
563
564     if(!text.isEmpty())
565     {
566         tooltip += QString("<br>");
567         tooltip += tr("Last received block was generated %1.").arg(text);
568     }
569
570     // Don't word-wrap this (fixed-width) tooltip
571     tooltip = QString("<nobr>") + tooltip + QString("</nobr>");
572
573     labelBlocksIcon->setToolTip(tooltip);
574     progressBarLabel->setToolTip(tooltip);
575     progressBar->setToolTip(tooltip);
576 }
577
578 void BitcoinGUI::error(const QString &title, const QString &message, bool modal)
579 {
580     // Report errors from network/worker thread
581     if(modal)
582     {
583         QMessageBox::critical(this, title, message, QMessageBox::Ok, QMessageBox::Ok);
584     } else {
585         notificator->notify(Notificator::Critical, title, message);
586     }
587 }
588
589 void BitcoinGUI::changeEvent(QEvent *e)
590 {
591     QMainWindow::changeEvent(e);
592 #ifndef Q_OS_MAC // Ignored on Mac
593     if(e->type() == QEvent::WindowStateChange)
594     {
595         if(clientModel && clientModel->getOptionsModel()->getMinimizeToTray())
596         {
597             QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent*>(e);
598             if(!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized())
599             {
600                 QTimer::singleShot(0, this, SLOT(hide()));
601                 e->ignore();
602             }
603         }
604     }
605 #endif
606 }
607
608 void BitcoinGUI::closeEvent(QCloseEvent *event)
609 {
610     if(clientModel)
611     {
612 #ifndef Q_OS_MAC // Ignored on Mac
613         if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
614            !clientModel->getOptionsModel()->getMinimizeOnClose())
615         {
616             qApp->quit();
617         }
618 #endif
619     }
620     QMainWindow::closeEvent(event);
621 }
622
623 void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
624 {
625     QString strMessage =
626         tr("This transaction is over the size limit.  You can still send it for a fee of %1, "
627           "which goes to the nodes that process your transaction and helps to support the network.  "
628           "Do you want to pay the fee?").arg(
629                 BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nFeeRequired));
630     QMessageBox::StandardButton retval = QMessageBox::question(
631           this, tr("Confirm transaction fee"), strMessage,
632           QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes);
633     *payFee = (retval == QMessageBox::Yes);
634 }
635
636 void BitcoinGUI::incomingTransaction(const QModelIndex & parent, int start, int end)
637 {
638     if(!walletModel || !clientModel)
639         return;
640     TransactionTableModel *ttm = walletModel->getTransactionTableModel();
641     qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
642                     .data(Qt::EditRole).toULongLong();
643     if(!clientModel->inInitialBlockDownload())
644     {
645         // On new transaction, make an info balloon
646         // Unless the initial block download is in progress, to prevent balloon-spam
647         QString date = ttm->index(start, TransactionTableModel::Date, parent)
648                         .data().toString();
649         QString type = ttm->index(start, TransactionTableModel::Type, parent)
650                         .data().toString();
651         QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
652                         .data().toString();
653         QIcon icon = qvariant_cast<QIcon>(ttm->index(start,
654                             TransactionTableModel::ToAddress, parent)
655                         .data(Qt::DecorationRole));
656
657         notificator->notify(Notificator::Information,
658                             (amount)<0 ? tr("Sent transaction") :
659                                          tr("Incoming transaction"),
660                               tr("Date: %1\n"
661                                  "Amount: %2\n"
662                                  "Type: %3\n"
663                                  "Address: %4\n")
664                               .arg(date)
665                               .arg(BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), amount, true))
666                               .arg(type)
667                               .arg(address), icon);
668     }
669 }
670
671 void BitcoinGUI::gotoOverviewPage()
672 {
673     overviewAction->setChecked(true);
674     centralWidget->setCurrentWidget(overviewPage);
675
676     exportAction->setEnabled(false);
677     disconnect(exportAction, SIGNAL(triggered()), 0, 0);
678 }
679
680 void BitcoinGUI::gotoHistoryPage()
681 {
682     historyAction->setChecked(true);
683     centralWidget->setCurrentWidget(transactionsPage);
684
685     exportAction->setEnabled(true);
686     disconnect(exportAction, SIGNAL(triggered()), 0, 0);
687     connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
688 }
689
690 void BitcoinGUI::gotoAddressBookPage()
691 {
692     addressBookAction->setChecked(true);
693     centralWidget->setCurrentWidget(addressBookPage);
694
695     exportAction->setEnabled(true);
696     disconnect(exportAction, SIGNAL(triggered()), 0, 0);
697     connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
698 }
699
700 void BitcoinGUI::gotoReceiveCoinsPage()
701 {
702     receiveCoinsAction->setChecked(true);
703     centralWidget->setCurrentWidget(receiveCoinsPage);
704
705     exportAction->setEnabled(true);
706     disconnect(exportAction, SIGNAL(triggered()), 0, 0);
707     connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
708 }
709
710 void BitcoinGUI::gotoSendCoinsPage()
711 {
712     sendCoinsAction->setChecked(true);
713     centralWidget->setCurrentWidget(sendCoinsPage);
714
715     exportAction->setEnabled(false);
716     disconnect(exportAction, SIGNAL(triggered()), 0, 0);
717 }
718
719 void BitcoinGUI::gotoSignMessageTab(QString addr)
720 {
721     // call show() in showTab_SM()
722     signVerifyMessageDialog->showTab_SM(true);
723
724     if(!addr.isEmpty())
725         signVerifyMessageDialog->setAddress_SM(addr);
726 }
727
728 void BitcoinGUI::gotoVerifyMessageTab(QString addr)
729 {
730     // call show() in showTab_VM()
731     signVerifyMessageDialog->showTab_VM(true);
732
733     if(!addr.isEmpty())
734         signVerifyMessageDialog->setAddress_VM(addr);
735 }
736
737 void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
738 {
739     // Accept only URIs
740     if(event->mimeData()->hasUrls())
741         event->acceptProposedAction();
742 }
743
744 void BitcoinGUI::dropEvent(QDropEvent *event)
745 {
746     if(event->mimeData()->hasUrls())
747     {
748         int nValidUrisFound = 0;
749         QList<QUrl> uris = event->mimeData()->urls();
750         foreach(const QUrl &uri, uris)
751         {
752             if (sendCoinsPage->handleURI(uri.toString()))
753                 nValidUrisFound++;
754         }
755
756         // if valid URIs were found
757         if (nValidUrisFound)
758             gotoSendCoinsPage();
759         else
760             notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters."));
761     }
762
763     event->acceptProposedAction();
764 }
765
766 void BitcoinGUI::handleURI(QString strURI)
767 {
768     // URI has to be valid
769     if (sendCoinsPage->handleURI(strURI))
770     {
771         showNormalIfMinimized();
772         gotoSendCoinsPage();
773     }
774     else
775         notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters."));
776 }
777
778 void BitcoinGUI::setEncryptionStatus(int status)
779 {
780     switch(status)
781     {
782     case WalletModel::Unencrypted:
783         labelEncryptionIcon->hide();
784         encryptWalletAction->setChecked(false);
785         changePassphraseAction->setEnabled(false);
786         encryptWalletAction->setEnabled(true);
787         break;
788     case WalletModel::Unlocked:
789         labelEncryptionIcon->show();
790         labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_open").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
791         labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
792         encryptWalletAction->setChecked(true);
793         changePassphraseAction->setEnabled(true);
794         encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
795         break;
796     case WalletModel::Locked:
797         labelEncryptionIcon->show();
798         labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_closed").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
799         labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
800         encryptWalletAction->setChecked(true);
801         changePassphraseAction->setEnabled(true);
802         encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
803         break;
804     }
805 }
806
807 void BitcoinGUI::encryptWallet(bool status)
808 {
809     if(!walletModel)
810         return;
811     AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
812                                      AskPassphraseDialog::Decrypt, this);
813     dlg.setModel(walletModel);
814     dlg.exec();
815
816     setEncryptionStatus(walletModel->getEncryptionStatus());
817 }
818
819 void BitcoinGUI::backupWallet()
820 {
821     QString saveDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
822     QString filename = QFileDialog::getSaveFileName(this, tr("Backup Wallet"), saveDir, tr("Wallet Data (*.dat)"));
823     if(!filename.isEmpty()) {
824         if(!walletModel->backupWallet(filename)) {
825             QMessageBox::warning(this, tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."));
826         }
827     }
828 }
829
830 void BitcoinGUI::changePassphrase()
831 {
832     AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
833     dlg.setModel(walletModel);
834     dlg.exec();
835 }
836
837 void BitcoinGUI::unlockWallet()
838 {
839     if(!walletModel)
840         return;
841     // Unlock wallet when requested by wallet model
842     if(walletModel->getEncryptionStatus() == WalletModel::Locked)
843     {
844         AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
845         dlg.setModel(walletModel);
846         dlg.exec();
847     }
848 }
849
850 void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
851 {
852     // activateWindow() (sometimes) helps with keyboard focus on Windows
853     if (isHidden())
854     {
855         show();
856         activateWindow();
857     }
858     else if (isMinimized())
859     {
860         showNormal();
861         activateWindow();
862     }
863     else if (GUIUtil::isObscured(this))
864     {
865         raise();
866         activateWindow();
867     }
868     else if(fToggleHidden)
869         hide();
870 }
871
872 void BitcoinGUI::toggleHidden()
873 {
874     showNormalIfMinimized(true);
875 }