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