diff options
-rw-r--r-- | bitcoin-qt.pro | 6 | ||||
-rw-r--r-- | multiwallet-qt.txt | 53 | ||||
-rw-r--r-- | src/alert.cpp | 33 | ||||
-rw-r--r-- | src/alert.h | 2 | ||||
-rw-r--r-- | src/bitcoinrpc.cpp | 4 | ||||
-rw-r--r-- | src/init.cpp | 1 | ||||
-rw-r--r-- | src/qt/askpassphrasedialog.cpp | 2 | ||||
-rw-r--r-- | src/qt/bitcoin.cpp | 5 | ||||
-rw-r--r-- | src/qt/bitcoinamountfield.cpp | 5 | ||||
-rw-r--r-- | src/qt/bitcoingui.cpp | 305 | ||||
-rw-r--r-- | src/qt/bitcoingui.h | 44 | ||||
-rw-r--r-- | src/qt/guiutil.cpp | 2 | ||||
-rw-r--r-- | src/qt/transactionview.cpp | 8 | ||||
-rw-r--r-- | src/qt/transactionview.h | 1 | ||||
-rw-r--r-- | src/qt/walletframe.cpp | 129 | ||||
-rw-r--r-- | src/qt/walletframe.h | 73 | ||||
-rw-r--r-- | src/qt/walletstack.cpp | 155 | ||||
-rw-r--r-- | src/qt/walletstack.h | 102 | ||||
-rw-r--r-- | src/qt/walletview.cpp | 354 | ||||
-rw-r--r-- | src/qt/walletview.h | 132 | ||||
-rw-r--r-- | src/test/alert_tests.cpp | 185 | ||||
-rw-r--r-- | src/test/data/alertTests | bin | 0 -> 1283 bytes |
22 files changed, 1366 insertions, 235 deletions
diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 11db551cb6..41b1ed6134 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -185,6 +185,9 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/transactionfilterproxy.h \ src/qt/transactionview.h \ src/qt/walletmodel.h \ + src/qt/walletview.h \ + src/qt/walletstack.h \ + src/qt/walletframe.h \ src/bitcoinrpc.h \ src/qt/overviewpage.h \ src/qt/csvmodelwriter.h \ @@ -248,6 +251,9 @@ SOURCES += src/qt/bitcoin.cpp \ src/qt/transactionfilterproxy.cpp \ src/qt/transactionview.cpp \ src/qt/walletmodel.cpp \ + src/qt/walletview.cpp \ + src/qt/walletstack.cpp \ + src/qt/walletframe.cpp \ src/bitcoinrpc.cpp \ src/rpcdump.cpp \ src/rpcnet.cpp \ diff --git a/multiwallet-qt.txt b/multiwallet-qt.txt new file mode 100644 index 0000000000..8394080db0 --- /dev/null +++ b/multiwallet-qt.txt @@ -0,0 +1,53 @@ +Multiwallet Qt Development and Integration Strategy +=================================================== + +In order to support loading of multiple wallets in bitcoin-qt, a few changes in the UI architecture will be needed. +Fortunately, only four of the files in the existing project are affected by this change. + +Three new classes have been implemented in three new .h/.cpp file pairs, with much of the functionality that was previously +implemented in the BitcoinGUI class moved over to these new classes. + +The two existing files most affected, by far, are bitcoingui.h and bitcoingui.cpp, as the BitcoinGUI class will require +some major retrofitting. + +Only requiring some minor changes is bitcoin.cpp. + +Finally, three new headers and source files will have to be added to bitcoin-qt.pro. + +Changes to class BitcoinGUI +--------------------------- +The principal change to the BitcoinGUI class concerns the QStackedWidget instance called centralWidget. +This widget owns five page views: overviewPage, transactionsPage, addressBookPage, receiveCoinsPage, and sendCoinsPage. + +A new class called *WalletView* inheriting from QStackedWidget has been written to handle all renderings and updates of +these page views. In addition to owning these five page views, a WalletView also has a pointer to a WalletModel instance. +This allows the construction of multiple WalletView objects, each rendering a distinct wallet. + +A second class called *WalletStack*, also inheriting from QStackedWidget, has been written to handle switching focus between +different loaded wallets. In its current implementation, as a QStackedWidget, only one wallet can be viewed at a time - +but this can be changed later. + +A third class called *WalletFrame* inheriting from QFrame has been written as a container for embedding all wallet-related +controls into BitcoinGUI. At present it just contains a WalletStack instance and does little more than passing on messages +from BitcoinGUI to the WalletStack, which in turn passes them to the individual WalletViews. It is a WalletFrame instance +that takes the place of what used to be centralWidget in BitcoinGUI. The purpose of this class is to allow future +refinements of the wallet controls with minimal need for further modifications to BitcoinGUI, thus greatly simplifying +merges while reducing the risk of breaking top-level stuff. + +Changes to bitcoin.cpp +---------------------- +bitcoin.cpp is the entry point into bitcoin-qt, and as such, will require some minor modifications to provide hooks for +multiple wallet support. Most importantly will be the way it instantiates WalletModels and passes them to the +singleton BitcoinGUI instance called window. Formerly, BitcoinGUI kept a pointer to a single instance of a WalletModel. +The initial change required is very simple: rather than calling window.setWalletModel(&walletModel); we perform the +following two steps: + +window.addWallet("~Default", &walletModel); +window.setCurrentWallet("~Default"); + +The string parameter is just an arbitrary name given to the default wallet. It's been prepended with a tilde to avoid name +collisions in the future with additional wallets. + +The shutdown call window.setWalletModel(0) has also been removed. In its place is now: + +window.removeAllWallets(); diff --git a/src/alert.cpp b/src/alert.cpp index 48920629e2..4b029840dd 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -2,6 +2,9 @@ // Alert system // +#include <algorithm> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/replace.hpp> #include <boost/foreach.hpp> #include <map> @@ -165,7 +168,7 @@ CAlert CAlert::getAlertByHash(const uint256 &hash) return retval; } -bool CAlert::ProcessAlert() +bool CAlert::ProcessAlert(bool fThread) { if (!CheckSignature()) return false; @@ -229,9 +232,35 @@ bool CAlert::ProcessAlert() // Add to mapAlerts mapAlerts.insert(make_pair(GetHash(), *this)); - // Notify UI if it applies to me + // Notify UI and -alertnotify if it applies to me if(AppliesToMe()) + { uiInterface.NotifyAlertChanged(GetHash(), CT_NEW); + std::string strCmd = GetArg("-alertnotify", ""); + if (!strCmd.empty()) + { + // Alert text should be plain ascii coming from a trusted source, but to + // be safe we first strip anything not in safeChars, then add single quotes around + // the whole string before passing it to the shell: + std::string singleQuote("'"); + // safeChars chosen to allow simple messages/URLs/email addresses, but avoid anything + // even possibly remotely dangerous like & or > + std::string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 .,;_/:?@"); + std::string safeStatus; + for (std::string::size_type i = 0; i < strStatusBar.size(); i++) + { + if (safeChars.find(strStatusBar[i]) != std::string::npos) + safeStatus.push_back(strStatusBar[i]); + } + safeStatus = singleQuote+safeStatus+singleQuote; + boost::replace_all(strCmd, "%s", safeStatus); + + if (fThread) + boost::thread t(runCommand, strCmd); // thread runs free + else + runCommand(strCmd); + } + } } printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); diff --git a/src/alert.h b/src/alert.h index 7949c76972..25e140f573 100644 --- a/src/alert.h +++ b/src/alert.h @@ -91,7 +91,7 @@ public: bool AppliesToMe() const; bool RelayTo(CNode* pnode) const; bool CheckSignature() const; - bool ProcessAlert(); + bool ProcessAlert(bool fThread = true); /* * Get copy of (active) alert object by hash. Returns a null alert if it is not found. diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 4a6cc42efc..b6d8de4a18 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -769,7 +769,9 @@ void ThreadRPCServer2(void* parg) "rpcpassword=%s\n" "(you do not need to remember this password)\n" "The username and password MUST NOT be the same.\n" - "If the file does not exist, create it with owner-readable-only file permissions.\n"), + "If the file does not exist, create it with owner-readable-only file permissions.\n" + "It is also recommended to set alertnotify so you are notified of problems;\n" + "for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" admin@foo.com\n"), strWhatAmI.c_str(), GetConfigFile().string().c_str(), EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), diff --git a/src/init.cpp b/src/init.cpp index fc87457b4b..1bf76631dc 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -300,6 +300,7 @@ std::string HelpMessage() " -rpcconnect=<ip> " + _("Send commands to node running on <ip> (default: 127.0.0.1)") + "\n" + " -blocknotify=<cmd> " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" + " -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" + + " -alertnotify=<cmd> " + _("Execute command when a relevant alert is received (%s in cmd is replaced by message)") + "\n" + " -upgradewallet " + _("Upgrade wallet to latest format") + "\n" + " -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" + " -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" + diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index cf35ee2457..f165c11cb1 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -235,7 +235,7 @@ bool AskPassphraseDialog::eventFilter(QObject *object, QEvent *event) if (str.length() != 0) { const QChar *psz = str.unicode(); bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0; - if ((fShift && psz->isLower()) || (!fShift && psz->isUpper())) { + if ((fShift && *psz >= 'a' && *psz <= 'z') || (!fShift && *psz >= 'A' && *psz <= 'Z')) { fCapsLock = true; ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); } else if (psz->isLetter()) { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index e3d51be54a..346128c0c4 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -232,7 +232,8 @@ int main(int argc, char *argv[]) WalletModel walletModel(pwalletMain, &optionsModel); window.setClientModel(&clientModel); - window.setWalletModel(&walletModel); + window.addWallet("~Default", &walletModel); + window.setCurrentWallet("~Default"); // If -min option passed, start window minimized. if(GetBoolArg("-min")) @@ -253,7 +254,7 @@ int main(int argc, char *argv[]) window.hide(); window.setClientModel(0); - window.setWalletModel(0); + window.removeAllWallets(); guiref = 0; } // Shutdown the core and its threads, but don't exit Bitcoin-Qt here diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index ddf185c415..4fa2ca508b 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -145,6 +145,11 @@ void BitcoinAmountField::unitChanged(int idx) amount->setDecimals(BitcoinUnits::decimals(currentUnit)); amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals())); + if(currentUnit == BitcoinUnits::uBTC) + amount->setSingleStep(0.01); + else + amount->setSingleStep(0.001); + if(valid) { // If value was valid, re-place it in the widget with the new unit diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 0d951718bb..eff8e667f6 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -10,26 +10,20 @@ #include "bitcoingui.h" #include "transactiontablemodel.h" -#include "addressbookpage.h" -#include "sendcoinsdialog.h" -#include "signverifymessagedialog.h" #include "optionsdialog.h" #include "aboutdialog.h" #include "clientmodel.h" #include "walletmodel.h" -#include "editaddressdialog.h" +#include "walletframe.h" #include "optionsmodel.h" #include "transactiondescdialog.h" -#include "addresstablemodel.h" -#include "transactionview.h" -#include "overviewpage.h" #include "bitcoinunits.h" #include "guiconstants.h" -#include "askpassphrasedialog.h" #include "notificator.h" #include "guiutil.h" #include "rpcconsole.h" #include "ui_interface.h" +#include "wallet.h" #ifdef Q_OS_MAC #include "macdockiconhandler.h" @@ -54,13 +48,17 @@ #include <QUrl> #include <QMimeData> #include <QStyle> +#include <QSettings> +#include <QDesktopWidget> +#include <QListWidget> #include <iostream> +const QString BitcoinGUI::DEFAULT_WALLET = "~Default"; + BitcoinGUI::BitcoinGUI(QWidget *parent): QMainWindow(parent), clientModel(0), - walletModel(0), encryptWalletAction(0), changePassphraseAction(0), aboutQtAction(0), @@ -69,7 +67,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): rpcConsole(0), prevBlocks(0) { - resize(850, 550); + restoreWindowGeometry(); setWindowTitle(tr("Bitcoin") + " - " + tr("Wallet")); #ifndef Q_OS_MAC qApp->setWindowIcon(QIcon(":icons/bitcoin")); @@ -93,31 +91,10 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): // Create system tray icon and notification createTrayIcon(); - // Create tabs - overviewPage = new OverviewPage(); - - transactionsPage = new QWidget(this); - QVBoxLayout *vbox = new QVBoxLayout(); - transactionView = new TransactionView(this); - vbox->addWidget(transactionView); - transactionsPage->setLayout(vbox); - - addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab); - - receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab); - - sendCoinsPage = new SendCoinsDialog(this); - - signVerifyMessageDialog = new SignVerifyMessageDialog(this); - - centralWidget = new QStackedWidget(this); - centralWidget->addWidget(overviewPage); - centralWidget->addWidget(transactionsPage); - centralWidget->addWidget(addressBookPage); - centralWidget->addWidget(receiveCoinsPage); - centralWidget->addWidget(sendCoinsPage); - setCentralWidget(centralWidget); - + // Create wallet frame and make it the central widget + walletFrame = new WalletFrame(this); + setCentralWidget(walletFrame); + // Create status bar statusBar(); @@ -162,31 +139,16 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this); - // Clicking on a transaction on the overview page simply sends you to transaction history page - connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage())); - connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); - - // Double-clicking on a transaction on the transaction history page shows details - connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails())); - rpcConsole = new RPCConsole(this); connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show())); - // Clicking on "Send Coins" in the address book sends you to the send coins tab - connect(addressBookPage, SIGNAL(sendCoins(QString)), this, SLOT(gotoSendCoinsPage(QString))); - // Clicking on "Verify Message" in the address book opens the verify message tab in the Sign/Verify Message dialog - connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString))); - // Clicking on "Sign Message" in the receive coins page opens the sign message tab in the Sign/Verify Message dialog - connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString))); - // Install event filter to be able to catch status tip events (QEvent::StatusTip) this->installEventFilter(this); - - gotoOverviewPage(); } BitcoinGUI::~BitcoinGUI() { + saveWindowGeometry(); if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); #ifdef Q_OS_MAC @@ -243,7 +205,7 @@ void BitcoinGUI::createActions() connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage())); - + quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); @@ -377,39 +339,23 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) // Receive and report messages from network/worker thread connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int))); - overviewPage->setClientModel(clientModel); - rpcConsole->setClientModel(clientModel); - addressBookPage->setOptionsModel(clientModel->getOptionsModel()); - receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel()); + walletFrame->setClientModel(clientModel); } } -void BitcoinGUI::setWalletModel(WalletModel *walletModel) +bool BitcoinGUI::addWallet(const QString& name, WalletModel *walletModel) { - this->walletModel = walletModel; - if(walletModel) - { - // Receive and report messages from wallet thread - connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int))); - - // Put transaction list in tabs - transactionView->setModel(walletModel); - overviewPage->setWalletModel(walletModel); - addressBookPage->setModel(walletModel->getAddressTableModel()); - receiveCoinsPage->setModel(walletModel->getAddressTableModel()); - sendCoinsPage->setModel(walletModel); - signVerifyMessageDialog->setModel(walletModel); - - setEncryptionStatus(walletModel->getEncryptionStatus()); - connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int))); - - // Balloon pop-up for new transaction - connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(incomingTransaction(QModelIndex,int,int))); - - // Ask for passphrase if needed - connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet())); - } + return walletFrame->addWallet(name, walletModel); +} + +bool BitcoinGUI::setCurrentWallet(const QString& name) +{ + return walletFrame->setCurrentWallet(name); +} + +void BitcoinGUI::removeAllWallets() +{ + walletFrame->removeAllWallets(); } void BitcoinGUI::createTrayIcon() @@ -468,6 +414,28 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) } #endif +void BitcoinGUI::saveWindowGeometry() +{ + QSettings settings; + settings.setValue("nWindowPos", pos()); + settings.setValue("nWindowSize", size()); +} + +void BitcoinGUI::restoreWindowGeometry() +{ + QSettings settings; + QPoint pos = settings.value("nWindowPos").toPoint(); + QSize size = settings.value("nWindowSize", QSize(850, 550)).toSize(); + if (!pos.x() && !pos.y()) + { + QRect screen = qApp->desktop()->screenGeometry(); + pos.setX((screen.width()-size.width())/2); + pos.setY((screen.height()-size.height())/2); + } + resize(size); + move(pos); +} + void BitcoinGUI::optionsClicked() { if(!clientModel || !clientModel->getOptionsModel()) @@ -484,6 +452,41 @@ void BitcoinGUI::aboutClicked() dlg.exec(); } +void BitcoinGUI::gotoOverviewPage() +{ + if (walletFrame) walletFrame->gotoOverviewPage(); +} + +void BitcoinGUI::gotoHistoryPage() +{ + if (walletFrame) walletFrame->gotoHistoryPage(); +} + +void BitcoinGUI::gotoAddressBookPage() +{ + if (walletFrame) walletFrame->gotoAddressBookPage(); +} + +void BitcoinGUI::gotoReceiveCoinsPage() +{ + if (walletFrame) walletFrame->gotoReceiveCoinsPage(); +} + +void BitcoinGUI::gotoSendCoinsPage() +{ + if (walletFrame) walletFrame->gotoSendCoinsPage(); +} + +void BitcoinGUI::gotoSignMessageTab(QString addr) +{ + if (walletFrame) walletFrame->gotoSignMessageTab(addr); +} + +void BitcoinGUI::gotoVerifyMessageTab(QString addr) +{ + if (walletFrame) walletFrame->gotoSignMessageTab(addr); +} + void BitcoinGUI::setNumConnections(int count) { QString icon; @@ -548,7 +551,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks) tooltip = tr("Up to date") + QString(".<br>") + tooltip; labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); - overviewPage->showOutOfSyncWarning(false); + walletFrame->showOutOfSyncWarning(false); progressBarLabel->setVisible(false); progressBar->setVisible(false); @@ -583,7 +586,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks) syncIconMovie->jumpToNextFrame(); prevBlocks = count; - overviewPage->showOutOfSyncWarning(true); + walletFrame->showOutOfSyncWarning(true); tooltip += QString("<br>"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); @@ -692,104 +695,20 @@ void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee) *payFee = (retval == QMessageBox::Yes); } -void BitcoinGUI::incomingTransaction(const QModelIndex& parent, int start, int /*end*/) +void BitcoinGUI::incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address) { - // Prevent balloon-spam when initial block download is in progress - if(!walletModel || !clientModel || clientModel->inInitialBlockDownload()) - return; - - TransactionTableModel *ttm = walletModel->getTransactionTableModel(); - - QString date = ttm->index(start, TransactionTableModel::Date, parent) - .data().toString(); - qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent) - .data(Qt::EditRole).toULongLong(); - QString type = ttm->index(start, TransactionTableModel::Type, parent) - .data().toString(); - QString address = ttm->index(start, TransactionTableModel::ToAddress, parent) - .data().toString(); - - // On new transaction, make an info balloon + // On new transaction, make an info balloon message((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(BitcoinUnits::formatWithUnit(unit, amount, true)) .arg(type) .arg(address), CClientUIInterface::MSG_INFORMATION); } -void BitcoinGUI::gotoOverviewPage() -{ - overviewAction->setChecked(true); - centralWidget->setCurrentWidget(overviewPage); - - exportAction->setEnabled(false); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); -} - -void BitcoinGUI::gotoHistoryPage() -{ - historyAction->setChecked(true); - centralWidget->setCurrentWidget(transactionsPage); - - exportAction->setEnabled(true); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); - connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked())); -} - -void BitcoinGUI::gotoAddressBookPage() -{ - addressBookAction->setChecked(true); - centralWidget->setCurrentWidget(addressBookPage); - - exportAction->setEnabled(true); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); - connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked())); -} - -void BitcoinGUI::gotoReceiveCoinsPage() -{ - receiveCoinsAction->setChecked(true); - centralWidget->setCurrentWidget(receiveCoinsPage); - - exportAction->setEnabled(true); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); - connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked())); -} - -void BitcoinGUI::gotoSendCoinsPage(QString addr) -{ - sendCoinsAction->setChecked(true); - centralWidget->setCurrentWidget(sendCoinsPage); - - exportAction->setEnabled(false); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); - - if(!addr.isEmpty()) - sendCoinsPage->setAddress(addr); -} - -void BitcoinGUI::gotoSignMessageTab(QString addr) -{ - // call show() in showTab_SM() - signVerifyMessageDialog->showTab_SM(true); - - if(!addr.isEmpty()) - signVerifyMessageDialog->setAddress_SM(addr); -} - -void BitcoinGUI::gotoVerifyMessageTab(QString addr) -{ - // call show() in showTab_VM() - signVerifyMessageDialog->showTab_VM(true); - - if(!addr.isEmpty()) - signVerifyMessageDialog->setAddress_VM(addr); -} - void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs @@ -805,13 +724,13 @@ void BitcoinGUI::dropEvent(QDropEvent *event) QList<QUrl> uris = event->mimeData()->urls(); foreach(const QUrl &uri, uris) { - if (sendCoinsPage->handleURI(uri.toString())) + if (walletFrame->handleURI(uri.toString())) nValidUrisFound++; } // if valid URIs were found if (nValidUrisFound) - gotoSendCoinsPage(); + walletFrame->gotoSendCoinsPage(); else message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), CClientUIInterface::ICON_WARNING); @@ -835,12 +754,7 @@ bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) void BitcoinGUI::handleURI(QString strURI) { // URI has to be valid - if (sendCoinsPage->handleURI(strURI)) - { - showNormalIfMinimized(); - gotoSendCoinsPage(); - } - else + if (!walletFrame->handleURI(strURI)) message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), CClientUIInterface::ICON_WARNING); } @@ -876,49 +790,22 @@ void BitcoinGUI::setEncryptionStatus(int status) void BitcoinGUI::encryptWallet(bool status) { - if(!walletModel) - return; - AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt: - AskPassphraseDialog::Decrypt, this); - dlg.setModel(walletModel); - dlg.exec(); - - setEncryptionStatus(walletModel->getEncryptionStatus()); + walletFrame->encryptWallet(status); } 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)) { - message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."), - CClientUIInterface::MSG_ERROR); - } - else - message(tr("Backup Successful"), tr("The wallet data was successfully saved to the new location."), - CClientUIInterface::MSG_INFORMATION); - } + walletFrame->backupWallet(); } void BitcoinGUI::changePassphrase() { - AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this); - dlg.setModel(walletModel); - dlg.exec(); + walletFrame->changePassphrase(); } void BitcoinGUI::unlockWallet() { - if(!walletModel) - return; - // Unlock wallet when requested by wallet model - if(walletModel->getEncryptionStatus() == WalletModel::Locked) - { - AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this); - dlg.setModel(walletModel); - dlg.exec(); - } + walletFrame->unlockWallet(); } void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 1b3e313fc2..750e97104c 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -3,10 +3,14 @@ #include <QMainWindow> #include <QSystemTrayIcon> +#include <QMap> class TransactionTableModel; +class WalletFrame; +class WalletView; class ClientModel; class WalletModel; +class WalletStack; class TransactionView; class OverviewPage; class AddressBookPage; @@ -15,11 +19,16 @@ class SignVerifyMessageDialog; class Notificator; class RPCConsole; +class CWallet; + QT_BEGIN_NAMESPACE class QLabel; class QModelIndex; class QProgressBar; class QStackedWidget; +class QUrl; +class QListWidget; +class QPushButton; QT_END_NAMESPACE /** @@ -31,6 +40,8 @@ class BitcoinGUI : public QMainWindow Q_OBJECT public: + static const QString DEFAULT_WALLET; + explicit BitcoinGUI(QWidget *parent = 0); ~BitcoinGUI(); @@ -42,7 +53,11 @@ public: The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending functionality. */ - void setWalletModel(WalletModel *walletModel); + + bool addWallet(const QString& name, WalletModel *walletModel); + bool setCurrentWallet(const QString& name); + + void removeAllWallets(); protected: void changeEvent(QEvent *e); @@ -53,16 +68,7 @@ protected: private: ClientModel *clientModel; - WalletModel *walletModel; - - QStackedWidget *centralWidget; - - OverviewPage *overviewPage; - QWidget *transactionsPage; - AddressBookPage *addressBookPage; - AddressBookPage *receiveCoinsPage; - SendCoinsDialog *sendCoinsPage; - SignVerifyMessageDialog *signVerifyMessageDialog; + WalletFrame *walletFrame; QLabel *labelEncryptionIcon; QLabel *labelConnectionsIcon; @@ -88,7 +94,7 @@ private: QAction *changePassphraseAction; QAction *aboutQtAction; QAction *openRPCConsoleAction; - + QSystemTrayIcon *trayIcon; Notificator *notificator; TransactionView *transactionView; @@ -108,6 +114,10 @@ private: void createTrayIcon(); /** Create system tray menu (or setup the dock menu) */ void createTrayIconMenu(); + /** Save window size and position */ + void saveWindowGeometry(); + /** Restore window size and position */ + void restoreWindowGeometry(); public slots: /** Set number of connections shown in the UI */ @@ -139,6 +149,9 @@ public slots: void askFee(qint64 nFeeRequired, bool *payFee); void handleURI(QString strURI); + /** Show incoming transaction notification for new transactions. */ + void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address); + private slots: /** Switch to overview (home) page */ void gotoOverviewPage(); @@ -149,7 +162,7 @@ private slots: /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ - void gotoSendCoinsPage(QString addr = ""); + void gotoSendCoinsPage(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = ""); @@ -164,11 +177,6 @@ private slots: /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); #endif - /** Show incoming transaction notification for new transactions. - - The new items are those between start and end inclusive, under the given parent item. - */ - void incomingTransaction(const QModelIndex& parent, int start, int /*end*/); /** Encrypt the wallet */ void encryptWallet(bool status); /** Backup the wallet */ diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 30ad8ef66b..06ccde3782 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -161,7 +161,7 @@ void copyEntryData(QAbstractItemView *view, int column, int role) if(!selection.isEmpty()) { // Copy first item - QApplication::clipboard()->setText(selection.at(0).data(role).toString()); + QApplication::clipboard()->setText(selection.at(0).data(role).toString(),QClipboard::Selection); } } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index e7c384161c..9240b71c71 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -123,6 +123,7 @@ TransactionView::TransactionView(QWidget *parent) : QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *editLabelAction = new QAction(tr("Edit label"), this); QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); @@ -130,6 +131,7 @@ TransactionView::TransactionView(QWidget *parent) : contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTxIDAction); contextMenu->addAction(editLabelAction); contextMenu->addAction(showDetailsAction); @@ -145,6 +147,7 @@ TransactionView::TransactionView(QWidget *parent) : connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel())); connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); } @@ -309,6 +312,11 @@ void TransactionView::copyAmount() GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole); } +void TransactionView::copyTxID() +{ + GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole); +} + void TransactionView::editLabel() { if(!transactionView->selectionModel() ||!model) diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index e61515fdae..bb41a83e32 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -66,6 +66,7 @@ private slots: void editLabel(); void copyLabel(); void copyAmount(); + void copyTxID(); signals: void doubleClicked(const QModelIndex&); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp new file mode 100644 index 0000000000..a53aa65466 --- /dev/null +++ b/src/qt/walletframe.cpp @@ -0,0 +1,129 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#include "walletframe.h" +#include "bitcoingui.h" +#include "walletstack.h" + +#include <QVBoxLayout> +#include <QMessageBox> + +#include <stdio.h> + +WalletFrame::WalletFrame(BitcoinGUI *_gui) : + QFrame(_gui), + gui(_gui), + clientModel(0) +{ + // Leave HBox hook for adding a list view later + QHBoxLayout *walletFrameLayout = new QHBoxLayout(this); + walletStack = new WalletStack(this); + walletStack->setBitcoinGUI(gui); + walletFrameLayout->addWidget(walletStack); +} + +WalletFrame::~WalletFrame() +{ +} + +void WalletFrame::setClientModel(ClientModel *clientModel) +{ + this->clientModel = clientModel; + walletStack->setClientModel(clientModel); +} + +bool WalletFrame::addWallet(const QString& name, WalletModel *walletModel) +{ + return walletStack->addWallet(name, walletModel); +} + +bool WalletFrame::setCurrentWallet(const QString& name) +{ + // TODO: Check if valid name + walletStack->setCurrentWallet(name); + return true; +} + +void WalletFrame::removeAllWallets() +{ + walletStack->removeAllWallets(); +} + +bool WalletFrame::handleURI(const QString &uri) +{ + return walletStack->handleURI(uri); +} + +void WalletFrame::showOutOfSyncWarning(bool fShow) +{ + if (!walletStack) { + QMessageBox box; + box.setText("walletStack is null"); + box.exec(); + return; + } + walletStack->showOutOfSyncWarning(fShow); +} + +void WalletFrame::gotoOverviewPage() +{ + walletStack->gotoOverviewPage(); +} + +void WalletFrame::gotoHistoryPage() +{ + walletStack->gotoHistoryPage(); +} + +void WalletFrame::gotoAddressBookPage() +{ + walletStack->gotoAddressBookPage(); +} + +void WalletFrame::gotoReceiveCoinsPage() +{ + walletStack->gotoReceiveCoinsPage(); +} + +void WalletFrame::gotoSendCoinsPage() +{ + walletStack->gotoSendCoinsPage(); +} + +void WalletFrame::gotoSignMessageTab(QString addr) +{ + walletStack->gotoSignMessageTab(addr); +} + +void WalletFrame::gotoVerifyMessageTab(QString addr) +{ + walletStack->gotoSignMessageTab(addr); +} + +void WalletFrame::encryptWallet(bool status) +{ + walletStack->encryptWallet(status); +} + +void WalletFrame::backupWallet() +{ + walletStack->backupWallet(); +} + +void WalletFrame::changePassphrase() +{ + walletStack->changePassphrase(); +} + +void WalletFrame::unlockWallet() +{ + walletStack->unlockWallet(); +} + +void WalletFrame::setEncryptionStatus() +{ + walletStack->setEncryptionStatus(); +} diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h new file mode 100644 index 0000000000..5b4baf7255 --- /dev/null +++ b/src/qt/walletframe.h @@ -0,0 +1,73 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#ifndef WALLETFRAME_H +#define WALLETFRAME_H + +#include <QFrame> + +class BitcoinGUI; +class ClientModel; +class WalletModel; +class WalletStack; + +class WalletFrame : public QFrame +{ + Q_OBJECT +public: + explicit WalletFrame(BitcoinGUI *_gui); + ~WalletFrame(); + + void setClientModel(ClientModel *clientModel); + + bool addWallet(const QString& name, WalletModel *walletModel); + bool setCurrentWallet(const QString& name); + + void removeAllWallets(); + + bool handleURI(const QString &uri); + + void showOutOfSyncWarning(bool fShow); + +private: + BitcoinGUI *gui; + ClientModel *clientModel; + WalletStack *walletStack; + +public slots: + /** Switch to overview (home) page */ + void gotoOverviewPage(); + /** Switch to history (transactions) page */ + void gotoHistoryPage(); + /** Switch to address book page */ + void gotoAddressBookPage(); + /** Switch to receive coins page */ + void gotoReceiveCoinsPage(); + /** Switch to send coins page */ + void gotoSendCoinsPage(); + + /** Show Sign/Verify Message dialog and switch to sign message tab */ + void gotoSignMessageTab(QString addr = ""); + /** Show Sign/Verify Message dialog and switch to verify message tab */ + void gotoVerifyMessageTab(QString addr = ""); + + /** Encrypt the wallet */ + void encryptWallet(bool status); + /** Backup the wallet */ + void backupWallet(); + /** Change encrypted wallet passphrase */ + void changePassphrase(); + /** Ask for passphrase to unlock wallet temporarily */ + void unlockWallet(); + + /** Set the encryption status as shown in the UI. + @param[in] status current encryption status + @see WalletModel::EncryptionStatus + */ + void setEncryptionStatus(); +}; + +#endif // WALLETFRAME_H
\ No newline at end of file diff --git a/src/qt/walletstack.cpp b/src/qt/walletstack.cpp new file mode 100644 index 0000000000..271d1c7924 --- /dev/null +++ b/src/qt/walletstack.cpp @@ -0,0 +1,155 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#include "walletstack.h" +#include "walletview.h" +#include "bitcoingui.h" + +#include <QMap> +#include <QMessageBox> + +WalletStack::WalletStack(QWidget *parent) : + QStackedWidget(parent), + clientModel(0), + bOutOfSync(true) +{ +} + +WalletStack::~WalletStack() +{ +} + +bool WalletStack::addWallet(const QString& name, WalletModel *walletModel) +{ + if (!gui || !clientModel || mapWalletViews.count(name) > 0) + return false; + + WalletView *walletView = new WalletView(this, gui); + walletView->setBitcoinGUI(gui); + walletView->setClientModel(clientModel); + walletView->setWalletModel(walletModel); + walletView->showOutOfSyncWarning(bOutOfSync); + addWidget(walletView); + mapWalletViews[name] = walletView; + return true; +} + +bool WalletStack::removeWallet(const QString& name) +{ + if (mapWalletViews.count(name) == 0) return false; + WalletView *walletView = mapWalletViews.take(name); + removeWidget(walletView); + return true; +} + +void WalletStack::removeAllWallets() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + removeWidget(i.value()); + mapWalletViews.clear(); +} + +bool WalletStack::handleURI(const QString &uri) +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (!walletView) return false; + + return walletView->handleURI(uri); +} + +void WalletStack::showOutOfSyncWarning(bool fShow) +{ + bOutOfSync = fShow; + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->showOutOfSyncWarning(fShow); +} + +void WalletStack::gotoOverviewPage() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoOverviewPage(); +} + +void WalletStack::gotoHistoryPage() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoHistoryPage(); +} + +void WalletStack::gotoAddressBookPage() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoAddressBookPage(); +} + +void WalletStack::gotoReceiveCoinsPage() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoReceiveCoinsPage(); +} + +void WalletStack::gotoSendCoinsPage() +{ + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoSendCoinsPage(); +} + +void WalletStack::gotoSignMessageTab(QString addr) +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->gotoSignMessageTab(addr); +} + +void WalletStack::gotoVerifyMessageTab(QString addr) +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->gotoVerifyMessageTab(addr); +} + +void WalletStack::encryptWallet(bool status) +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->encryptWallet(status); +} + +void WalletStack::backupWallet() +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->backupWallet(); +} + +void WalletStack::changePassphrase() +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->changePassphrase(); +} + +void WalletStack::unlockWallet() +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->unlockWallet(); +} + +void WalletStack::setEncryptionStatus() +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->setEncryptionStatus(); +} + +void WalletStack::setCurrentWallet(const QString& name) +{ + if (mapWalletViews.count(name) == 0) return; + WalletView *walletView = mapWalletViews.value(name); + setCurrentWidget(walletView); + walletView->setEncryptionStatus(); +} diff --git a/src/qt/walletstack.h b/src/qt/walletstack.h new file mode 100644 index 0000000000..f3485816e9 --- /dev/null +++ b/src/qt/walletstack.h @@ -0,0 +1,102 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#ifndef WALLETSTACK_H +#define WALLETSTACK_H + +#include <QStackedWidget> +#include <QMap> +#include <boost/shared_ptr.hpp> + +class BitcoinGUI; +class TransactionTableModel; +class ClientModel; +class WalletModel; +class WalletView; +class TransactionView; +class OverviewPage; +class AddressBookPage; +class SendCoinsDialog; +class SignVerifyMessageDialog; +class Notificator; +class RPCConsole; + +class CWalletManager; + +QT_BEGIN_NAMESPACE +class QLabel; +class QModelIndex; +QT_END_NAMESPACE + +/* + WalletStack class. This class is a container for WalletView instances. It takes the place of centralWidget. + It was added to support multiple wallet functionality. It communicates with both the client and the + wallet models to give the user an up-to-date view of the current core state. It manages all the wallet views + it contains and updates them accordingly. + */ +class WalletStack : public QStackedWidget +{ + Q_OBJECT +public: + explicit WalletStack(QWidget *parent = 0); + ~WalletStack(); + + void setBitcoinGUI(BitcoinGUI *gui) { this->gui = gui; } + + void setClientModel(ClientModel *clientModel) { this->clientModel = clientModel; } + + bool addWallet(const QString& name, WalletModel *walletModel); + bool removeWallet(const QString& name); + + void removeAllWallets(); + + bool handleURI(const QString &uri); + + void showOutOfSyncWarning(bool fShow); + +private: + BitcoinGUI *gui; + ClientModel *clientModel; + QMap<QString, WalletView*> mapWalletViews; + + bool bOutOfSync; + +public slots: + void setCurrentWallet(const QString& name); + + /** Switch to overview (home) page */ + void gotoOverviewPage(); + /** Switch to history (transactions) page */ + void gotoHistoryPage(); + /** Switch to address book page */ + void gotoAddressBookPage(); + /** Switch to receive coins page */ + void gotoReceiveCoinsPage(); + /** Switch to send coins page */ + void gotoSendCoinsPage(); + + /** Show Sign/Verify Message dialog and switch to sign message tab */ + void gotoSignMessageTab(QString addr = ""); + /** Show Sign/Verify Message dialog and switch to verify message tab */ + void gotoVerifyMessageTab(QString addr = ""); + + /** Encrypt the wallet */ + void encryptWallet(bool status); + /** Backup the wallet */ + void backupWallet(); + /** Change encrypted wallet passphrase */ + void changePassphrase(); + /** Ask for passphrase to unlock wallet temporarily */ + void unlockWallet(); + + /** Set the encryption status as shown in the UI. + @param[in] status current encryption status + @see WalletModel::EncryptionStatus + */ + void setEncryptionStatus(); +}; + +#endif // WALLETSTACK_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp new file mode 100644 index 0000000000..7dd36234c9 --- /dev/null +++ b/src/qt/walletview.cpp @@ -0,0 +1,354 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#include "walletview.h" +#include "bitcoingui.h" +#include "transactiontablemodel.h" +#include "addressbookpage.h" +#include "sendcoinsdialog.h" +#include "signverifymessagedialog.h" +#include "optionsdialog.h" +#include "aboutdialog.h" +#include "clientmodel.h" +#include "walletmodel.h" +#include "editaddressdialog.h" +#include "optionsmodel.h" +#include "transactiondescdialog.h" +#include "addresstablemodel.h" +#include "transactionview.h" +#include "overviewpage.h" +#include "bitcoinunits.h" +#include "guiconstants.h" +#include "askpassphrasedialog.h" +#include "guiutil.h" +#include "ui_interface.h" + +#include <QVBoxLayout> +#include <QActionGroup> +#include <QAction> +#include <QLabel> +#include <QDesktopServices> +#include <QFileDialog> + +WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui): + QStackedWidget(parent), + gui(_gui), + clientModel(0), + walletModel(0), + encryptWalletAction(0), + changePassphraseAction(0) +{ + // Create actions for the toolbar, menu bar and tray/dock icon + createActions(); + + // Create tabs + overviewPage = new OverviewPage(); + + transactionsPage = new QWidget(this); + QVBoxLayout *vbox = new QVBoxLayout(); + transactionView = new TransactionView(this); + vbox->addWidget(transactionView); + transactionsPage->setLayout(vbox); + + addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab); + + receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab); + + sendCoinsPage = new SendCoinsDialog(gui); + + signVerifyMessageDialog = new SignVerifyMessageDialog(gui); + + addWidget(overviewPage); + addWidget(transactionsPage); + addWidget(addressBookPage); + addWidget(receiveCoinsPage); + addWidget(sendCoinsPage); + + // Clicking on a transaction on the overview page simply sends you to transaction history page + connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage())); + connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); + + // Double-clicking on a transaction on the transaction history page shows details + connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails())); + + // Clicking on "Verify Message" in the address book sends you to the verify message tab + connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString))); + // Clicking on "Sign Message" in the receive coins page sends you to the sign message tab + connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString))); + + gotoOverviewPage(); +} + +WalletView::~WalletView() +{ +} + +void WalletView::createActions() +{ + QActionGroup *tabGroup = new QActionGroup(this); + + overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this); + overviewAction->setStatusTip(tr("Show general overview of wallet")); + overviewAction->setToolTip(overviewAction->statusTip()); + overviewAction->setCheckable(true); + overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); + tabGroup->addAction(overviewAction); + + sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this); + sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); + sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); + sendCoinsAction->setCheckable(true); + sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); + tabGroup->addAction(sendCoinsAction); + + receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this); + receiveCoinsAction->setStatusTip(tr("Show the list of addresses for receiving payments")); + receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); + receiveCoinsAction->setCheckable(true); + receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); + tabGroup->addAction(receiveCoinsAction); + + historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this); + historyAction->setStatusTip(tr("Browse transaction history")); + historyAction->setToolTip(historyAction->statusTip()); + historyAction->setCheckable(true); + historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); + tabGroup->addAction(historyAction); + + addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this); + addressBookAction->setStatusTip(tr("Edit the list of stored addresses and labels")); + addressBookAction->setToolTip(addressBookAction->statusTip()); + addressBookAction->setCheckable(true); + addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5)); + tabGroup->addAction(addressBookAction); + + connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); + connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); + connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); + connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); + connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage())); + + encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this); + encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet")); + encryptWalletAction->setCheckable(true); + backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); + backupWalletAction->setStatusTip(tr("Backup wallet to another location")); + changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this); + changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption")); + signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this); + signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them")); + verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this); + verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses")); + + exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this); + exportAction->setStatusTip(tr("Export the data in the current tab to a file")); + exportAction->setToolTip(exportAction->statusTip()); + + connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool))); + connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet())); + connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase())); + connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); + connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); +} + +void WalletView::setBitcoinGUI(BitcoinGUI *gui) +{ + this->gui = gui; +} + +void WalletView::setClientModel(ClientModel *clientModel) +{ + this->clientModel = clientModel; + if(clientModel) + { + overviewPage->setClientModel(clientModel); + addressBookPage->setOptionsModel(clientModel->getOptionsModel()); + receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel()); + } +} + +void WalletView::setWalletModel(WalletModel *walletModel) +{ + this->walletModel = walletModel; + if(walletModel) + { + // Receive and report messages from wallet thread + connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), gui, SLOT(message(QString,QString,unsigned int))); + + // Put transaction list in tabs + transactionView->setModel(walletModel); + overviewPage->setWalletModel(walletModel); + addressBookPage->setModel(walletModel->getAddressTableModel()); + receiveCoinsPage->setModel(walletModel->getAddressTableModel()); + sendCoinsPage->setModel(walletModel); + signVerifyMessageDialog->setModel(walletModel); + + setEncryptionStatus(); + connect(walletModel, SIGNAL(encryptionStatusChanged(int)), gui, SLOT(setEncryptionStatus(int))); + + // Balloon pop-up for new transaction + connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(incomingTransaction(QModelIndex,int,int))); + + // Ask for passphrase if needed + connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet())); + } +} + +void WalletView::incomingTransaction(const QModelIndex& parent, int start, int /*end*/) +{ + // Prevent balloon-spam when initial block download is in progress + if(!walletModel || !clientModel || clientModel->inInitialBlockDownload()) + return; + + TransactionTableModel *ttm = walletModel->getTransactionTableModel(); + + QString date = ttm->index(start, TransactionTableModel::Date, parent) + .data().toString(); + qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent) + .data(Qt::EditRole).toULongLong(); + QString type = ttm->index(start, TransactionTableModel::Type, parent) + .data().toString(); + QString address = ttm->index(start, TransactionTableModel::ToAddress, parent) + .data().toString(); + + gui->incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address); +} + +void WalletView::gotoOverviewPage() +{ + overviewAction->setChecked(true); + setCurrentWidget(overviewPage); + + exportAction->setEnabled(false); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); +} + +void WalletView::gotoHistoryPage() +{ + historyAction->setChecked(true); + setCurrentWidget(transactionsPage); + + exportAction->setEnabled(true); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); + connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked())); +} + +void WalletView::gotoAddressBookPage() +{ + addressBookAction->setChecked(true); + setCurrentWidget(addressBookPage); + + exportAction->setEnabled(true); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); + connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked())); +} + +void WalletView::gotoReceiveCoinsPage() +{ + receiveCoinsAction->setChecked(true); + setCurrentWidget(receiveCoinsPage); + + exportAction->setEnabled(true); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); + connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked())); +} + +void WalletView::gotoSendCoinsPage() +{ + sendCoinsAction->setChecked(true); + setCurrentWidget(sendCoinsPage); + + exportAction->setEnabled(false); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); +} + +void WalletView::gotoSignMessageTab(QString addr) +{ + // call show() in showTab_SM() + signVerifyMessageDialog->showTab_SM(true); + + if(!addr.isEmpty()) + signVerifyMessageDialog->setAddress_SM(addr); +} + +void WalletView::gotoVerifyMessageTab(QString addr) +{ + // call show() in showTab_VM() + signVerifyMessageDialog->showTab_VM(true); + + if(!addr.isEmpty()) + signVerifyMessageDialog->setAddress_VM(addr); +} + +bool WalletView::handleURI(const QString& strURI) +{ + // URI has to be valid + if (sendCoinsPage->handleURI(strURI)) + { + gotoSendCoinsPage(); + return true; + } + else + return false; +} + +void WalletView::showOutOfSyncWarning(bool fShow) +{ + overviewPage->showOutOfSyncWarning(fShow); +} + +void WalletView::setEncryptionStatus() +{ + gui->setEncryptionStatus(walletModel->getEncryptionStatus()); +} + +void WalletView::encryptWallet(bool status) +{ + if(!walletModel) + return; + AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt: + AskPassphraseDialog::Decrypt, this); + dlg.setModel(walletModel); + dlg.exec(); + + setEncryptionStatus(); +} + +void WalletView::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)) { + gui->message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."), + CClientUIInterface::MSG_ERROR); + } + else + gui->message(tr("Backup Successful"), tr("The wallet data was successfully saved to the new location."), + CClientUIInterface::MSG_INFORMATION); + } +} + +void WalletView::changePassphrase() +{ + AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this); + dlg.setModel(walletModel); + dlg.exec(); +} + +void WalletView::unlockWallet() +{ + if(!walletModel) + return; + // Unlock wallet when requested by wallet model + if(walletModel->getEncryptionStatus() == WalletModel::Locked) + { + AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this); + dlg.setModel(walletModel); + dlg.exec(); + } +} diff --git a/src/qt/walletview.h b/src/qt/walletview.h new file mode 100644 index 0000000000..38eb0227af --- /dev/null +++ b/src/qt/walletview.h @@ -0,0 +1,132 @@ +/* + * Qt4 bitcoin GUI. + * + * W.J. van der Laan 2011-2012 + * The Bitcoin Developers 2011-2013 + */ +#ifndef WALLETVIEW_H +#define WALLETVIEW_H + +#include <QStackedWidget> + +class BitcoinGUI; +class TransactionTableModel; +class ClientModel; +class WalletModel; +class TransactionView; +class OverviewPage; +class AddressBookPage; +class SendCoinsDialog; +class SignVerifyMessageDialog; +class Notificator; +class RPCConsole; + +QT_BEGIN_NAMESPACE +class QLabel; +class QModelIndex; +QT_END_NAMESPACE + +/* + WalletView class. This class represents the view to a single wallet. + It was added to support multiple wallet functionality. Each wallet gets its own WalletView instance. + It communicates with both the client and the wallet models to give the user an up-to-date view of the + current core state. +*/ +class WalletView : public QStackedWidget +{ + Q_OBJECT +public: + explicit WalletView(QWidget *parent, BitcoinGUI *_gui); + ~WalletView(); + + void setBitcoinGUI(BitcoinGUI *gui); + /** Set the client model. + The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic. + */ + void setClientModel(ClientModel *clientModel); + /** Set the wallet model. + The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending + functionality. + */ + void setWalletModel(WalletModel *walletModel); + + bool handleURI(const QString &uri); + + void showOutOfSyncWarning(bool fShow); + +private: + BitcoinGUI *gui; + ClientModel *clientModel; + WalletModel *walletModel; + + OverviewPage *overviewPage; + QWidget *transactionsPage; + AddressBookPage *addressBookPage; + AddressBookPage *receiveCoinsPage; + SendCoinsDialog *sendCoinsPage; + SignVerifyMessageDialog *signVerifyMessageDialog; + + QLabel *labelEncryptionIcon; + QLabel *labelConnectionsIcon; + QLabel *labelBlocksIcon; + QLabel *progressBarLabel; + + QAction *overviewAction; + QAction *historyAction; + QAction *quitAction; + QAction *sendCoinsAction; + QAction *addressBookAction; + QAction *signMessageAction; + QAction *verifyMessageAction; + QAction *aboutAction; + QAction *receiveCoinsAction; + QAction *optionsAction; + QAction *toggleHideAction; + QAction *exportAction; + QAction *encryptWalletAction; + QAction *backupWalletAction; + QAction *changePassphraseAction; + QAction *aboutQtAction; + QAction *openRPCConsoleAction; + + TransactionView *transactionView; + + /** Create the main UI actions. */ + void createActions(); + /** Create the menu bar and sub-menus. */ + +public slots: + /** Switch to overview (home) page */ + void gotoOverviewPage(); + /** Switch to history (transactions) page */ + void gotoHistoryPage(); + /** Switch to address book page */ + void gotoAddressBookPage(); + /** Switch to receive coins page */ + void gotoReceiveCoinsPage(); + /** Switch to send coins page */ + void gotoSendCoinsPage(); + + /** Show Sign/Verify Message dialog and switch to sign message tab */ + void gotoSignMessageTab(QString addr = ""); + /** Show Sign/Verify Message dialog and switch to verify message tab */ + void gotoVerifyMessageTab(QString addr = ""); + + /** Show incoming transaction notification for new transactions. + + The new items are those between start and end inclusive, under the given parent item. + */ + void incomingTransaction(const QModelIndex& parent, int start, int /*end*/); + /** Encrypt the wallet */ + void encryptWallet(bool status); + /** Backup the wallet */ + void backupWallet(); + /** Change encrypted wallet passphrase */ + void changePassphrase(); + /** Ask for passphrase to unlock wallet temporarily */ + void unlockWallet(); + + void setEncryptionStatus(); +}; + +#endif // WALLETVIEW_H diff --git a/src/test/alert_tests.cpp b/src/test/alert_tests.cpp new file mode 100644 index 0000000000..f7a11376d3 --- /dev/null +++ b/src/test/alert_tests.cpp @@ -0,0 +1,185 @@ +// +// Unit tests for alert system +// + +#include <boost/foreach.hpp> +#include <boost/test/unit_test.hpp> +#include <fstream> + +#include "alert.h" +#include "serialize.h" +#include "util.h" + +#if 0 +// +// alertTests contains 7 alerts, generated with this code: +// (SignAndSave code not shown, alert signing key is secret) +// +{ + CAlert alert; + alert.nRelayUntil = 60; + alert.nExpiration = 24 * 60 * 60; + alert.nID = 1; + alert.nCancel = 0; // cancels previous messages up to this ID number + alert.nMinVer = 0; // These versions are protocol versions + alert.nMaxVer = 70001; + alert.nPriority = 1; + alert.strComment = "Alert comment"; + alert.strStatusBar = "Alert 1"; + + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.insert(std::string("/Satoshi:0.1.0/")); + alert.strStatusBar = "Alert 1 for Satoshi 0.1.0"; + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.insert(std::string("/Satoshi:0.2.0/")); + alert.strStatusBar = "Alert 1 for Satoshi 0.1.0, 0.2.0"; + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.clear(); + ++alert.nID; + alert.nCancel = 1; + alert.nPriority = 100; + alert.strStatusBar = "Alert 2, cancels 1"; + SignAndSave(alert, "test/alertTests"); + + alert.nExpiration += 60; + ++alert.nID; + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.nMinVer = 11; + alert.nMaxVer = 22; + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.strStatusBar = "Alert 2 for Satoshi 0.1.0"; + alert.setSubVer.insert(std::string("/Satoshi:0.1.0/")); + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.nMinVer = 0; + alert.nMaxVer = 999999; + alert.strStatusBar = "Evil Alert'; /bin/ls; echo '"; + alert.setSubVer.clear(); + SignAndSave(alert, "test/alertTests"); +} +#endif + +struct ReadAlerts +{ + ReadAlerts() + { + std::string filename("alertTests"); + namespace fs = boost::filesystem; + fs::path testFile = fs::current_path() / "test" / "data" / filename; +#ifdef TEST_DATA_DIR + if (!fs::exists(testFile)) + { + testFile = fs::path(BOOST_PP_STRINGIZE(TEST_DATA_DIR)) / filename; + } +#endif + FILE* fp = fopen(testFile.string().c_str(), "rb"); + if (!fp) return; + + + CAutoFile filein = CAutoFile(fp, SER_DISK, CLIENT_VERSION); + if (!filein) return; + + try { + while (!feof(filein)) + { + CAlert alert; + filein >> alert; + alerts.push_back(alert); + } + } + catch (std::exception) { } + } + ~ReadAlerts() { } + + static std::vector<std::string> read_lines(boost::filesystem::path filepath) + { + std::vector<std::string> result; + + std::ifstream f(filepath.string().c_str()); + std::string line; + while (std::getline(f,line)) + result.push_back(line); + + return result; + } + + std::vector<CAlert> alerts; +}; + +BOOST_FIXTURE_TEST_SUITE(Alert_tests, ReadAlerts) + + +BOOST_AUTO_TEST_CASE(AlertApplies) +{ + SetMockTime(11); + + BOOST_FOREACH(const CAlert& alert, alerts) + { + BOOST_CHECK(alert.CheckSignature()); + } + // Matches: + BOOST_CHECK(alerts[0].AppliesTo(1, "")); + BOOST_CHECK(alerts[0].AppliesTo(70001, "")); + BOOST_CHECK(alerts[0].AppliesTo(1, "/Satoshi:11.11.11/")); + + BOOST_CHECK(alerts[1].AppliesTo(1, "/Satoshi:0.1.0/")); + BOOST_CHECK(alerts[1].AppliesTo(70001, "/Satoshi:0.1.0/")); + + BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.1.0/")); + BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.2.0/")); + + // Don't match: + BOOST_CHECK(!alerts[0].AppliesTo(-1, "")); + BOOST_CHECK(!alerts[0].AppliesTo(70002, "")); + + BOOST_CHECK(!alerts[1].AppliesTo(1, "")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.1.0")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(-1, "/Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(70002, "/Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.2.0/")); + + BOOST_CHECK(!alerts[2].AppliesTo(1, "/Satoshi:0.3.0/")); + + SetMockTime(0); +} + + +// This uses sh 'echo' to test the -alertnotify function, writing to a +// /tmp file. So skip it on Windows: +#ifndef WIN32 +BOOST_AUTO_TEST_CASE(AlertNotify) +{ + SetMockTime(11); + + boost::filesystem::path temp = GetTempPath() / "alertnotify.txt"; + boost::filesystem::remove(temp); + + mapArgs["-alertnotify"] = std::string("echo %s >> ") + temp.string(); + + BOOST_FOREACH(CAlert alert, alerts) + alert.ProcessAlert(false); + + std::vector<std::string> r = read_lines(temp); + BOOST_CHECK_EQUAL(r.size(), 4u); + BOOST_CHECK_EQUAL(r[0], "Alert 1"); + BOOST_CHECK_EQUAL(r[1], "Alert 2, cancels 1"); + BOOST_CHECK_EQUAL(r[2], "Alert 2, cancels 1"); + BOOST_CHECK_EQUAL(r[3], "Evil Alert; /bin/ls; echo "); // single-quotes should be removed + + boost::filesystem::remove(temp); + + SetMockTime(0); +} +#endif + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/alertTests b/src/test/data/alertTests Binary files differnew file mode 100644 index 0000000000..7fc4528961 --- /dev/null +++ b/src/test/data/alertTests |