34da1350cdcec2863b010be124c52ecbd9094f93
[novacoin.git] / src / qt / bitcoingui.cpp
1 /*
2  * Qt4 bitcoin GUI.
3  *
4  * W.J. van der Laan 2011
5  */
6 #include "bitcoingui.h"
7 #include "transactiontablemodel.h"
8 #include "addressbookdialog.h"
9 #include "sendcoinsdialog.h"
10 #include "optionsdialog.h"
11 #include "aboutdialog.h"
12 #include "clientmodel.h"
13 #include "walletmodel.h"
14 #include "guiutil.h"
15 #include "editaddressdialog.h"
16 #include "optionsmodel.h"
17 #include "transactiondescdialog.h"
18 #include "addresstablemodel.h"
19 #include "transactionview.h"
20 #include "overviewpage.h"
21
22 #include <QApplication>
23 #include <QMainWindow>
24 #include <QMenuBar>
25 #include <QMenu>
26 #include <QIcon>
27 #include <QTabWidget>
28 #include <QVBoxLayout>
29 #include <QToolBar>
30 #include <QStatusBar>
31 #include <QLabel>
32 #include <QLineEdit>
33 #include <QPushButton>
34 #include <QLocale>
35 #include <QMessageBox>
36 #include <QProgressBar>
37 #include <QStackedWidget>
38
39 #include <QDebug>
40
41 #include <iostream>
42
43 BitcoinGUI::BitcoinGUI(QWidget *parent):
44     QMainWindow(parent),
45     clientModel(0),
46     walletModel(0),
47     trayIcon(0)
48 {
49     resize(850, 550);
50     setWindowTitle(tr("Bitcoin Wallet"));
51     setWindowIcon(QIcon(":icons/bitcoin"));
52
53     createActions();
54
55     // Menus
56     QMenu *file = menuBar()->addMenu("&File");
57     file->addAction(sendCoins);
58     file->addAction(receiveCoins);
59     file->addSeparator();
60     file->addAction(quit);
61     
62     QMenu *settings = menuBar()->addMenu("&Settings");
63     settings->addAction(options);
64
65     QMenu *help = menuBar()->addMenu("&Help");
66     help->addAction(about);
67     
68     // Toolbar
69     QToolBar *toolbar = addToolBar("Main toolbar");
70     toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
71     toolbar->addAction(overviewAction);
72     toolbar->addAction(historyAction);
73     toolbar->addSeparator();
74     toolbar->addAction(sendCoins);
75     toolbar->addAction(receiveCoins);
76     toolbar->addAction(addressbook);
77
78     QToolBar *toolbar2 = addToolBar("Transactions toolbar");
79     toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
80     toolbar2->addAction(exportAction);
81
82     // Overview page
83     overviewPage = new OverviewPage();
84     QVBoxLayout *vbox = new QVBoxLayout();
85
86     transactionView = new TransactionView(this);
87     connect(transactionView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(transactionDetails(const QModelIndex&)));
88     vbox->addWidget(transactionView);
89
90     transactionsPage = new QWidget(this);
91     transactionsPage->setLayout(vbox);
92
93     centralWidget = new QStackedWidget(this);
94     centralWidget->addWidget(overviewPage);
95     centralWidget->addWidget(transactionsPage);
96     setCentralWidget(centralWidget);
97     
98     // Create status bar
99     statusBar();
100
101     labelConnections = new QLabel();
102     labelConnections->setFrameStyle(QFrame::Panel | QFrame::Sunken);
103     labelConnections->setMinimumWidth(150);
104     labelConnections->setToolTip(tr("Number of connections to other clients"));
105
106     labelBlocks = new QLabel();
107     labelBlocks->setFrameStyle(QFrame::Panel | QFrame::Sunken);
108     labelBlocks->setMinimumWidth(130);
109     labelBlocks->setToolTip(tr("Number of blocks in the block chain"));
110
111     // Progress bar for blocks download
112     progressBarLabel = new QLabel(tr("Synchronizing with network..."));
113     progressBarLabel->setVisible(false);
114     progressBar = new QProgressBar();
115     progressBar->setToolTip(tr("Block chain synchronization in progress"));
116     progressBar->setVisible(false);
117
118     statusBar()->addWidget(progressBarLabel);
119     statusBar()->addWidget(progressBar);
120     statusBar()->addPermanentWidget(labelConnections);
121     statusBar()->addPermanentWidget(labelBlocks);
122
123     createTrayIcon();
124
125     gotoOverviewTab();
126 }
127
128 void BitcoinGUI::createActions()
129 {
130     QActionGroup *tabGroup = new QActionGroup(this);
131     overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
132     overviewAction->setCheckable(true);
133     tabGroup->addAction(overviewAction);
134     historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
135     historyAction->setCheckable(true);
136     tabGroup->addAction(historyAction);
137
138     connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewTab()));
139     connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryTab()));
140
141     quit = new QAction(QIcon(":/icons/quit"), tr("&Exit"), this);
142     quit->setToolTip(tr("Quit application"));
143     sendCoins = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
144     sendCoins->setToolTip(tr("Send coins to a bitcoin address"));
145     addressbook = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
146     addressbook->setToolTip(tr("Edit the list of stored addresses and labels"));
147     about = new QAction(QIcon(":/icons/bitcoin"), tr("&About"), this);
148     about->setToolTip(tr("Show information about Bitcoin"));
149     receiveCoins = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
150     receiveCoins->setToolTip(tr("Show the list of addresses for receiving payments"));
151     options = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
152     options->setToolTip(tr("Modify configuration options for bitcoin"));
153     openBitcoin = new QAction(QIcon(":/icons/bitcoin"), tr("Open &Bitcoin"), this);
154     openBitcoin->setToolTip(tr("Show the Bitcoin window"));
155     exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
156     exportAction->setToolTip(tr("Export data in current view to a file"));
157
158     connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
159     connect(sendCoins, SIGNAL(triggered()), this, SLOT(sendCoinsClicked()));
160     connect(addressbook, SIGNAL(triggered()), this, SLOT(addressbookClicked()));
161     connect(receiveCoins, SIGNAL(triggered()), this, SLOT(receiveCoinsClicked()));
162     connect(options, SIGNAL(triggered()), this, SLOT(optionsClicked()));
163     connect(about, SIGNAL(triggered()), this, SLOT(aboutClicked()));
164     connect(openBitcoin, SIGNAL(triggered()), this, SLOT(show()));
165     connect(exportAction, SIGNAL(triggered()), this, SLOT(exportClicked()));
166 }
167
168 void BitcoinGUI::setClientModel(ClientModel *clientModel)
169 {
170     this->clientModel = clientModel;
171
172     if(clientModel->isTestNet())
173     {
174         QString title_testnet = tr("Bitcoin Wallet [testnet]");
175         setWindowTitle(title_testnet);
176         setWindowIcon(QIcon(":icons/bitcoin_testnet"));
177         if(trayIcon)
178         {
179             trayIcon->setToolTip(title_testnet);
180             trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
181         }
182     }
183
184     // Keep up to date with client
185     setNumConnections(clientModel->getNumConnections());
186     connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
187
188     setNumBlocks(clientModel->getNumBlocks());
189     connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int)));
190
191     // Report errors from network/worker thread
192     connect(clientModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
193 }
194
195 void BitcoinGUI::setWalletModel(WalletModel *walletModel)
196 {
197     this->walletModel = walletModel;
198
199     // Keep up to date with wallet
200     setBalance(walletModel->getBalance());
201     connect(walletModel, SIGNAL(balanceChanged(qint64)), this, SLOT(setBalance(qint64)));
202
203     setNumTransactions(walletModel->getNumTransactions());
204     connect(walletModel, SIGNAL(numTransactionsChanged(int)), this, SLOT(setNumTransactions(int)));
205
206     // Report errors from wallet thread
207     connect(walletModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
208
209     // Put transaction list in tabs
210     transactionView->setModel(walletModel->getTransactionTableModel());
211
212     // Balloon popup for new transaction
213     connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
214             this, SLOT(incomingTransaction(const QModelIndex &, int, int)));
215 }
216
217 void BitcoinGUI::createTrayIcon()
218 {
219     QMenu *trayIconMenu = new QMenu(this);
220     trayIconMenu->addAction(openBitcoin);
221     trayIconMenu->addAction(sendCoins);
222     trayIconMenu->addAction(options);
223     trayIconMenu->addSeparator();
224     trayIconMenu->addAction(quit);
225
226     trayIcon = new QSystemTrayIcon(this);
227     trayIcon->setContextMenu(trayIconMenu);
228     trayIcon->setToolTip("Bitcoin client");
229     trayIcon->setIcon(QIcon(":/icons/toolbar"));
230     connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
231             this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
232     trayIcon->show();
233 }
234
235 void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
236 {
237     if(reason == QSystemTrayIcon::DoubleClick)
238     {
239         // Doubleclick on system tray icon triggers "open bitcoin"
240         openBitcoin->trigger();
241     }
242 }
243
244 void BitcoinGUI::sendCoinsClicked()
245 {
246     SendCoinsDialog dlg;
247     dlg.setModel(walletModel);
248     dlg.exec();
249 }
250
251 void BitcoinGUI::addressbookClicked()
252 {
253     AddressBookDialog dlg(AddressBookDialog::ForEditing);
254     dlg.setModel(walletModel->getAddressTableModel());
255     dlg.setTab(AddressBookDialog::SendingTab);
256     dlg.exec();
257 }
258
259 void BitcoinGUI::receiveCoinsClicked()
260 {
261     AddressBookDialog dlg(AddressBookDialog::ForEditing);
262     dlg.setModel(walletModel->getAddressTableModel());
263     dlg.setTab(AddressBookDialog::ReceivingTab);
264     dlg.exec();
265 }
266
267 void BitcoinGUI::optionsClicked()
268 {
269     OptionsDialog dlg;
270     dlg.setModel(clientModel->getOptionsModel());
271     dlg.exec();
272 }
273
274 void BitcoinGUI::aboutClicked()
275 {
276     AboutDialog dlg;
277     dlg.setModel(clientModel);
278     dlg.exec();
279 }
280
281 void BitcoinGUI::setBalance(qint64 balance)
282 {
283     overviewPage->setBalance(balance);
284 }
285
286 void BitcoinGUI::setNumConnections(int count)
287 {
288     QString icon;
289     switch(count)
290     {
291     case 0: icon = ":/icons/connect_0"; break;
292     case 1: case 2: case 3: icon = ":/icons/connect_1"; break;
293     case 4: case 5: case 6: icon = ":/icons/connect_2"; break;
294     case 7: case 8: case 9: icon = ":/icons/connect_3"; break;
295     default: icon = ":/icons/connect_4"; break;
296     }
297     labelConnections->setTextFormat(Qt::RichText);
298     labelConnections->setText("<img src=\""+icon+"\"> " + tr("%n connection(s)", "", count));
299 }
300
301 void BitcoinGUI::setNumBlocks(int count)
302 {
303     int total = clientModel->getTotalBlocksEstimate();
304     if(count < total)
305     {
306         progressBarLabel->setVisible(true);
307         progressBar->setVisible(true);
308         progressBar->setMaximum(total);
309         progressBar->setValue(count);
310     }
311     else
312     {
313         progressBarLabel->setVisible(false);
314         progressBar->setVisible(false);
315     }
316
317     labelBlocks->setText(tr("%n block(s)", "", count));
318 }
319
320 void BitcoinGUI::setNumTransactions(int count)
321 {
322     overviewPage->setNumTransactions(count);
323 }
324
325 void BitcoinGUI::error(const QString &title, const QString &message)
326 {
327     // Report errors from network/worker thread
328     if(trayIcon->supportsMessages())
329     {
330         // Show as "balloon" message if possible
331         trayIcon->showMessage(title, message, QSystemTrayIcon::Critical);
332     }
333     else
334     {
335         // Fall back to old fashioned popup dialog if not
336         QMessageBox::critical(this, title,
337             message,
338             QMessageBox::Ok, QMessageBox::Ok);
339     }
340 }
341
342 void BitcoinGUI::changeEvent(QEvent *e)
343 {
344     if (e->type() == QEvent::WindowStateChange)
345     {
346         if(clientModel->getOptionsModel()->getMinimizeToTray())
347         {
348             if (isMinimized())
349             {
350                 hide();
351                 e->ignore();
352             }
353             else
354             {
355                 e->accept();
356             }
357         }
358     }
359     QMainWindow::changeEvent(e);
360 }
361
362 void BitcoinGUI::closeEvent(QCloseEvent *event)
363 {
364     if(!clientModel->getOptionsModel()->getMinimizeToTray() &&
365        !clientModel->getOptionsModel()->getMinimizeOnClose())
366     {
367         qApp->quit();
368     }
369     QMainWindow::closeEvent(event);
370 }
371
372 void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
373 {
374     QString strMessage =
375         tr("This transaction is over the size limit.  You can still send it for a fee of %1, "
376           "which goes to the nodes that process your transaction and helps to support the network.  "
377           "Do you want to pay the fee?").arg(GUIUtil::formatMoney(nFeeRequired));
378     QMessageBox::StandardButton retval = QMessageBox::question(
379           this, tr("Sending..."), strMessage,
380           QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes);
381     *payFee = (retval == QMessageBox::Yes);
382 }
383
384 void BitcoinGUI::transactionDetails(const QModelIndex& idx)
385 {
386     // A transaction is doubleclicked
387     TransactionDescDialog dlg(idx);
388     dlg.exec();
389 }
390
391 void BitcoinGUI::incomingTransaction(const QModelIndex & parent, int start, int end)
392 {
393     TransactionTableModel *ttm = walletModel->getTransactionTableModel();
394     qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
395                     .data(Qt::EditRole).toULongLong();
396     if(amount>0 && !clientModel->inInitialBlockDownload())
397     {
398         // On incoming transaction, make an info balloon
399         // Unless the initial block download is in progress, to prevent balloon-spam
400         QString date = ttm->index(start, TransactionTableModel::Date, parent)
401                         .data().toString();
402         QString type = ttm->index(start, TransactionTableModel::Type, parent)
403                         .data().toString();
404         QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
405                         .data().toString();
406
407         trayIcon->showMessage(tr("Incoming transaction"),
408                               tr("Date: ") + date + "\n" +
409                               tr("Amount: ") + GUIUtil::formatMoney(amount, true) + "\n" +
410                               tr("Type: ") + type + "\n" +
411                               tr("Address: ") + address + "\n",
412                               QSystemTrayIcon::Information);
413     }
414 }
415
416 void BitcoinGUI::gotoOverviewTab()
417 {
418     overviewAction->setChecked(true);
419     centralWidget->setCurrentWidget(overviewPage);
420     exportAction->setEnabled(false);
421 }
422
423 void BitcoinGUI::gotoHistoryTab()
424 {
425     historyAction->setChecked(true);
426     centralWidget->setCurrentWidget(transactionsPage);
427     exportAction->setEnabled(true);
428 }
429
430 void BitcoinGUI::exportClicked()
431 {
432     // Redirect to the right view, as soon as export for other views
433     // (such as address book) is implemented.
434     transactionView->exportClicked();
435 }
436