convert to full tab-based ui
[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 "addressbookpage.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(sendCoinsAction);
58     file->addAction(receiveCoinsAction);
59     file->addSeparator();
60     file->addAction(quitAction);
61     
62     QMenu *settings = menuBar()->addMenu("&Settings");
63     settings->addAction(optionsAction);
64
65     QMenu *help = menuBar()->addMenu("&Help");
66     help->addAction(aboutAction);
67     
68     // Toolbar
69     QToolBar *toolbar = addToolBar("Main toolbar");
70     toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
71     toolbar->addAction(overviewAction);
72     toolbar->addAction(sendCoinsAction);
73     toolbar->addAction(receiveCoinsAction);
74     toolbar->addAction(historyAction);
75     toolbar->addAction(addressBookAction);
76
77     QToolBar *toolbar2 = addToolBar("Transactions toolbar");
78     toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
79     toolbar2->addAction(exportAction);
80
81     // Overview page
82     overviewPage = new OverviewPage();
83     QVBoxLayout *vbox = new QVBoxLayout();
84
85     transactionView = new TransactionView(this);
86     connect(transactionView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(transactionDetails(const QModelIndex&)));
87     vbox->addWidget(transactionView);
88
89     transactionsPage = new QWidget(this);
90     transactionsPage->setLayout(vbox);
91
92     addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
93
94     receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
95
96     sendCoinsPage = new SendCoinsDialog(this);
97
98     centralWidget = new QStackedWidget(this);
99     centralWidget->addWidget(overviewPage);
100     centralWidget->addWidget(transactionsPage);
101     centralWidget->addWidget(addressBookPage);
102     centralWidget->addWidget(receiveCoinsPage);
103     centralWidget->addWidget(sendCoinsPage);
104     setCentralWidget(centralWidget);
105     
106     // Create status bar
107     statusBar();
108
109     labelConnections = new QLabel();
110     labelConnections->setFrameStyle(QFrame::Panel | QFrame::Sunken);
111     labelConnections->setMinimumWidth(150);
112     labelConnections->setToolTip(tr("Number of connections to other clients"));
113
114     labelBlocks = new QLabel();
115     labelBlocks->setFrameStyle(QFrame::Panel | QFrame::Sunken);
116     labelBlocks->setMinimumWidth(130);
117     labelBlocks->setToolTip(tr("Number of blocks in the block chain"));
118
119     // Progress bar for blocks download
120     progressBarLabel = new QLabel(tr("Synchronizing with network..."));
121     progressBarLabel->setVisible(false);
122     progressBar = new QProgressBar();
123     progressBar->setToolTip(tr("Block chain synchronization in progress"));
124     progressBar->setVisible(false);
125
126     statusBar()->addWidget(progressBarLabel);
127     statusBar()->addWidget(progressBar);
128     statusBar()->addPermanentWidget(labelConnections);
129     statusBar()->addPermanentWidget(labelBlocks);
130
131     createTrayIcon();
132
133     gotoOverviewPage();
134 }
135
136 void BitcoinGUI::createActions()
137 {
138     QActionGroup *tabGroup = new QActionGroup(this);
139
140     overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
141     overviewAction->setCheckable(true);
142     tabGroup->addAction(overviewAction);
143
144     historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
145     historyAction->setCheckable(true);
146     tabGroup->addAction(historyAction);
147
148     addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
149     addressBookAction->setToolTip(tr("Edit the list of stored addresses and labels"));
150     addressBookAction->setCheckable(true);
151     tabGroup->addAction(addressBookAction);
152
153     receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
154     receiveCoinsAction->setToolTip(tr("Show the list of addresses for receiving payments"));
155     receiveCoinsAction->setCheckable(true);
156     tabGroup->addAction(receiveCoinsAction);
157
158     sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
159     sendCoinsAction->setToolTip(tr("Send coins to a bitcoin address"));
160     sendCoinsAction->setCheckable(true);
161     tabGroup->addAction(sendCoinsAction);
162
163     connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
164     connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
165     connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
166     connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
167     connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
168
169     quitAction = new QAction(QIcon(":/icons/quit"), tr("&Exit"), this);
170     quitAction->setToolTip(tr("Quit application"));
171     aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About"), this);
172     aboutAction->setToolTip(tr("Show information about Bitcoin"));
173     optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this);
174     optionsAction->setToolTip(tr("Modify configuration options for bitcoin"));
175     openBitcoinAction = new QAction(QIcon(":/icons/bitcoin"), tr("Open &Bitcoin"), this);
176     openBitcoinAction->setToolTip(tr("Show the Bitcoin window"));
177     exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
178     exportAction->setToolTip(tr("Export data in current view to a file"));
179
180     connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
181     connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
182     connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
183     connect(openBitcoinAction, SIGNAL(triggered()), this, SLOT(show()));
184     connect(exportAction, SIGNAL(triggered()), this, SLOT(exportClicked()));
185 }
186
187 void BitcoinGUI::setClientModel(ClientModel *clientModel)
188 {
189     this->clientModel = clientModel;
190
191     if(clientModel->isTestNet())
192     {
193         QString title_testnet = tr("Bitcoin Wallet [testnet]");
194         setWindowTitle(title_testnet);
195         setWindowIcon(QIcon(":icons/bitcoin_testnet"));
196         if(trayIcon)
197         {
198             trayIcon->setToolTip(title_testnet);
199             trayIcon->setIcon(QIcon(":/icons/toolbar_testnet"));
200         }
201     }
202
203     // Keep up to date with client
204     setNumConnections(clientModel->getNumConnections());
205     connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
206
207     setNumBlocks(clientModel->getNumBlocks());
208     connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int)));
209
210     // Report errors from network/worker thread
211     connect(clientModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
212 }
213
214 void BitcoinGUI::setWalletModel(WalletModel *walletModel)
215 {
216     this->walletModel = walletModel;
217
218     // Keep up to date with wallet
219     setBalance(walletModel->getBalance());
220     connect(walletModel, SIGNAL(balanceChanged(qint64)), this, SLOT(setBalance(qint64)));
221
222     setNumTransactions(walletModel->getNumTransactions());
223     connect(walletModel, SIGNAL(numTransactionsChanged(int)), this, SLOT(setNumTransactions(int)));
224
225     // Report errors from wallet thread
226     connect(walletModel, SIGNAL(error(QString,QString)), this, SLOT(error(QString,QString)));
227
228     // Put transaction list in tabs
229     transactionView->setModel(walletModel->getTransactionTableModel());
230
231     addressBookPage->setModel(walletModel->getAddressTableModel());
232     receiveCoinsPage->setModel(walletModel->getAddressTableModel());
233     sendCoinsPage->setModel(walletModel);
234
235     // Balloon popup for new transaction
236     connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
237             this, SLOT(incomingTransaction(const QModelIndex &, int, int)));
238 }
239
240 void BitcoinGUI::createTrayIcon()
241 {
242     QMenu *trayIconMenu = new QMenu(this);
243     trayIconMenu->addAction(openBitcoinAction);
244     trayIconMenu->addAction(sendCoinsAction);
245     trayIconMenu->addAction(optionsAction);
246     trayIconMenu->addSeparator();
247     trayIconMenu->addAction(quitAction);
248
249     trayIcon = new QSystemTrayIcon(this);
250     trayIcon->setContextMenu(trayIconMenu);
251     trayIcon->setToolTip("Bitcoin client");
252     trayIcon->setIcon(QIcon(":/icons/toolbar"));
253     connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
254             this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
255     trayIcon->show();
256 }
257
258 void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
259 {
260     if(reason == QSystemTrayIcon::DoubleClick)
261     {
262         // Doubleclick on system tray icon triggers "open bitcoin"
263         openBitcoinAction->trigger();
264     }
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::gotoOverviewPage()
417 {
418     overviewAction->setChecked(true);
419     centralWidget->setCurrentWidget(overviewPage);
420     exportAction->setEnabled(false);
421 }
422
423 void BitcoinGUI::gotoHistoryPage()
424 {
425     historyAction->setChecked(true);
426     centralWidget->setCurrentWidget(transactionsPage);
427     exportAction->setEnabled(true);
428 }
429
430 void BitcoinGUI::gotoAddressBookPage()
431 {
432     addressBookAction->setChecked(true);
433     centralWidget->setCurrentWidget(addressBookPage);
434     exportAction->setEnabled(false); // TODO
435 }
436
437 void BitcoinGUI::gotoReceiveCoinsPage()
438 {
439     receiveCoinsAction->setChecked(true);
440     centralWidget->setCurrentWidget(receiveCoinsPage);
441     exportAction->setEnabled(false); // TODO
442 }
443
444 void BitcoinGUI::gotoSendCoinsPage()
445 {
446     sendCoinsAction->setChecked(true);
447     centralWidget->setCurrentWidget(sendCoinsPage);
448     exportAction->setEnabled(false);
449 }
450
451 void BitcoinGUI::exportClicked()
452 {
453     // Redirect to the right view, as soon as export for other views
454     // (such as address book) is implemented.
455     transactionView->exportClicked();
456 }
457