diff options
48 files changed, 884 insertions, 651 deletions
diff --git a/doc/assets-attribution.txt b/doc/assets-attribution.txt index bf3114f7b8..75062f7fef 100644 --- a/doc/assets-attribution.txt +++ b/doc/assets-attribution.txt @@ -55,6 +55,6 @@ Site: https://bitcointalk.org/index.php?topic=32273.0 License: Public domain Icon: src/qt/res/icons/debugwindow.png -Designer: Based on icon from Turbomilk -Site: http://www.iconza.com/ -License: Free for commercial use +Designer: Vignoni David +Site: http://www.oxygen-icons.org/ +License: Oxygen icon theme is dual licensed. You may copy it under the Creative Common Attribution-ShareAlike 3.0 License or the GNU Library General Public License. diff --git a/src/base58.h b/src/base58.h index bc681a08ca..a4ff35c4a8 100644 --- a/src/base58.h +++ b/src/base58.h @@ -252,7 +252,7 @@ public: bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } }; -/** base58-encoded bitcoin addresses. +/** base58-encoded Bitcoin addresses. * Public-key-hash-addresses have version 0 (or 111 testnet). * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. * Script-hash-addresses have version 5 (or 196 testnet). diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index b32ca9f15d..24fa97588c 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -115,6 +115,21 @@ HexBits(unsigned int nBits) return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); } +static std::string +HelpRequiringPassphrase() +{ + return pwalletMain->IsCrypted() + ? "\nrequires wallet passphrase to be set with walletpassphrase first" + : ""; +} + +static inline void +EnsureWalletIsUnlocked() +{ + if (pwalletMain->IsLocked()) + throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); +} + enum DecomposeMode { DM_NONE = 0, DM_HASH, @@ -371,10 +386,7 @@ string CRPCTable::help(string strCommand) const const CRPCCommand *pcmd = mi->second; string strMethod = mi->first; // We already filter duplicates, but these deprecated screw up the sort order - if (strMethod == "getamountreceived" || - strMethod == "getallreceived" || - strMethod == "getblocknumber" || // deprecated - (strMethod.find("label") != string::npos)) + if (strMethod.find("label") != string::npos) continue; if (strCommand != "" && strMethod != strCommand) continue; @@ -421,10 +433,10 @@ Value stop(const Array& params, bool fHelp) if (fHelp || params.size() != 0) throw runtime_error( "stop\n" - "Stop bitcoin server."); + "Stop Bitcoin server."); // Shutdown will take long enough that the response should get back - QueueShutdown(); - return "bitcoin server stopping"; + uiInterface.QueueShutdown(); + return "Bitcoin server stopping"; } @@ -439,18 +451,6 @@ Value getblockcount(const Array& params, bool fHelp) } -// deprecated -Value getblocknumber(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getblocknumber\n" - "Deprecated. Use getblockcount."); - - return nBestHeight; -} - - Value getconnectioncount(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -577,7 +577,7 @@ Value getnewaddress(const Array& params, bool fHelp) if (fHelp || params.size() > 1) throw runtime_error( "getnewaddress [account]\n" - "Returns a new bitcoin address for receiving payments. " + "Returns a new Bitcoin address for receiving payments. " "If [account] is specified (recommended), it is added to the address book " "so payments received with the address will be credited to [account]."); @@ -644,7 +644,7 @@ Value getaccountaddress(const Array& params, bool fHelp) if (fHelp || params.size() != 1) throw runtime_error( "getaccountaddress <account>\n" - "Returns the current bitcoin address for receiving payments to this account."); + "Returns the current Bitcoin address for receiving payments to this account."); // Parse the account first so we don't generate a key if there's an error string strAccount = AccountFromValue(params[0]); @@ -667,7 +667,7 @@ Value setaccount(const Array& params, bool fHelp) CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); string strAccount; @@ -697,7 +697,7 @@ Value getaccount(const Array& params, bool fHelp) CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); string strAccount; map<CBitcoinAddress, string>::iterator mi = pwalletMain->mapAddressBook.find(address); @@ -746,19 +746,15 @@ Value settxfee(const Array& params, bool fHelp) Value sendtoaddress(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + if (fHelp || params.size() < 2 || params.size() > 4) throw runtime_error( "sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001"); + "<amount> is a real and is rounded to the nearest 0.00000001" + + HelpRequiringPassphrase()); CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); // Amount int64 nAmount = AmountFromValue(params[1]); @@ -787,8 +783,7 @@ Value signmessage(const Array& params, bool fHelp) "signmessage <bitcoinaddress> <message>\n" "Sign a message with the private key of an address"); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); string strAddress = params[0].get_str(); string strMessage = params[1].get_str(); @@ -856,7 +851,7 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); CScript scriptPubKey; if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); scriptPubKey.SetBitcoinAddress(address); if (!IsMine(*pwalletMain,scriptPubKey)) return (double)0.0; @@ -1069,20 +1064,16 @@ Value movecmd(const Array& params, bool fHelp) Value sendfrom(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) + if (fHelp || params.size() < 3 || params.size() > 6) throw runtime_error( "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) - throw runtime_error( - "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" - "<amount> is a real and is rounded to the nearest 0.00000001"); + "<amount> is a real and is rounded to the nearest 0.00000001" + + HelpRequiringPassphrase()); string strAccount = AccountFromValue(params[0]); CBitcoinAddress address(params[1].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); int64 nAmount = AmountFromValue(params[2]); int nMinDepth = 1; if (params.size() > 3) @@ -1095,8 +1086,7 @@ Value sendfrom(const Array& params, bool fHelp) if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) wtx.mapValue["to"] = params[5].get_str(); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -1114,15 +1104,11 @@ Value sendfrom(const Array& params, bool fHelp) Value sendmany(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + if (fHelp || params.size() < 2 || params.size() > 4) throw runtime_error( "sendmany <fromaccount> {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendmany <fromaccount> {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers"); + "amounts are double-precision floating point numbers" + + HelpRequiringPassphrase()); string strAccount = AccountFromValue(params[0]); Object sendTo = params[1].get_obj(); @@ -1143,7 +1129,7 @@ Value sendmany(const Array& params, bool fHelp) { CBitcoinAddress address(s.name_); if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid bitcoin address:")+s.name_); + throw JSONRPCError(-5, string("Invalid Bitcoin address:")+s.name_); if (setAddress.count(address)) throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); @@ -1157,8 +1143,7 @@ Value sendmany(const Array& params, bool fHelp) vecSend.push_back(make_pair(scriptPubKey, nAmount)); } - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -1187,7 +1172,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) { string msg = "addmultisigaddress <nrequired> <'[\"key\",\"key\"]'> [account]\n" "Add a nrequired-to-sign multisignature address to the wallet\"\n" - "each key is a bitcoin address or hex-encoded public key\n" + "each key is a Bitcoin address or hex-encoded public key\n" "If [account] is specified, assign address to [account]."; throw runtime_error(msg); } @@ -1211,7 +1196,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) { const std::string& ks = keys[i].get_str(); - // Case 1: bitcoin address and we have full public key: + // Case 1: Bitcoin address and we have full public key: CBitcoinAddress address(ks); if (address.IsValid()) { @@ -1757,17 +1742,13 @@ Value backupwallet(const Array& params, bool fHelp) Value keypoolrefill(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) + if (fHelp || params.size() > 0) throw runtime_error( "keypoolrefill\n" - "Fills the keypool, requires wallet passphrase to be set."); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) - throw runtime_error( - "keypoolrefill\n" - "Fills the keypool."); + "Fills the keypool." + + HelpRequiringPassphrase()); - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + EnsureWalletIsUnlocked(); pwalletMain->TopUpKeyPool(); @@ -1947,8 +1928,8 @@ Value encryptwallet(const Array& params, bool fHelp) // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: - QueueShutdown(); - return "wallet encrypted; bitcoin server stopping, restart to run with encrypted wallet"; + uiInterface.QueueShutdown(); + return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet"; } @@ -2257,7 +2238,6 @@ static const CRPCCommand vRPCCommands[] = { "help", &help, true }, { "stop", &stop, true }, { "getblockcount", &getblockcount, true }, - { "getblocknumber", &getblocknumber, true }, { "getconnectioncount", &getconnectioncount, true }, { "getdifficulty", &getdifficulty, true }, { "getgenerate", &getgenerate, true }, @@ -2640,7 +2620,7 @@ void ThreadRPCServer2(void* parg) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); - ThreadSafeMessageBox(strprintf( + uiInterface.ThreadSafeMessageBox(strprintf( _("%s, you must set a rpcpassword in the configuration file:\n %s\n" "It is recommended you use the following random password:\n" "rpcuser=bitcoinrpc\n" @@ -2650,8 +2630,8 @@ void ThreadRPCServer2(void* parg) strWhatAmI.c_str(), GetConfigFile().string().c_str(), EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return; } @@ -2670,9 +2650,9 @@ void ThreadRPCServer2(void* parg) } catch(boost::system::system_error &e) { - ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + uiInterface.ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return; } diff --git a/src/db.cpp b/src/db.cpp index 5703c94ee9..90442f77f2 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -96,6 +96,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) : pdb(NULL) dbenv.set_lk_max_locks(10000); dbenv.set_lk_max_objects(10000); dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug + dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1); dbenv.set_flags(DB_AUTO_COMMIT, 1); dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); ret = dbenv.open(pathDataDir.string().c_str(), @@ -216,7 +216,7 @@ public: if (!pdb) return false; DbTxn* ptxn = NULL; - int ret = dbenv.txn_begin(GetTxn(), &ptxn, DB_TXN_NOSYNC); + int ret = dbenv.txn_begin(GetTxn(), &ptxn, DB_TXN_WRITE_NOSYNC); if (!ptxn || ret != 0) return false; vTxn.push_back(ptxn); diff --git a/src/init.cpp b/src/init.cpp index 8079f247f6..4e193ffe3c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -13,6 +13,7 @@ #include <boost/filesystem/fstream.hpp> #include <boost/filesystem/convenience.hpp> #include <boost/interprocess/sync/file_lock.hpp> +#include <boost/algorithm/string/predicate.hpp> #ifndef WIN32 #include <signal.h> @@ -22,6 +23,7 @@ using namespace std; using namespace boost; CWallet* pwalletMain; +CClientUIInterface uiInterface; ////////////////////////////////////////////////////////////////////////////// // @@ -90,17 +92,6 @@ void HandleSIGTERM(int) // Start // #if !defined(QT_GUI) -int main(int argc, char* argv[]) -{ - bool fRet = false; - fRet = AppInit(argc, argv); - - if (fRet && fDaemon) - return 0; - - return 1; -} - bool AppInit(int argc, char* argv[]) { bool fRet = false; @@ -136,7 +127,7 @@ bool AppInit(int argc, char* argv[]) // Command-line RPC for (int i = 1; i < argc; i++) - if (!IsSwitchChar(argv[i][0]) && !(strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0)) + if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "bitcoin:")) fCommandLine = true; if (fCommandLine) @@ -156,18 +147,33 @@ bool AppInit(int argc, char* argv[]) Shutdown(NULL); return fRet; } + +extern void noui_connect(); +int main(int argc, char* argv[]) +{ + bool fRet = false; + + // Connect bitcoind signal handlers + noui_connect(); + + fRet = AppInit(argc, argv); + + if (fRet && fDaemon) + return 0; + + return 1; +} #endif bool static InitError(const std::string &str) { - ThreadSafeMessageBox(str, _("Bitcoin"), wxOK | wxMODAL); + uiInterface.ThreadSafeMessageBox(str, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::MODAL); return false; - } bool static InitWarning(const std::string &str) { - ThreadSafeMessageBox(str, _("Bitcoin"), wxOK | wxICON_EXCLAMATION | wxMODAL); + uiInterface.ThreadSafeMessageBox(str, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); return true; } @@ -352,7 +358,7 @@ bool AppInit2() return false; } - // Make sure only a single bitcoin process is using the data directory. + // Make sure only a single Bitcoin process is using the data directory. boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. if (file) fclose(file); @@ -365,10 +371,10 @@ bool AppInit2() // Load data files // if (fDaemon) - fprintf(stdout, "bitcoin server starting\n"); + fprintf(stdout, "Bitcoin server starting\n"); int64 nStart; - InitMessage(_("Loading addresses...")); + uiInterface.InitMessage(_("Loading addresses...")); printf("Loading addresses...\n"); nStart = GetTimeMillis(); @@ -381,7 +387,7 @@ bool AppInit2() printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n", addrman.size(), GetTimeMillis() - nStart); - InitMessage(_("Loading block index...")); + uiInterface.InitMessage(_("Loading block index...")); printf("Loading block index...\n"); nStart = GetTimeMillis(); if (!LoadBlockIndex()) @@ -407,7 +413,7 @@ bool AppInit2() } } - InitMessage(_("Loading wallet...")); + uiInterface.InitMessage(_("Loading wallet...")); printf("Loading wallet...\n"); nStart = GetTimeMillis(); bool fFirstRun; @@ -475,14 +481,14 @@ bool AppInit2() } if (pindexBest != pindexRescan) { - InitMessage(_("Rescanning...")); + uiInterface.InitMessage(_("Rescanning...")); printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); pwalletMain->ScanForWalletTransactions(pindexRescan, true); printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart); } - InitMessage(_("Done loading")); + uiInterface.InitMessage(_("Done loading")); printf("Done loading\n"); //// debug print @@ -498,7 +504,7 @@ bool AppInit2() // Add wallet transactions that aren't already in a block to mapTransactions pwalletMain->ReacceptWalletTransactions(); - // Note: Bitcoin-QT stores several settings in the wallet, so we want + // Note: Bitcoin-Qt stores several settings in the wallet, so we want // to load the wallet BEFORE parsing command-line arguments, so // the command-line/bitcoin.conf settings override GUI setting. @@ -651,7 +657,7 @@ bool AppInit2() if (!ParseMoney(mapArgs["-paytxfee"], nTransactionFee)) return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s'"), mapArgs["-paytxfee"].c_str())); if (nTransactionFee > 0.25 * COIN) - InitWarning(_("Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction.")); + InitWarning(_("Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction.")); } // diff --git a/src/init.h b/src/init.h index 76090d7602..6159ededa5 100644 --- a/src/init.h +++ b/src/init.h @@ -10,7 +10,6 @@ extern CWallet* pwalletMain; void Shutdown(void* parg); -bool AppInit(int argc, char* argv[]); bool AppInit2(); std::string HelpMessage(); diff --git a/src/keystore.cpp b/src/keystore.cpp index 3cb95767bb..c56e820e0f 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -73,6 +73,20 @@ bool CCryptoKeyStore::SetCrypted() return true; } +bool CCryptoKeyStore::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) { { @@ -99,6 +113,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) } vMasterKey = vMasterKeyIn; } + NotifyStatusChanged(this); return true; } diff --git a/src/keystore.h b/src/keystore.h index d3d7e8adda..479d6c5a2e 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -8,6 +8,7 @@ #include "crypter.h" #include "sync.h" #include "base58.h" +#include <boost/signals2/signal.hpp> class CScript; @@ -143,18 +144,7 @@ public: return result; } - bool Lock() - { - if (!SetCrypted()) - return false; - - { - LOCK(cs_KeyStore); - vMasterKey.clear(); - } - - return true; - } + bool Lock(); virtual bool AddCryptedKey(const std::vector<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); bool AddKey(const CKey& key); @@ -185,6 +175,11 @@ public: mi++; } } + + /* Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 53872d28a4..9b38d9efba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -947,7 +947,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) { bnBestInvalidWork = pindexNew->bnChainWork; CTxDB().WriteBestInvalidWork(bnBestInvalidWork); - MainFrameRepaint(); + uiInterface.NotifyBlocksChanged(); } printf("InvalidChainFound: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str()); printf("InvalidChainFound: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); @@ -1648,7 +1648,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) hashPrevBestCoinBase = vtx[0].GetHash(); } - MainFrameRepaint(); + uiInterface.NotifyBlocksChanged(); return true; } @@ -1852,15 +1852,15 @@ bool CheckDiskSpace(uint64 nAdditionalBytes) { uint64 nFreeBytesAvailable = filesystem::space(GetDataDir()).available; - // Check for 15MB because database could create another 10MB log file at any time - if (nFreeBytesAvailable < (uint64)15000000 + nAdditionalBytes) + // Check for nMinDiskSpace bytes (currently 50MB) + if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes) { fShutdown = true; - string strMessage = _("Warning: Disk space is low "); + string strMessage = _("Warning: Disk space is low"); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage, "Bitcoin", wxOK | wxICON_EXCLAMATION | wxMODAL); - QueueShutdown(); + uiInterface.ThreadSafeMessageBox(strMessage, "Bitcoin", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return false; } return true; @@ -2177,6 +2177,18 @@ string GetWarnings(string strFor) return "error"; } +CAlert CAlert::getAlertByHash(const uint256 &hash) +{ + CAlert retval; + { + LOCK(cs_mapAlerts); + map<uint256, CAlert>::iterator mi = mapAlerts.find(hash); + if(mi != mapAlerts.end()) + retval = mi->second; + } + return retval; +} + bool CAlert::ProcessAlert() { if (!CheckSignature()) @@ -2193,11 +2205,13 @@ bool CAlert::ProcessAlert() if (Cancels(alert)) { printf("cancelling alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); mapAlerts.erase(mi++); } else if (!alert.IsInEffect()) { printf("expiring alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); mapAlerts.erase(mi++); } else @@ -2217,10 +2231,12 @@ bool CAlert::ProcessAlert() // Add to mapAlerts mapAlerts.insert(make_pair(GetHash(), *this)); + // Notify UI if it applies to me + if(AppliesToMe()) + uiInterface.NotifyAlertChanged(GetHash(), CT_NEW); } printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); - MainFrameRepaint(); return true; } diff --git a/src/main.h b/src/main.h index f879b73130..ac5ba254ce 100644 --- a/src/main.h +++ b/src/main.h @@ -71,8 +71,8 @@ extern unsigned char pchMessageStart[4]; // Settings extern int64 nTransactionFee; - - +// Minimum disk space required - used in CheckDiskSpace() +static const uint64 nMinDiskSpace = 52428800; class CReserveKey; @@ -1574,6 +1574,11 @@ public: } bool ProcessAlert(); + + /* + * Get copy of (active) alert object by hash. Returns a null alert if it is not found. + */ + static CAlert getAlertByHash(const uint256 &hash); }; class CTxMemPool diff --git a/src/net.cpp b/src/net.cpp index bff0dc82ab..4c795554a9 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -705,7 +705,7 @@ void ThreadSocketHandler2(void* parg) if (vNodes.size() != nPrevNodeCount) { nPrevNodeCount = vNodes.size(); - MainFrameRepaint(); + uiInterface.NotifyNumConnectionsChanged(vNodes.size()); } @@ -1760,7 +1760,7 @@ bool BindListenPort(const CService &addrBind, string& strError) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) - strError = strprintf(_("Unable to bind to %s on this computer. Bitcoin is probably already running."), addrBind.ToString().c_str()); + strError = strprintf(_("Unable to bind to %s on this computer. Bitcoin is probably already running."), addrBind.ToString().c_str()); else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %d, %s)"), addrBind.ToString().c_str(), nErr, strerror(nErr)); printf("%s\n", strError.c_str()); diff --git a/src/noui.cpp b/src/noui.cpp index 6d984680b1..3ba7e729f5 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -3,42 +3,33 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "ui_interface.h" +#include "init.h" +#include "bitcoinrpc.h" #include <string> -#include "init.h" -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static int noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool noui_ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { return true; } -void MainFrameRepaint() -{ -} - -void AddressBookRepaint() -{ -} - -void InitMessage(const std::string &message) -{ -} - -std::string _(const char* psz) -{ - return psz; -} - -void QueueShutdown() +static void noui_QueueShutdown() { // Without UI, Shutdown can simply be started in a new thread CreateThread(Shutdown, NULL); } +void noui_connect() +{ + // Connect bitcoind signal handlers + uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(noui_ThreadSafeAskFee); + uiInterface.QueueShutdown.connect(noui_QueueShutdown); +} diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d314e62b5a..c207987561 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -132,6 +132,10 @@ void AddressBookPage::setModel(AddressTableModel *model) connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged())); + // Select row for newly created address + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(selectNewAddress(QModelIndex,int,int))); + if(mode == ForSending) { // Auto-select first row when in sending mode @@ -193,20 +197,11 @@ void AddressBookPage::on_newAddressButton_clicked() EditAddressDialog dlg( tab == SendingTab ? EditAddressDialog::NewSendingAddress : - EditAddressDialog::NewReceivingAddress); + EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if(dlg.exec()) { - // Select row for newly created address - QString address = dlg.getAddress(); - QModelIndexList lst = proxyModel->match(proxyModel->index(0, - AddressTableModel::Address, QModelIndex()), - Qt::EditRole, address, 1, Qt::MatchExactly); - if(!lst.isEmpty()) - { - ui->tableView->setFocus(); - ui->tableView->selectRow(lst.at(0).row()); - } + newAddressToSelect = dlg.getAddress(); } } @@ -338,3 +333,15 @@ void AddressBookPage::contextualMenu(const QPoint &point) contextMenu->exec(QCursor::pos()); } } + +void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int end) +{ + QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); + if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) + { + // Select row of newly created address, once + ui->tableView->setFocus(); + ui->tableView->selectRow(idx.row()); + newAddressToSelect.clear(); + } +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b2cf2db979..b2e91c7cb2 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -13,6 +13,7 @@ class QTableView; class QItemSelection; class QSortFilterProxyModel; class QMenu; +class QModelIndex; QT_END_NAMESPACE /** Widget that shows a list of sending or receiving addresses. @@ -51,6 +52,7 @@ private: QSortFilterProxyModel *proxyModel; QMenu *contextMenu; QAction *deleteAction; + QString newAddressToSelect; private slots: void on_deleteButton_clicked(); @@ -67,6 +69,9 @@ private slots: void onCopyLabelAction(); /** Edit currently selected address entry */ void onEditAction(); + + /** New entry/entries were added to address table */ + void selectNewAddress(const QModelIndex &parent, int begin, int end); }; #endif // ADDRESSBOOKDIALOG_H diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 7b95f51c04..75ea2c12c5 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -26,20 +26,36 @@ struct AddressTableEntry type(type), label(label), address(address) {} }; +struct AddressTableEntryLessThan +{ + bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const + { + return a.address < b.address; + } + bool operator()(const AddressTableEntry &a, const QString &b) const + { + return a.address < b; + } + bool operator()(const QString &a, const AddressTableEntry &b) const + { + return a < b.address; + } +}; + // Private implementation class AddressTablePriv { public: CWallet *wallet; QList<AddressTableEntry> cachedAddressTable; + AddressTableModel *parent; - AddressTablePriv(CWallet *wallet): - wallet(wallet) {} + AddressTablePriv(CWallet *wallet, AddressTableModel *parent): + wallet(wallet), parent(parent) {} void refreshAddressTable() { cachedAddressTable.clear(); - { LOCK(wallet->cs_wallet); BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook) @@ -54,6 +70,53 @@ public: } } + void updateEntry(const QString &address, const QString &label, bool isMine, int status) + { + // Find address / label in model + QList<AddressTableEntry>::iterator lower = qLowerBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + QList<AddressTableEntry>::iterator upper = qUpperBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + int lowerIndex = (lower - cachedAddressTable.begin()); + int upperIndex = (upper - cachedAddressTable.begin()); + bool inModel = (lower != upper); + AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; + + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); + break; + } + parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); + cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address)); + parent->endInsertRows(); + break; + case CT_UPDATED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); + break; + } + lower->type = newEntryType; + lower->label = label; + parent->emitDataChanged(lowerIndex); + break; + case CT_DELETED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); + break; + } + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedAddressTable.erase(lower, upper); + parent->endRemoveRows(); + break; + } + } + int size() { return cachedAddressTable.size(); @@ -76,7 +139,7 @@ AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) : QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) { columns << tr("Label") << tr("Address"); - priv = new AddressTablePriv(wallet); + priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } @@ -158,7 +221,6 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu { case Label: wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString()); - rec->label = value.toString(); break; case Address: // Refuse to set invalid address, set error status and return false @@ -177,12 +239,9 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu // Add new entry with new address wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString()); } - - rec->address = value.toString(); } break; } - emit dataChanged(index, index); return true; } @@ -232,12 +291,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & pa } } -void AddressTableModel::update() +void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status) { // Update address book model from Bitcoin core - beginResetModel(); - priv->refreshAddressTable(); - endResetModel(); + priv->updateEntry(address, label, isMine, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) @@ -341,3 +398,7 @@ int AddressTableModel::lookupAddress(const QString &address) const } } +void AddressTableModel::emitDataChanged(int idx) +{ + emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); +} diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 7fd07cfb81..42974e3e1f 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -74,13 +74,18 @@ private: QStringList columns; EditStatus editStatus; + /** Notify listeners that data changed. */ + void emitDataChanged(int index); + signals: void defaultAddressChanged(const QString &address); public slots: - /* Update address list from core. Invalidates any indices. + /* Update address list from core. */ - void update(); + void updateEntry(const QString &address, const QString &label, bool isMine, int status); + + friend class AddressTablePriv; }; #endif // ADDRESSTABLEMODEL_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 9452f7e1e3..bdc6ea6ffd 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -21,6 +21,7 @@ #include <QLibraryInfo> #include <boost/interprocess/ipc/message_queue.hpp> +#include <boost/algorithm/string/predicate.hpp> #if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED) #define _BITCOIN_QT_PLUGINS_INCLUDED @@ -36,15 +37,13 @@ Q_IMPORT_PLUGIN(qtaccessiblewidgets) // Need a global reference for the notifications to find the GUI static BitcoinGUI *guiref; static QSplashScreen *splashref; -static WalletModel *walletmodel; -static ClientModel *clientmodel; -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static void ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { // Message from network thread if(guiref) { - bool modal = (style & wxMODAL); + bool modal = (style & CClientUIInterface::MODAL); // in case of modal message, use blocking connection to wait for user to click OK QMetaObject::invokeMethod(guiref, "error", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, @@ -57,10 +56,9 @@ int ThreadSafeMessageBox(const std::string& message, const std::string& caption, printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); } - return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { if(!guiref) return false; @@ -75,7 +73,7 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) return payFee; } -void ThreadSafeHandleURI(const std::string& strURI) +static void ThreadSafeHandleURI(const std::string& strURI) { if(!guiref) return; @@ -84,21 +82,7 @@ void ThreadSafeHandleURI(const std::string& strURI) Q_ARG(QString, QString::fromStdString(strURI))); } -void MainFrameRepaint() -{ - if(clientmodel) - QMetaObject::invokeMethod(clientmodel, "update", Qt::QueuedConnection); - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "update", Qt::QueuedConnection); -} - -void AddressBookRepaint() -{ - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "updateAddressList", Qt::QueuedConnection); -} - -void InitMessage(const std::string &message) +static void InitMessage(const std::string &message) { if(splashref) { @@ -107,7 +91,7 @@ void InitMessage(const std::string &message) } } -void QueueShutdown() +static void QueueShutdown() { QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); } @@ -115,7 +99,7 @@ void QueueShutdown() /* Translate string to current locale using Qt. */ -std::string _(const char* psz) +static std::string Translate(const char* psz) { return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } @@ -147,14 +131,14 @@ HelpMessageBox::HelpMessageBox(QWidget *parent): QMessageBox(parent) { header = tr("Bitcoin-Qt") + " " + tr("version") + " " + - QString::fromStdString(FormatFullVersion()) + "\n\n" + + QString::fromStdString(FormatFullVersion()) + "\n\n" + tr("Usage:") + "\n" + - " bitcoin-qt [options] " + "\n"; + " bitcoin-qt [" + tr("options") + "] " + "\n"; coreOptions = QString::fromStdString(HelpMessage()); uiOptions = tr("UI options") + ":\n" + - " -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" + - " -min " + tr("Start minimized") + "\n" + - " -splash " + tr("Show splash screen on startup (default: 1)") + "\n"; + " -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" + + " -min " + tr("Start minimized") + "\n" + + " -splash " + tr("Show splash screen on startup (default: 1)") + "\n"; setWindowTitle(tr("Bitcoin-Qt")); setTextFormat(Qt::PlainText); @@ -177,9 +161,6 @@ void HelpMessageBox::exec() #endif } -#ifdef WIN32 -#define strncasecmp strnicmp -#endif #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -189,7 +170,7 @@ int main(int argc, char *argv[]) // Do this early as we don't want to bother initializing if we are just calling IPC for (int i = 1; i < argc; i++) { - if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0) + if (boost::algorithm::istarts_with(argv[i], "bitcoin:")) { const char *strURI = argv[i]; try { @@ -266,6 +247,14 @@ int main(int argc, char *argv[]) if (translator.load(lang_territory, ":/translations/")) app.installTranslator(&translator); + // Subscribe to global signals from core + uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee); + uiInterface.ThreadSafeHandleURI.connect(ThreadSafeHandleURI); + uiInterface.InitMessage.connect(InitMessage); + uiInterface.QueueShutdown.connect(QueueShutdown); + uiInterface.Translate.connect(Translate); + // Show help message immediately after parsing command-line options (for "-lang") and setting locale, // but before showing splash screen. if (mapArgs.count("-?") || mapArgs.count("--help")) @@ -307,9 +296,7 @@ int main(int argc, char *argv[]) splash.finish(&window); ClientModel clientModel(&optionsModel); - clientmodel = &clientModel; WalletModel walletModel(pwalletMain, &optionsModel); - walletmodel = &walletModel; window.setClientModel(&clientModel); window.setWalletModel(&walletModel); @@ -323,17 +310,16 @@ int main(int argc, char *argv[]) { window.show(); } +#if !defined(MAC_OSX) && !defined(WIN32) +// TODO: implement qtipcserver.cpp for Mac and Windows // Place this here as guiref has to be defined if we dont want to lose URIs ipcInit(); -#if !defined(MAC_OSX) && !defined(WIN32) -// TODO: implement qtipcserver.cpp for Mac and Windows - // Check for URI in argv for (int i = 1; i < argc; i++) { - if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0) + if (boost::algorithm::istarts_with(argv[i], "bitcoin:")) { const char *strURI = argv[i]; try { @@ -351,8 +337,6 @@ int main(int argc, char *argv[]) window.setClientModel(0); window.setWalletModel(0); guiref = 0; - clientmodel = 0; - walletmodel = 0; } Shutdown(NULL); } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 2ad1ead3ee..9deaa4b6d3 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -207,7 +207,7 @@ void BitcoinGUI::createActions() tabGroup->addAction(receiveCoinsAction); sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this); - sendCoinsAction->setToolTip(tr("Send coins to a bitcoin address")); + sendCoinsAction->setToolTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setCheckable(true); sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); @@ -243,7 +243,7 @@ void BitcoinGUI::createActions() aboutQtAction->setToolTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this); - optionsAction->setToolTip(tr("Modify configuration options for bitcoin")); + optionsAction->setToolTip(tr("Modify configuration options for Bitcoin")); optionsAction->setMenuRole(QAction::PreferencesRole); toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("Show/Hide &Bitcoin"), this); toggleHideAction->setToolTip(tr("Show or hide the Bitcoin window")); @@ -350,11 +350,11 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) setNumConnections(clientModel->getNumConnections()); connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); - setNumBlocks(clientModel->getNumBlocks()); - connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers()); + connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); // Report errors from network/worker thread - connect(clientModel, SIGNAL(error(QString,QString, bool)), this, SLOT(error(QString,QString,bool))); + connect(clientModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool))); rpcConsole->setClientModel(clientModel); } @@ -426,7 +426,7 @@ void BitcoinGUI::createTrayIcon() trayIconMenu->addAction(quitAction); #endif - notificator = new Notificator(tr("bitcoin-qt"), trayIcon); + notificator = new Notificator(qApp->applicationName(), trayIcon); } #ifndef Q_WS_MAC @@ -434,7 +434,7 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if(reason == QSystemTrayIcon::Trigger) { - // Click on system tray icon triggers "show/hide bitcoin" + // Click on system tray icon triggers "show/hide Bitcoin" toggleHideAction->trigger(); } } @@ -493,7 +493,7 @@ void BitcoinGUI::setNumConnections(int count) labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count)); } -void BitcoinGUI::setNumBlocks(int count) +void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks) { // don't show / hide progressBar and it's label if we have no connection(s) to the network if (!clientModel || clientModel->getNumConnections() == 0) @@ -504,7 +504,6 @@ void BitcoinGUI::setNumBlocks(int count) return; } - int nTotalBlocks = clientModel->getNumBlocksOfPeers(); QString tooltip; if(count < nTotalBlocks) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 88e6d064d7..8a7f6e541b 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -111,7 +111,7 @@ public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set number of blocks shown in the UI */ - void setNumBlocks(int count); + void setNumBlocks(int count, int countOfPeers); /** Set the encryption status as shown in the UI. @param[in] status current encryption status @see WalletModel::EncryptionStatus diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 85ab03612d..64fd2a9450 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -5,15 +5,30 @@ #include "transactiontablemodel.h" #include "main.h" -static const int64 nClientStartupTime = GetTime(); +#include "ui_interface.h" #include <QDateTime> +#include <QTimer> + +static const int64 nClientStartupTime = GetTime(); ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : QObject(parent), optionsModel(optionsModel), - cachedNumConnections(0), cachedNumBlocks(0) + cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0) { numBlocksAtStartup = -1; + + pollTimer = new QTimer(); + pollTimer->setInterval(MODEL_UPDATE_DELAY); + pollTimer->start(); + connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer())); + + subscribeToCoreSignals(); +} + +ClientModel::~ClientModel() +{ + unsubscribeFromCoreSignals(); } int ClientModel::getNumConnections() const @@ -37,27 +52,42 @@ QDateTime ClientModel::getLastBlockDate() const return QDateTime::fromTime_t(pindexBest->GetBlockTime()); } -void ClientModel::update() +void ClientModel::updateTimer() { - int newNumConnections = getNumConnections(); + // Some quantities (such as number of blocks) change so fast that we don't want to be notified for each change. + // Periodically check and update with a timer. int newNumBlocks = getNumBlocks(); - QString newStatusBar = getStatusBarWarnings(); + int newNumBlocksOfPeers = getNumBlocksOfPeers(); - if(cachedNumConnections != newNumConnections) - emit numConnectionsChanged(newNumConnections); - if(cachedNumBlocks != newNumBlocks || cachedStatusBar != newStatusBar) + if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers) + emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers); + + cachedNumBlocks = newNumBlocks; + cachedNumBlocksOfPeers = newNumBlocksOfPeers; +} + +void ClientModel::updateNumConnections(int numConnections) +{ + emit numConnectionsChanged(numConnections); +} + +void ClientModel::updateAlert(const QString &hash, int status) +{ + // Show error message notification for new alert + if(status == CT_NEW) { - // Simply emit a numBlocksChanged for now in case the status message changes, - // so that the view updates the status bar. - // TODO: It should send a notification. - // (However, this might generate looped notifications and needs to be thought through and tested carefully) - // error(tr("Network Alert"), newStatusBar); - emit numBlocksChanged(newNumBlocks); + uint256 hash_256; + hash_256.SetHex(hash.toStdString()); + CAlert alert = CAlert::getAlertByHash(hash_256); + if(!alert.IsNull()) + { + emit error(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), false); + } } - cachedNumConnections = newNumConnections; - cachedNumBlocks = newNumBlocks; - cachedStatusBar = newStatusBar; + // Emit a numBlocksChanged when the status message changes, + // so that the view recomputes and updates the status bar. + emit numBlocksChanged(getNumBlocks(), getNumBlocksOfPeers()); } bool ClientModel::isTestNet() const @@ -104,3 +134,41 @@ QDateTime ClientModel::formatClientStartupTime() const { return QDateTime::fromTime_t(nClientStartupTime); } + +// Handlers for core signals +static void NotifyBlocksChanged(ClientModel *clientmodel) +{ + // This notification is too frequent. Don't trigger a signal. + // Don't remove it, though, as it might be useful later. +} + +static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) +{ + // Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections); + QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + Q_ARG(int, newNumConnections)); +} + +static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void ClientModel::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} + +void ClientModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client + uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 67835db727..0349c389c5 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -10,6 +10,7 @@ class CWallet; QT_BEGIN_NAMESPACE class QDateTime; +class QTimer; QT_END_NAMESPACE /** Model for Bitcoin network client. */ @@ -18,6 +19,7 @@ class ClientModel : public QObject Q_OBJECT public: explicit ClientModel(OptionsModel *optionsModel, QObject *parent = 0); + ~ClientModel(); OptionsModel *getOptionsModel(); @@ -44,23 +46,26 @@ public: private: OptionsModel *optionsModel; - int cachedNumConnections; int cachedNumBlocks; - QString cachedStatusBar; + int cachedNumBlocksOfPeers; int numBlocksAtStartup; + QTimer *pollTimer; + + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); signals: void numConnectionsChanged(int count); - void numBlocksChanged(int count); + void numBlocksChanged(int count, int countOfPeers); //! Asynchronous error notification void error(const QString &title, const QString &message, bool modal); public slots: - -private slots: - void update(); + void updateTimer(); + void updateNumConnections(int numConnections); + void updateAlert(const QString &hash, int status); }; #endif // CLIENTMODEL_H diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index cecb8aecd7..0d88aa47cb 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -93,7 +93,7 @@ void EditAddressDialog::accept() break; case AddressTableModel::INVALID_ADDRESS: QMessageBox::warning(this, windowTitle(), - tr("The entered address \"%1\" is not a valid bitcoin address.").arg(ui->addressEdit->text()), + tr("The entered address \"%1\" is not a valid Bitcoin address.").arg(ui->addressEdit->text()), QMessageBox::Ok, QMessageBox::Ok); return; case AddressTableModel::WALLET_UNLOCK_FAILURE: diff --git a/src/qt/forms/messagepage.ui b/src/qt/forms/messagepage.ui index 7c8f3b5ad6..0b685aae1d 100644 --- a/src/qt/forms/messagepage.ui +++ b/src/qt/forms/messagepage.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>Sign Message Dialog</string> + <string>Sign Message</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index f30d5db35b..c5f9aae511 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -22,7 +22,7 @@ namespace GUIUtil QString dateTimeStr(const QDateTime &datetime); QString dateTimeStr(qint64 nTime); - // Render bitcoin addresses in monospace font + // Render Bitcoin addresses in monospace font QFont bitcoinAddressFont(); // Set up widgets for address and amounts diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index c3260217bb..7c6ad087cb 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -249,7 +249,7 @@ DisplayOptionsPage::DisplayOptionsPage(QWidget *parent): QHBoxLayout *lang_hbox = new QHBoxLayout(); lang_hbox->addSpacing(18); - QLabel *lang_label = new QLabel(tr("User Interface &Language: ")); + QLabel *lang_label = new QLabel(tr("User Interface &Language:")); lang_hbox->addWidget(lang_label); lang = new QValueComboBox(this); // Make list of languages @@ -270,7 +270,7 @@ DisplayOptionsPage::DisplayOptionsPage(QWidget *parent): QHBoxLayout *unit_hbox = new QHBoxLayout(); unit_hbox->addSpacing(18); - QLabel *unit_label = new QLabel(tr("&Unit to show amounts in: ")); + QLabel *unit_label = new QLabel(tr("&Unit to show amounts in:")); unit_hbox->addWidget(unit_label); unit = new QValueComboBox(this); unit->setModel(new BitcoinUnits(this)); @@ -354,7 +354,7 @@ NetworkOptionsPage::NetworkOptionsPage(QWidget *parent): QHBoxLayout *proxy_hbox = new QHBoxLayout(); proxy_hbox->addSpacing(18); - QLabel *proxy_ip_label = new QLabel(tr("Proxy &IP: ")); + QLabel *proxy_ip_label = new QLabel(tr("Proxy &IP:")); proxy_hbox->addWidget(proxy_ip_label); proxy_ip = new QLineEdit(); proxy_ip->setMaximumWidth(140); @@ -363,7 +363,7 @@ NetworkOptionsPage::NetworkOptionsPage(QWidget *parent): proxy_ip->setToolTip(tr("IP address of the proxy (e.g. 127.0.0.1)")); proxy_ip_label->setBuddy(proxy_ip); proxy_hbox->addWidget(proxy_ip); - QLabel *proxy_port_label = new QLabel(tr("&Port: ")); + QLabel *proxy_port_label = new QLabel(tr("&Port:")); proxy_hbox->addWidget(proxy_port_label); proxy_port = new QLineEdit(); proxy_port->setMaximumWidth(55); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 181dec4400..9f1c6447ae 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -24,7 +24,7 @@ void OptionsModel::Init() nTransactionFee = settings.value("nTransactionFee").toLongLong(); language = settings.value("language", "").toString(); - // These are shared with core bitcoin; we want + // These are shared with core Bitcoin; we want // command-line options to override the GUI settings: if (settings.contains("fUseUPnP")) SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool()); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 4315a33f8c..c0374689c6 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -3,8 +3,8 @@ #include <QAbstractListModel> -/** Interface from QT to configuration data structure for bitcoin client. - To QT, the options are presented as a list with the different options +/** Interface from Qt to configuration data structure for Bitcoin client. + To Qt, the options are presented as a list with the different options laid out vertically. This can be changed to a tree once the settings become sufficiently complex. diff --git a/src/qt/qtipcserver.cpp b/src/qt/qtipcserver.cpp index 06ada5aaca..3d7d90e902 100644 --- a/src/qt/qtipcserver.cpp +++ b/src/qt/qtipcserver.cpp @@ -31,7 +31,7 @@ void ipcThread(void* parg) ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(100); if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(strBuf, nSize)); Sleep(1000); } if (fShutdown) @@ -69,7 +69,7 @@ void ipcInit() ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(1); if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(strBuf, nSize)); } else break; diff --git a/src/qt/res/icons/debugwindow.png b/src/qt/res/icons/debugwindow.png Binary files differindex 065399afe7..1712adf0e7 100644 --- a/src/qt/res/icons/debugwindow.png +++ b/src/qt/res/icons/debugwindow.png diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 78dbcd7daa..7029ee33bc 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -109,7 +109,9 @@ RPCConsole::RPCConsole(QWidget *parent) : { ui->setupUi(this); -#ifndef WIN32 +#ifdef WIN32 + ui->openDebugLogfileButton->setIcon(QIcon(":/icons/export")); +#else // Show Debug logfile label and Open button only for Windows ui->labelDebugLogfile->setVisible(false); ui->openDebugLogfileButton->setVisible(false); @@ -155,7 +157,7 @@ void RPCConsole::setClientModel(ClientModel *model) { // Subscribe to information, replies, messages, errors connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); - connect(model, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); @@ -166,7 +168,7 @@ void RPCConsole::setClientModel(ClientModel *model) setNumConnections(model->getNumConnections()); ui->isTestNet->setChecked(model->isTestNet()); - setNumBlocks(model->getNumBlocks()); + setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers()); } } @@ -207,9 +209,9 @@ void RPCConsole::clear() "b { color: #006060; } " ); - message(CMD_REPLY, tr("Welcome to the Bitcoin RPC console.<br>" - "Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.<br>" - "Type <b>help</b> for an overview of available commands."), true); + message(CMD_REPLY, (tr("Welcome to the Bitcoin RPC console.") + "<br>" + + tr("Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.") + "<br>" + + tr("Type <b>help</b> for an overview of available commands.")), true); } void RPCConsole::message(int category, const QString &message, bool html) @@ -233,9 +235,10 @@ void RPCConsole::setNumConnections(int count) ui->numberOfConnections->setText(QString::number(count)); } -void RPCConsole::setNumBlocks(int count) +void RPCConsole::setNumBlocks(int count, int countOfPeers) { ui->numberOfBlocks->setText(QString::number(count)); + ui->totalBlocks->setText(QString::number(countOfPeers)); if(clientModel) { // If there is no current number available display N/A instead of 0, which can't ever be true diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 0a7b10f4a2..4b71cdb988 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -8,7 +8,7 @@ namespace Ui { } class ClientModel; -/** Local bitcoin RPC console. */ +/** Local Bitcoin RPC console. */ class RPCConsole: public QDialog { Q_OBJECT @@ -33,6 +33,7 @@ protected: private slots: void on_lineEdit_returnPressed(); void on_tabWidget_currentChanged(int index); + /** open the debug.log from the current datadir */ void on_openDebugLogfileButton_clicked(); public slots: @@ -41,7 +42,7 @@ public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set number of blocks shown in the UI */ - void setNumBlocks(int count); + void setNumBlocks(int count, int countOfPeers); /** Go forward or back in history */ void browseHistory(int offset); /** Scroll console view to end */ diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 53cfb409ff..017244ffd0 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -40,114 +40,111 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(); std::map<std::string, std::string> mapValue = wtx.mapValue; - if (showTransaction(wtx)) + if (nNet > 0 || wtx.IsCoinBase()) { - if (nNet > 0 || wtx.IsCoinBase()) + // + // Credit + // + BOOST_FOREACH(const CTxOut& txout, wtx.vout) { - // - // Credit - // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if(wallet->IsMine(txout)) { - if(wallet->IsMine(txout)) + TransactionRecord sub(hash, nTime); + CBitcoinAddress address; + sub.idx = parts.size(); // sequence number + sub.credit = txout.nValue; + if (wtx.IsCoinBase()) { - TransactionRecord sub(hash, nTime); - CBitcoinAddress address; - sub.idx = parts.size(); // sequence number - sub.credit = txout.nValue; - if (wtx.IsCoinBase()) - { - // Generated - sub.type = TransactionRecord::Generated; - } - else if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) - { - // Received by Bitcoin Address - sub.type = TransactionRecord::RecvWithAddress; - sub.address = address.ToString(); - } - else - { - // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction - sub.type = TransactionRecord::RecvFromOther; - sub.address = mapValue["from"]; - } - - parts.append(sub); + // Generated + sub.type = TransactionRecord::Generated; } + else if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) + { + // Received by Bitcoin Address + sub.type = TransactionRecord::RecvWithAddress; + sub.address = address.ToString(); + } + else + { + // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction + sub.type = TransactionRecord::RecvFromOther; + sub.address = mapValue["from"]; + } + + parts.append(sub); } } - else + } + else + { + bool fAllFromMe = true; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && wallet->IsMine(txin); + + bool fAllToMe = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && wallet->IsMine(txout); + + if (fAllFromMe && fAllToMe) { - bool fAllFromMe = true; - BOOST_FOREACH(const CTxIn& txin, wtx.vin) - fAllFromMe = fAllFromMe && wallet->IsMine(txin); + // Payment to self + int64 nChange = wtx.GetChange(); - bool fAllToMe = true; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - fAllToMe = fAllToMe && wallet->IsMine(txout); + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", + -(nDebit - nChange), nCredit - nChange)); + } + else if (fAllFromMe) + { + // + // Debit + // + int64 nTxFee = nDebit - wtx.GetValueOut(); - if (fAllFromMe && fAllToMe) + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { - // Payment to self - int64 nChange = wtx.GetChange(); + const CTxOut& txout = wtx.vout[nOut]; + TransactionRecord sub(hash, nTime); + sub.idx = parts.size(); - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange), nCredit - nChange)); - } - else if (fAllFromMe) - { - // - // Debit - // - int64 nTxFee = nDebit - wtx.GetValueOut(); + if(wallet->IsMine(txout)) + { + // Ignore parts sent to self, as this is usually the change + // from a transaction sent back to our own address. + continue; + } - for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, address)) { - const CTxOut& txout = wtx.vout[nOut]; - TransactionRecord sub(hash, nTime); - sub.idx = parts.size(); - - if(wallet->IsMine(txout)) - { - // Ignore parts sent to self, as this is usually the change - // from a transaction sent back to our own address. - continue; - } - - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address)) - { - // Sent to Bitcoin Address - sub.type = TransactionRecord::SendToAddress; - sub.address = address.ToString(); - } - else - { - // Sent to IP, or other non-address transaction like OP_EVAL - sub.type = TransactionRecord::SendToOther; - sub.address = mapValue["to"]; - } - - int64 nValue = txout.nValue; - /* Add fee to first output */ - if (nTxFee > 0) - { - nValue += nTxFee; - nTxFee = 0; - } - sub.debit = -nValue; - - parts.append(sub); + // Sent to Bitcoin Address + sub.type = TransactionRecord::SendToAddress; + sub.address = address.ToString(); } + else + { + // Sent to IP, or other non-address transaction like OP_EVAL + sub.type = TransactionRecord::SendToOther; + sub.address = mapValue["to"]; + } + + int64 nValue = txout.nValue; + /* Add fee to first output */ + if (nTxFee > 0) + { + nValue += nTxFee; + nTxFee = 0; + } + sub.debit = -nValue; + + parts.append(sub); } - else - { - // - // Mixed debit transaction, can't break down payees - // - parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); - } + } + else + { + // + // Mixed debit transaction, can't break down payees + // + parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); } } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 5f505f444e..d36bb495a0 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -9,6 +9,7 @@ #include "bitcoinunits.h" #include "wallet.h" +#include "ui_interface.h" #include <QLocale> #include <QList> @@ -66,15 +67,14 @@ public: */ void refreshWallet() { -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "refreshWallet"; -#endif + OutputDebugStringF("refreshWallet\n"); cachedWallet.clear(); { LOCK(wallet->cs_wallet); for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it) { - cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); + if(TransactionRecord::showTransaction(it->second)) + cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); } } } @@ -82,49 +82,55 @@ public: /* Update our model of the wallet incrementally, to synchronize our model of the wallet with that of the core. - Call with list of hashes of transactions that were added, removed or changed. + Call with transaction that was added, removed or changed. */ - void updateWallet(const QList<uint256> &updated) + void updateWallet(const uint256 &hash, int status) { - // Walk through updated transactions, update model as needed. -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "updateWallet"; -#endif - // Sort update list, and iterate through it in reverse, so that model updates - // can be emitted from end to beginning (so that earlier updates will not influence - // the indices of latter ones). - QList<uint256> updated_sorted = updated; - qSort(updated_sorted); - + OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status); { LOCK(wallet->cs_wallet); - for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx) + + // Find transaction in wallet + std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); + bool inWallet = mi != wallet->mapWallet.end(); + + // Find bounds of this transaction in model + QList<TransactionRecord>::iterator lower = qLowerBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + QList<TransactionRecord>::iterator upper = qUpperBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + int lowerIndex = (lower - cachedWallet.begin()); + int upperIndex = (upper - cachedWallet.begin()); + bool inModel = (lower != upper); + + // Determine whether to show transaction or not + bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second)); + + if(status == CT_UPDATED) { - const uint256 &hash = updated_sorted.at(update_idx); - // Find transaction in wallet - std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); - bool inWallet = mi != wallet->mapWallet.end(); - // Find bounds of this transaction in model - QList<TransactionRecord>::iterator lower = qLowerBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - QList<TransactionRecord>::iterator upper = qUpperBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - int lowerIndex = (lower - cachedWallet.begin()); - int upperIndex = (upper - cachedWallet.begin()); - - // Determine if transaction is in model already - bool inModel = false; - if(lower != upper) - { - inModel = true; - } + if(showTransaction && !inModel) + status = CT_NEW; /* Not in model, but want to show, treat as new */ + if(!showTransaction && inModel) + status = CT_DELETED; /* In model, but want to hide, treat as deleted */ + } -#ifdef WALLET_UPDATE_DEBUG - qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel - << lowerIndex << "-" << upperIndex; -#endif + OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n", + inWallet, inModel, lowerIndex, upperIndex, showTransaction, status); - if(inWallet && !inModel) + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n"); + break; + } + if(!inWallet) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n"); + break; + } + if(showTransaction) { // Added -- insert at the right position QList<TransactionRecord> toInsert = @@ -141,17 +147,22 @@ public: parent->endInsertRows(); } } - else if(!inWallet && inModel) - { - // Removed -- remove entire transaction from table - parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); - cachedWallet.erase(lower, upper); - parent->endRemoveRows(); - } - else if(inWallet && inModel) + break; + case CT_DELETED: + if(!inModel) { - // Updated -- nothing to do, status update will take care of this + OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n"); + break; } + // Removed -- remove entire transaction from table + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedWallet.erase(lower, upper); + parent->endRemoveRows(); + break; + case CT_UPDATED: + // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for + // visible transactions. + break; } } } @@ -209,14 +220,15 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren QAbstractTableModel(parent), wallet(wallet), walletModel(parent), - priv(new TransactionTablePriv(wallet, this)) + priv(new TransactionTablePriv(wallet, this)), + cachedNumBlocks(0) { columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount"); priv->refreshWallet(); QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(update())); + connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations())); timer->start(MODEL_UPDATE_DELAY); } @@ -225,29 +237,23 @@ TransactionTableModel::~TransactionTableModel() delete priv; } -void TransactionTableModel::update() +void TransactionTableModel::updateTransaction(const QString &hash, int status) { - QList<uint256> updated; + uint256 updated; + updated.SetHex(hash.toStdString()); - // Check if there are changes to wallet map - { - TRY_LOCK(wallet->cs_wallet, lockWallet); - if (lockWallet && !wallet->vWalletUpdated.empty()) - { - BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated) - { - updated.append(hash); - } - wallet->vWalletUpdated.clear(); - } - } + priv->updateWallet(updated, status); +} - if(!updated.empty()) +void TransactionTableModel::updateConfirmations() +{ + if(nBestHeight != cachedNumBlocks) { - priv->updateWallet(updated); - - // Status (number of confirmations) and (possibly) description - // columns changed for all rows. + cachedNumBlocks = nBestHeight; + // Blocks came in since last poll. + // Invalidate status (number of confirmations) and (possibly) description + // for all rows. Qt is smart enough to only actually request the data for the + // visible rows. emit dataChanged(index(0, Status), index(priv->size()-1, Status)); emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress)); } diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index db88a0604f..0aafa70915 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -60,6 +60,7 @@ private: WalletModel *walletModel; QStringList columns; TransactionTablePriv *priv; + int cachedNumBlocks; QString lookupAddress(const std::string &address, bool tooltip) const; QVariant addressColor(const TransactionRecord *wtx) const; @@ -72,8 +73,9 @@ private: QVariant txStatusDecoration(const TransactionRecord *wtx) const; QVariant txAddressDecoration(const TransactionRecord *wtx) const; -private slots: - void update(); +public slots: + void updateTransaction(const QString &hash, int status); + void updateConfirmations(); friend class TransactionTablePriv; }; diff --git a/src/qt/verifymessagedialog.cpp b/src/qt/verifymessagedialog.cpp index 1f82e2ac31..0bac24820c 100644 --- a/src/qt/verifymessagedialog.cpp +++ b/src/qt/verifymessagedialog.cpp @@ -25,7 +25,7 @@ VerifyMessageDialog::VerifyMessageDialog(AddressTableModel *addressModel, QWidge #if (QT_VERSION >= 0x040700) /* Do not move this to the XML file, Qt before 4.7 will choke on it */ ui->lnSig->setPlaceholderText(tr("Enter Bitcoin signature")); - ui->lnAddress->setPlaceholderText(tr("Click \"Apply\" to obtain address")); + ui->lnAddress->setPlaceholderText(tr("Click \"Verify Message\" to obtain address")); #endif GUIUtil::setupAddressWidget(ui->lnAddress, this); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index b9ccb06c09..b89c3dba33 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -18,6 +18,13 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p { addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(wallet, this); + + subscribeToCoreSignals(); +} + +WalletModel::~WalletModel() +{ + unsubscribeFromCoreSignals(); } qint64 WalletModel::getBalance() const @@ -40,30 +47,38 @@ int WalletModel::getNumTransactions() const return numTransactions; } -void WalletModel::update() +void WalletModel::updateStatus() +{ + EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + + if(cachedEncryptionStatus != newEncryptionStatus) + emit encryptionStatusChanged(newEncryptionStatus); +} + +void WalletModel::updateTransaction(const QString &hash, int status) { + if(transactionTableModel) + transactionTableModel->updateTransaction(hash, status); + + // Balance and number of transactions might have changed qint64 newBalance = getBalance(); qint64 newUnconfirmedBalance = getUnconfirmedBalance(); int newNumTransactions = getNumTransactions(); - EncryptionStatus newEncryptionStatus = getEncryptionStatus(); if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) emit balanceChanged(newBalance, newUnconfirmedBalance); - if(cachedNumTransactions != newNumTransactions) emit numTransactionsChanged(newNumTransactions); - if(cachedEncryptionStatus != newEncryptionStatus) - emit encryptionStatusChanged(newEncryptionStatus); - cachedBalance = newBalance; cachedUnconfirmedBalance = newUnconfirmedBalance; cachedNumTransactions = newNumTransactions; } -void WalletModel::updateAddressList() +void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) { - addressTableModel->update(); + if(addressTableModel) + addressTableModel->updateEntry(address, label, isMine, status); } bool WalletModel::validateAddress(const QString &address) @@ -139,7 +154,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie } return TransactionCreationFailed; } - if(!ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString())) + if(!uiInterface.ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString())) { return Aborted; } @@ -246,6 +261,47 @@ bool WalletModel::backupWallet(const QString &filename) return BackupWallet(*wallet, filename.toLocal8Bit().data()); } +// Handlers for core signals +static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) +{ + OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status) +{ + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", address.c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(address)), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); +} + +static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void WalletModel::subscribeToCoreSignals() +{ + // Connect signals to wallet + wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + +void WalletModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from wallet + wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 6c47f61bef..8b615ffe8e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -24,6 +24,7 @@ class WalletModel : public QObject Q_OBJECT public: explicit WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); + ~WalletModel(); enum StatusCode // Returned by sendCoins { @@ -118,6 +119,8 @@ private: qint64 cachedNumTransactions; EncryptionStatus cachedEncryptionStatus; + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); signals: // Signal that balance in wallet changed void balanceChanged(qint64 balance, qint64 unconfirmedBalance); @@ -137,8 +140,12 @@ signals: void error(const QString &title, const QString &message, bool modal); public slots: - void update(); - void updateAddressList(); + /* Wallet status might have changed */ + void updateStatus(); + /* New transaction, or transaction changed status */ + void updateTransaction(const QString &hash, int status); + /* New, updated or removed address book entry */ + void updateAddressBook(const QString &address, const QString &label, bool isMine, int status); }; diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 2db4882068..5fa24f638d 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -73,8 +73,6 @@ Value importprivkey(const Array& params, bool fHelp) pwalletMain->ReacceptWalletTransactions(); } - MainFrameRepaint(); - return Value::null; } @@ -88,7 +86,7 @@ Value dumpprivkey(const Array& params, bool fHelp) string strAddress = params[0].get_str(); CBitcoinAddress address; if (!address.SetString(strAddress)) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid Bitcoin address"); CSecret vchSecret; bool fCompressed; if (!pwalletMain->GetSecret(address, vchSecret, fCompressed)) diff --git a/src/sync.cpp b/src/sync.cpp index ec131734a7..f2403a43f2 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -3,8 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sync.h" +#include "util.h" - +#include <boost/foreach.hpp> #ifdef DEBUG_LOCKORDER // @@ -40,7 +41,7 @@ private: typedef std::vector< std::pair<void*, CLockLocation> > LockStack; -static boost::interprocess::interprocess_mutex dd_mutex; +static boost::mutex dd_mutex; static std::map<std::pair<void*, void*>, LockStack> lockorders; static boost::thread_specific_ptr<LockStack> lockstack; @@ -66,7 +67,6 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) { - bool fOrderOK = true; if (lockstack.get() == NULL) lockstack.reset(new LockStack); @@ -75,20 +75,21 @@ static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) (*lockstack).push_back(std::make_pair(c, locklocation)); - if (!fTry) BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, (*lockstack)) - { - if (i.first == c) break; - - std::pair<void*, void*> p1 = std::make_pair(i.first, c); - if (lockorders.count(p1)) - continue; - lockorders[p1] = (*lockstack); - - std::pair<void*, void*> p2 = std::make_pair(c, i.first); - if (lockorders.count(p2)) - { - potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]); - break; + if (!fTry) { + BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, (*lockstack)) { + if (i.first == c) break; + + std::pair<void*, void*> p1 = std::make_pair(i.first, c); + if (lockorders.count(p1)) + continue; + lockorders[p1] = (*lockstack); + + std::pair<void*, void*> p2 = std::make_pair(c, i.first); + if (lockorders.count(p2)) + { + potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]); + break; + } } } dd_mutex.unlock(); diff --git a/src/sync.h b/src/sync.h index d92f747ed1..dffe4f6ee8 100644 --- a/src/sync.h +++ b/src/sync.h @@ -5,19 +5,19 @@ #ifndef BITCOIN_SYNC_H #define BITCOIN_SYNC_H -#include <boost/interprocess/sync/interprocess_recursive_mutex.hpp> -#include <boost/interprocess/sync/scoped_lock.hpp> -#include <boost/interprocess/sync/interprocess_semaphore.hpp> -#include <boost/interprocess/sync/lock_options.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread/locks.hpp> +#include <boost/thread/condition_variable.hpp> /** Wrapped boost mutex: supports recursive locking, but no waiting */ -typedef boost::interprocess::interprocess_recursive_mutex CCriticalSection; +typedef boost::recursive_mutex CCriticalSection; /** Wrapped boost mutex: supports waiting but not recursive locking */ -typedef boost::interprocess::interprocess_mutex CWaitableCriticalSection; +typedef boost::mutex CWaitableCriticalSection; #ifdef DEBUG_LOCKORDER void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false); @@ -32,12 +32,12 @@ template<typename Mutex> class CMutexLock { private: - boost::interprocess::scoped_lock<Mutex> lock; + boost::unique_lock<Mutex> lock; public: void Enter(const char* pszName, const char* pszFile, int nLine) { - if (!lock.owns()) + if (!lock.owns_lock()) { EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex())); #ifdef DEBUG_LOCKCONTENTION @@ -55,7 +55,7 @@ public: void Leave() { - if (lock.owns()) + if (lock.owns_lock()) { lock.unlock(); LeaveCritical(); @@ -64,17 +64,17 @@ public: bool TryEnter(const char* pszName, const char* pszFile, int nLine) { - if (!lock.owns()) + if (!lock.owns_lock()) { EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true); lock.try_lock(); - if (!lock.owns()) + if (!lock.owns_lock()) LeaveCritical(); } - return lock.owns(); + return lock.owns_lock(); } - CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, boost::interprocess::defer_lock) + CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, boost::defer_lock) { if (fTry) TryEnter(pszName, pszFile, nLine); @@ -84,16 +84,16 @@ public: ~CMutexLock() { - if (lock.owns()) + if (lock.owns_lock()) LeaveCritical(); } operator bool() { - return lock.owns(); + return lock.owns_lock(); } - boost::interprocess::scoped_lock<Mutex> &GetLock() + boost::unique_lock<Mutex> &GetLock() { return lock; } @@ -117,47 +117,40 @@ typedef CMutexLock<CCriticalSection> CCriticalBlock; LeaveCritical(); \ } -#ifdef MAC_OSX -// boost::interprocess::interprocess_semaphore seems to spinlock on OSX; prefer polling instead class CSemaphore { private: - CCriticalSection cs; - int val; + boost::condition_variable condition; + boost::mutex mutex; + int value; public: - CSemaphore(int init) : val(init) {} + CSemaphore(int init) : value(init) {} void wait() { - do { - { - LOCK(cs); - if (val>0) { - val--; - return; - } - } - Sleep(100); - } while(1); + boost::unique_lock<boost::mutex> lock(mutex); + while (value < 1) { + condition.wait(lock); + } + value--; } bool try_wait() { - LOCK(cs); - if (val>0) { - val--; - return true; - } - return false; + boost::unique_lock<boost::mutex> lock(mutex); + if (value < 1) + return false; + value--; + return true; } void post() { - LOCK(cs); - val++; + { + boost::unique_lock<boost::mutex> lock(mutex); + value++; + } + condition.notify_one(); } }; -#else -typedef boost::interprocess::interprocess_semaphore CSemaphore; -#endif /** RAII-style semaphore lock */ class CSemaphoreGrant diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 7ff7545ab4..bf597c9b73 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -5,11 +5,15 @@ #include "wallet.h" CWallet* pwalletMain; +CClientUIInterface uiInterface; extern bool fPrintToConsole; +extern void noui_connect(); + struct TestingSetup { TestingSetup() { fPrintToConsole = true; // don't want to write to debug.log file + noui_connect(); pwalletMain = new CWallet(); RegisterWallet(pwalletMain); } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 0393edb1a7..2bfda61675 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat) { BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 0), "01/01/70 00:00:00"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 0x7FFFFFFF), "01/19/38 03:14:07"); - // Formats used within bitcoin + // Formats used within Bitcoin BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 1317425777), "09/30/11 23:36:17"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M", 1317425777), "09/30/11 23:36"); } diff --git a/src/ui_interface.h b/src/ui_interface.h index 63d2e5c1d0..b94446cc20 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -6,45 +6,99 @@ #include <string> #include "util.h" // for int64 +#include <boost/signals2/signal.hpp> +#include <boost/signals2/last_value.hpp> -#define wxYES 0x00000002 -#define wxOK 0x00000004 -#define wxNO 0x00000008 -#define wxYES_NO (wxYES|wxNO) -#define wxCANCEL 0x00000010 -#define wxAPPLY 0x00000020 -#define wxCLOSE 0x00000040 -#define wxOK_DEFAULT 0x00000000 -#define wxYES_DEFAULT 0x00000000 -#define wxNO_DEFAULT 0x00000080 -#define wxCANCEL_DEFAULT 0x80000000 -#define wxICON_EXCLAMATION 0x00000100 -#define wxICON_HAND 0x00000200 -#define wxICON_WARNING wxICON_EXCLAMATION -#define wxICON_ERROR wxICON_HAND -#define wxICON_QUESTION 0x00000400 -#define wxICON_INFORMATION 0x00000800 -#define wxICON_STOP wxICON_HAND -#define wxICON_ASTERISK wxICON_INFORMATION -#define wxICON_MASK (0x00000100|0x00000200|0x00000400|0x00000800) -#define wxFORWARD 0x00001000 -#define wxBACKWARD 0x00002000 -#define wxRESET 0x00004000 -#define wxHELP 0x00008000 -#define wxMORE 0x00010000 -#define wxSETUP 0x00020000 -// Force blocking, modal message box dialog (not just notification) -#define wxMODAL 0x00040000 - -/* These UI communication functions are implemented in bitcoin.cpp (for ui) and noui.cpp (no ui) */ - -extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK); -extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption); -extern void ThreadSafeHandleURI(const std::string& strURI); -extern void MainFrameRepaint(); -extern void AddressBookRepaint(); -extern void QueueShutdown(); -extern void InitMessage(const std::string &message); -extern std::string _(const char* psz); +class CBasicKeyStore; +class CWallet; +class uint256; + +/** General change type (added, updated, removed). */ +enum ChangeType +{ + CT_NEW, + CT_UPDATED, + CT_DELETED +}; + +/** Signals for UI communication. */ +class CClientUIInterface +{ +public: + /** Flags for CClientUIInterface::ThreadSafeMessageBox */ + enum MessageBoxFlags + { + YES = 0x00000002, + OK = 0x00000004, + NO = 0x00000008, + YES_NO = (YES|NO), + CANCEL = 0x00000010, + APPLY = 0x00000020, + CLOSE = 0x00000040, + OK_DEFAULT = 0x00000000, + YES_DEFAULT = 0x00000000, + NO_DEFAULT = 0x00000080, + CANCEL_DEFAULT = 0x80000000, + ICON_EXCLAMATION = 0x00000100, + ICON_HAND = 0x00000200, + ICON_WARNING = ICON_EXCLAMATION, + ICON_ERROR = ICON_HAND, + ICON_QUESTION = 0x00000400, + ICON_INFORMATION = 0x00000800, + ICON_STOP = ICON_HAND, + ICON_ASTERISK = ICON_INFORMATION, + ICON_MASK = (0x00000100|0x00000200|0x00000400|0x00000800), + FORWARD = 0x00001000, + BACKWARD = 0x00002000, + RESET = 0x00004000, + HELP = 0x00008000, + MORE = 0x00010000, + SETUP = 0x00020000, + // Force blocking, modal message box dialog (not just OS notification) + MODAL = 0x00040000 + }; + + /** Show message box. */ + boost::signals2::signal<void (const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox; + + /** Ask the user whether he want to pay a fee or not. */ + boost::signals2::signal<bool (int64 nFeeRequired, const std::string& strCaption), boost::signals2::last_value<bool> > ThreadSafeAskFee; + + /** Handle an URL passed on the command line. */ + boost::signals2::signal<void (const std::string& strURI)> ThreadSafeHandleURI; + + /** Progress message during initialization. */ + boost::signals2::signal<void (const std::string &message)> InitMessage; + + /** Initiate client shutdown. */ + boost::signals2::signal<void ()> QueueShutdown; + + /** Translate a message to the native language of the user. */ + boost::signals2::signal<std::string (const char* psz)> Translate; + + /** Block chain changed. */ + boost::signals2::signal<void ()> NotifyBlocksChanged; + + /** Number of network connections changed. */ + boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged; + + /** + * New, updated or cancelled alert. + * @note called with lock cs_mapAlerts held. + */ + boost::signals2::signal<void (const uint256 &hash, ChangeType status)> NotifyAlertChanged; +}; + +extern CClientUIInterface uiInterface; + +/** + * Translation function: Call Translate signal on UI interface, which returns a boost::optional result. + * If no translation slot is registered, nothing is returned, and simply return the input. + */ +inline std::string _(const char* psz) +{ + boost::optional<std::string> rv = uiInterface.Translate(psz); + return rv ? (*rv) : psz; +} #endif diff --git a/src/util.cpp b/src/util.cpp index d153574f96..a82625c84d 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -27,6 +27,7 @@ namespace boost { #include <boost/foreach.hpp> #include <openssl/crypto.h> #include <openssl/rand.h> +#include <stdarg.h> #ifdef WIN32 #ifdef _MSC_VER @@ -181,8 +182,6 @@ int GetRandInt(int nMax) - - inline int OutputDebugStringF(const char* pszFormat, ...) { int ret = 0; @@ -229,68 +228,30 @@ inline int OutputDebugStringF(const char* pszFormat, ...) { static CCriticalSection cs_OutputDebugStringF; - // accumulate a line at a time + // accumulate and output a line at a time { LOCK(cs_OutputDebugStringF); - static char pszBuffer[50000]; - static char* pend; - if (pend == NULL) - pend = pszBuffer; + static std::string buffer; + va_list arg_ptr; va_start(arg_ptr, pszFormat); - int limit = END(pszBuffer) - pend - 2; - int ret = _vsnprintf(pend, limit, pszFormat, arg_ptr); + buffer += vstrprintf(pszFormat, arg_ptr); va_end(arg_ptr); - if (ret < 0 || ret >= limit) - { - pend = END(pszBuffer) - 2; - *pend++ = '\n'; - } - else - pend += ret; - *pend = '\0'; - char* p1 = pszBuffer; - char* p2; - while ((p2 = strchr(p1, '\n'))) + + int line_start = 0, line_end; + while((line_end = buffer.find('\n', line_start)) != -1) { - p2++; - char c = *p2; - *p2 = '\0'; - OutputDebugStringA(p1); - *p2 = c; - p1 = p2; + OutputDebugStringA(buffer.substr(line_start, line_end - line_start).c_str()); + line_start = line_end + 1; } - if (p1 != pszBuffer) - memmove(pszBuffer, p1, pend - p1 + 1); - pend -= (p1 - pszBuffer); + buffer.erase(0, line_start); } } #endif return ret; } - -// Safer snprintf -// - prints up to limit-1 characters -// - output string is always null terminated even if limit reached -// - return value is the number of characters actually printed -int my_snprintf(char* buffer, size_t limit, const char* format, ...) -{ - if (limit == 0) - return 0; - va_list arg_ptr; - va_start(arg_ptr, format); - int ret = _vsnprintf(buffer, limit, format, arg_ptr); - va_end(arg_ptr); - if (ret < 0 || ret >= (int)limit) - { - ret = limit - 1; - buffer[limit-1] = 0; - } - return ret; -} - -string real_strprintf(const std::string &format, int dummy, ...) +string vstrprintf(const std::string &format, va_list ap) { char buffer[50000]; char* p = buffer; @@ -299,7 +260,7 @@ string real_strprintf(const std::string &format, int dummy, ...) loop { va_list arg_ptr; - va_start(arg_ptr, dummy); + va_copy(arg_ptr, ap); ret = _vsnprintf(p, limit, format.c_str(), arg_ptr); va_end(arg_ptr); if (ret >= 0 && ret < limit) @@ -317,19 +278,22 @@ string real_strprintf(const std::string &format, int dummy, ...) return str; } +string real_strprintf(const std::string &format, int dummy, ...) +{ + va_list arg_ptr; + va_start(arg_ptr, dummy); + string str = vstrprintf(format, arg_ptr); + va_end(arg_ptr); + return str; +} + bool error(const char *format, ...) { - char buffer[50000]; - int limit = sizeof(buffer); va_list arg_ptr; va_start(arg_ptr, format); - int ret = _vsnprintf(buffer, limit, format, arg_ptr); + std::string str = vstrprintf(format, arg_ptr); va_end(arg_ptr); - if (ret < 0 || ret >= limit) - { - buffer[limit - 1] = 0; - } - printf("ERROR: %s\n", buffer); + printf("ERROR: %s\n", str.c_str()); return false; } @@ -757,7 +721,7 @@ bool WildcardMatch(const string& str, const string& mask) -void FormatException(char* pszMessage, std::exception* pex, const char* pszThread) +static std::string FormatException(std::exception* pex, const char* pszThread) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; @@ -766,37 +730,34 @@ void FormatException(char* pszMessage, std::exception* pex, const char* pszThrea const char* pszModule = "bitcoin"; #endif if (pex) - snprintf(pszMessage, 1000, + return strprintf( "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); else - snprintf(pszMessage, 1000, + return strprintf( "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } void LogException(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n%s", pszMessage); + std::string message = FormatException(pex, pszThread); + printf("\n%s", message.c_str()); } void PrintException(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n\n************************\n%s\n", pszMessage); - fprintf(stderr, "\n\n************************\n%s\n", pszMessage); - strMiscWarning = pszMessage; + std::string message = FormatException(pex, pszThread); + printf("\n\n************************\n%s\n", message.c_str()); + fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); + strMiscWarning = message; throw; } void PrintExceptionContinue(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n\n************************\n%s\n", pszMessage); - fprintf(stderr, "\n\n************************\n%s\n", pszMessage); - strMiscWarning = pszMessage; + std::string message = FormatException(pex, pszThread); + printf("\n\n************************\n%s\n", message.c_str()); + fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); + strMiscWarning = message; } boost::filesystem::path GetDefaultDataDir() @@ -1039,7 +1000,7 @@ void AddTimeData(const CNetAddr& ip, int64 nTime) string strMessage = _("Warning: Please check that your computer's date and time are correct. If your clock is wrong Bitcoin will not work properly."); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage+" ", string("Bitcoin"), wxOK | wxICON_EXCLAMATION); + uiInterface.ThreadSafeMessageBox(strMessage+" ", string("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION); } } } diff --git a/src/util.h b/src/util.h index 9b566160b6..fde695d123 100644 --- a/src/util.h +++ b/src/util.h @@ -43,11 +43,6 @@ static const int64 CENT = 1000000; #define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) #define printf OutputDebugStringF -#ifdef snprintf -#undef snprintf -#endif -#define snprintf my_snprintf - #ifndef PRI64d #if defined(_MSC_VER) || defined(__MSVCRT__) #define PRI64d "I64d" @@ -133,6 +128,7 @@ int my_snprintf(char* buffer, size_t limit, const char* format, ...); */ std::string real_strprintf(const std::string &format, int dummy, ...); #define strprintf(format, ...) real_strprintf(format, 0, __VA_ARGS__) +std::string vstrprintf(const std::string &format, va_list ap); bool error(const char *format, ...); void LogException(std::exception* pex, const char* pszThread); diff --git a/src/wallet.cpp b/src/wallet.cpp index d7a70fe563..62f663c0dc 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -274,7 +274,9 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // Need to completely rewrite the wallet file; if we don't, bdb might keep // bits of the unencrypted private key in slack space in the database file. CDB::Rewrite(strWalletFile); + } + NotifyStatusChanged(this); return true; } @@ -297,7 +299,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); - vWalletUpdated.push_back(txin.prevout.hash); + NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); } } } @@ -373,15 +375,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) } } #endif - // Notify UI - vWalletUpdated.push_back(hash); - // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins WalletUpdateSpent(wtx); - } - // Refresh UI - MainFrameRepaint(); + // Notify UI of new or updated transaction + NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + } return true; } @@ -1183,7 +1182,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) coin.BindWallet(this); coin.MarkSpent(txin.prevout.n); coin.WriteToDisk(); - vWalletUpdated.push_back(coin.GetHash()); + NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } if (fFileBacked) @@ -1202,7 +1201,6 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) } wtxNew.RelayWalletTransaction(); } - MainFrameRepaint(); return true; } @@ -1231,13 +1229,12 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, return strError; } - if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."))) + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) return "ABORTED"; if (!CommitTransaction(wtxNew, reservekey)) return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); - MainFrameRepaint(); return ""; } @@ -1251,7 +1248,7 @@ string CWallet::SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 if (nValue + nTransactionFee > GetBalance()) return _("Insufficient funds"); - // Parse bitcoin address + // Parse Bitcoin address CScript scriptPubKey; scriptPubKey.SetBitcoinAddress(address); @@ -1290,8 +1287,9 @@ int CWallet::LoadWallet(bool& fFirstRunRet) bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& strName) { + std::map<CBitcoinAddress, std::string>::iterator mi = mapAddressBook.find(address); mapAddressBook[address] = strName; - AddressBookRepaint(); + NotifyAddressBookChanged(this, address.ToString(), strName, HaveKey(address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); @@ -1300,7 +1298,7 @@ bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& s bool CWallet::DelAddressBookName(const CBitcoinAddress& address) { mapAddressBook.erase(address); - AddressBookRepaint(); + NotifyAddressBookChanged(this, address.ToString(), "", HaveKey(address), CT_DELETED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).EraseName(address.ToString()); @@ -1558,3 +1556,14 @@ void CWallet::GetAllReserveAddresses(set<CBitcoinAddress>& setAddress) setAddress.insert(address); } } + +void CWallet::UpdatedTransaction(const uint256 &hashTx) +{ + { + LOCK(cs_wallet); + // Only notify UI if this transaction is in this wallet + map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx); + if (mi != mapWallet.end()) + NotifyTransactionChanged(this, hashTx, CT_UPDATED); + } +} diff --git a/src/wallet.h b/src/wallet.h index 9e451f89d6..57633c4aa3 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -9,6 +9,7 @@ #include "key.h" #include "keystore.h" #include "script.h" +#include "ui_interface.h" class CWalletTx; class CReserveKey; @@ -102,8 +103,6 @@ public: } std::map<uint256, CWalletTx> mapWallet; - std::vector<uint256> vWalletUpdated; - std::map<uint256, int> mapRequestCount; std::map<CBitcoinAddress, std::string> mapAddressBook; @@ -232,13 +231,7 @@ public: bool DelAddressBookName(const CBitcoinAddress& address); - void UpdatedTransaction(const uint256 &hashTx) - { - { - LOCK(cs_wallet); - vWalletUpdated.push_back(hashTx); - } - } + void UpdatedTransaction(const uint256 &hashTx); void PrintWallet(const CBlock& block); @@ -269,6 +262,16 @@ public: // get the current wallet format (the oldest client version guaranteed to understand this wallet) int GetVersion() { return nWalletVersion; } + + /** Address book entry changed. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged; + + /** Wallet transaction added, removed or updated. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, ChangeType status)> NotifyTransactionChanged; }; /** A key allocated from the key pool. */ |