diff --git a/src/Makefile.am b/src/Makefile.am index 4bbb9c8ebcba8..86d85074eb420 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -170,7 +170,8 @@ OMNICORE_H = \ omnicore_encoding.h \ omnicore_createpayload.h \ omnicore_rpctx.h \ - omnicore_utils.h + omnicore_utils.h \ + omnicore_pending.h OMNICORE_CPP = \ mastercore.cpp \ @@ -188,7 +189,8 @@ OMNICORE_CPP = \ omnicore_encoding.cpp \ omnicore_createpayload.cpp \ omnicore_rpctx.cpp \ - omnicore_utils.cpp + omnicore_utils.cpp \ + omnicore_pending.cpp BITCOIN_CORE_H += \ $(OMNICORE_H) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 5812f9aff0b87..b565bd27ebfb2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -91,7 +91,16 @@ QT_FORMS_UI = \ qt/forms/sendcoinsdialog.ui \ qt/forms/sendcoinsentry.ui \ qt/forms/signverifymessagedialog.ui \ - qt/forms/transactiondescdialog.ui + qt/forms/transactiondescdialog.ui \ + qt/forms/sendmpdialog.ui \ + qt/forms/lookupaddressdialog.ui \ + qt/forms/lookupspdialog.ui \ + qt/forms/lookuptxdialog.ui \ + qt/forms/txhistorydialog.ui \ + qt/forms/metadexcanceldialog.ui \ + qt/forms/metadexdialog.ui \ + qt/forms/tradehistorydialog.ui \ + qt/forms/balancesdialog.ui QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ @@ -136,7 +145,16 @@ QT_MOC_CPP = \ qt/moc_utilitydialog.cpp \ qt/moc_walletframe.cpp \ qt/moc_walletmodel.cpp \ - qt/moc_walletview.cpp + qt/moc_walletview.cpp \ + qt/moc_sendmpdialog.cpp \ + qt/moc_lookupaddressdialog.cpp \ + qt/moc_lookupspdialog.cpp \ + qt/moc_lookuptxdialog.cpp \ + qt/moc_txhistorydialog.cpp \ + qt/moc_balancesdialog.cpp \ + qt/moc_metadexdialog.cpp \ + qt/moc_metadexcanceldialog.cpp \ + qt/moc_tradehistorydialog.cpp BITCOIN_MM = \ qt/macdockiconhandler.mm \ @@ -207,7 +225,18 @@ BITCOIN_QT_H = \ qt/walletmodel.h \ qt/walletmodeltransaction.h \ qt/walletview.h \ - qt/winshutdownmonitor.h + qt/winshutdownmonitor.h \ + qt/lookupaddressdialog.h \ + qt/lookupspdialog.h \ + qt/lookuptxdialog.h \ + qt/txhistorydialog.h \ + qt/balancesdialog.h \ + qt/omnicore_init.h \ + qt/metadexdialog.h \ + qt/metadexcanceldialog.h \ + qt/tradehistorydialog.h \ + qt/sendmpdialog.h \ + qt/omnicore_qtutils.h RES_ICONS = \ qt/res/icons/add.png \ @@ -256,7 +285,24 @@ RES_ICONS = \ qt/res/icons/tx_mined.png \ qt/res/icons/unit_btc.png \ qt/res/icons/unit_mbtc.png \ - qt/res/icons/unit_ubtc.png + qt/res/icons/unit_ubtc.png \ + qt/res/icons/token_msc.png \ + qt/res/icons/token_btc.png \ + qt/res/icons/mp_balances.png \ + qt/res/icons/mp_exchange.png \ + qt/res/icons/mp_history.png \ + qt/res/icons/mp_home.png \ + qt/res/icons/mp_meta_cancelled.png \ + qt/res/icons/mp_meta_filled.png \ + qt/res/icons/mp_meta_open.png \ + qt/res/icons/mp_meta_partial.png \ + qt/res/icons/mp_meta_partialclosed.png \ + qt/res/icons/mp_meta_pending.png \ + qt/res/icons/mp_receive.png \ + qt/res/icons/mp_send.png \ + qt/res/icons/mp_sp.png \ + qt/res/icons/mp_toolbox.png \ + qt/res/icons/transaction_invalid.png BITCOIN_QT_CPP = \ qt/bitcoinaddressvalidator.cpp \ @@ -307,7 +353,18 @@ BITCOIN_QT_CPP += \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ - qt/walletview.cpp + qt/walletview.cpp \ + qt/sendmpdialog.cpp \ + qt/lookupaddressdialog.cpp \ + qt/lookupspdialog.cpp \ + qt/lookuptxdialog.cpp \ + qt/txhistorydialog.cpp \ + qt/balancesdialog.cpp \ + qt/omnicore_init.cpp \ + qt/metadexdialog.cpp \ + qt/metadexcanceldialog.cpp \ + qt/tradehistorydialog.cpp \ + qt/omnicore_qtutils.cpp endif RES_IMAGES = \ diff --git a/src/init.cpp b/src/init.cpp index e3ea1077619fd..1c06213dd252a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1140,7 +1140,7 @@ bool AppInit2(boost::thread_group& threadGroup) failMsg += _("Please add txindex=1 to your configuration file manually.\n\nOmni Core will now shutdown."); return InitError(failMsg); } - fprintf(fp, "\ntxindex=1"); + fprintf(fp, "\ntxindex=1\n"); fflush(fp); fclose(fp); return InitError(_("Your configuration file has been updated.\n\n" diff --git a/src/mastercore.cpp b/src/mastercore.cpp index 3e16c8f1173bf..2176a1eebf845 100644 --- a/src/mastercore.cpp +++ b/src/mastercore.cpp @@ -27,6 +27,7 @@ #include "mastercore_version.h" #include "omnicore_encoding.h" #include "omnicore_utils.h" +#include "omnicore_pending.h" #include "base58.h" #include "chainparams.h" @@ -106,10 +107,14 @@ static const int nBlockTop = 0; static int nWaterlineBlock = 0; // uint64_t global_metadex_market; -uint64_t global_balance_money_maineco[100000]; -uint64_t global_balance_reserved_maineco[100000]; -uint64_t global_balance_money_testeco[100000]; -uint64_t global_balance_reserved_testeco[100000]; +//! Available balances of wallet properties in the main ecosystem +std::map global_balance_money_maineco; +//! Reserved balances of wallet properties in the main ecosystem +std::map global_balance_reserved_maineco; +//! Available balances of wallet properties in the test ecosystem +std::map global_balance_money_testeco; +//! Reserved balances of wallet properties in the test ecosystem +std::map global_balance_reserved_testeco; /** * Used to indicate, whether to automatically commit created transactions. @@ -308,59 +313,6 @@ CrowdMap mastercore::my_crowds; PendingMap mastercore::my_pending; -static CMPPending *pendingDelete(const uint256 txid, bool bErase = false) -{ - if (msc_debug_verbose3) file_log("%s(%s)\n", __FUNCTION__, txid.GetHex().c_str()); - - PendingMap::iterator it = my_pending.find(txid); - - if (it != my_pending.end()) - { - // display - CMPPending *p_pending = &(it->second); - - int64_t src_amount = getMPbalance(p_pending->src, p_pending->prop, PENDING); - - if (msc_debug_verbose3) file_log("%s()src= %ld, line %d, file: %s\n", __FUNCTION__, src_amount, __LINE__, __FILE__); - - if (src_amount) - { - update_tally_map(p_pending->src, p_pending->prop, p_pending->amount, PENDING); - } - - if (bErase) - { - my_pending.erase(it); - } - else - { - return &(it->second); - } - } - - return (CMPPending *) NULL; -} - -int pendingAdd(const uint256 &txid, const string &FromAddress, unsigned int propId, int64_t Amount, int64_t type, const string &txDesc) -{ -CMPPending pending; - - if (msc_debug_verbose3) file_log("%s(%s,%s,%u,%ld,%d, %s), line %d, file: %s\n", __FUNCTION__, txid.GetHex().c_str(), FromAddress.c_str(), propId, Amount, type, txDesc,__LINE__, __FILE__); - - // support for pending, 0-confirm - if (update_tally_map(FromAddress, propId, -Amount, PENDING)) - { - pending.src = FromAddress; - pending.amount = Amount; - pending.prop = propId; - pending.desc = txDesc; - pending.type = type; - my_pending.insert(std::make_pair(txid, pending)); - } - - return 0; -} - // this is the master list of all amounts for all addresses for all properties, map is sorted by Bitcoin address std::map mastercore::mp_tally_map; @@ -663,7 +615,7 @@ int64_t prev = 0, owners = 0; bool mastercore::update_tally_map(string who, unsigned int which_property, int64_t amount, TallyType ttype) { bool bRet = false; -uint64_t before, after; +int64_t before, after; if (0 == amount) { @@ -693,7 +645,7 @@ uint64_t before, after; { if ((exodus_address != who) || (exodus_address == who && msc_debug_exo)) { - file_log("%s(%s, %u=0x%X, %+ld, ttype=%d); before=%lu, after=%lu\n", + file_log("%s(%s, %u=0x%X, %+ld, ttype=%d); before=%ld, after=%ld\n", __FUNCTION__, who.c_str(), which_property, which_property, amount, ttype, before, after); } } @@ -848,6 +800,15 @@ const double available_reward=all_reward * part_available; return devmsc; } +uint32_t mastercore::GetNextPropertyId(bool maineco) +{ + if(maineco) { + return _my_sps->peekNextSPID(1); + } else { + return _my_sps->peekNextSPID(2); + } +} + // TODO: optimize efficiency -- iterate only over wallet's addresses in the future // NOTE: if we loop over wallet addresses we miss tokens that may be in change addresses (since mapAddressBook does not // include change addresses). with current transaction load, about 0.02 - 0.06 seconds is spent on this function @@ -2356,7 +2317,7 @@ int mastercore_handler_tx(const CTransaction &tx, int nBlock, unsigned int idx, // NOTE1: Every incoming TX is checked, not just MP-ones because: // if for some reason the incoming TX doesn't pass our parser validation steps successfuly, I'd still want to clear pending amounts for that TX. // NOTE2: Plus I wanna clear the amount before that TX is parsed by our protocol, in case we ever consider pending amounts in internal calculations. - (void) pendingDelete(tx.GetHash(), true); + (void) PendingDelete(tx.GetHash()); CMPTransaction mp_obj; // save the augmented offer or accept amount into the database as well (expecting them to be numerically lower than that in the blockchain) @@ -3285,7 +3246,7 @@ int CMPSTOList::deleteAboveBlock(int blockNum) } // MPTradeList here -bool CMPTradeList::getMatchingTrades(const uint256 txid, unsigned int propertyId, Array *tradeArray, uint64_t *totalSold, uint64_t *totalBought) +bool CMPTradeList::getMatchingTrades(const uint256 txid, unsigned int propertyId, Array *tradeArray, int64_t *totalSold, int64_t *totalBought) { if (!pdb) return false; leveldb::Slice skey, svalue; diff --git a/src/mastercore.h b/src/mastercore.h index 0ad5fbefae79c..b08a4cbad197c 100644 --- a/src/mastercore.h +++ b/src/mastercore.h @@ -338,7 +338,7 @@ class CMPTradeList : public CDBBase bool exists(const uint256 &txid); void printStats(); void printAll(); - bool getMatchingTrades(const uint256 txid, unsigned int propertyId, Array *tradeArray, uint64_t *totalSold, uint64_t *totalBought); + bool getMatchingTrades(const uint256 txid, unsigned int propertyId, Array *tradeArray, int64_t *totalSold, int64_t *totalBought); int getMPTradeCountTotal(); }; @@ -397,13 +397,15 @@ class CMPPending }; -//temp - only supporting 100,000 properties per eco here, research best way to expand array -//these 4 arrays use about 3MB total memory with 100K properties limit (100000*8*4 bytes) extern uint64_t global_metadex_market; -extern uint64_t global_balance_money_maineco[100000]; -extern uint64_t global_balance_reserved_maineco[100000]; -extern uint64_t global_balance_money_testeco[100000]; -extern uint64_t global_balance_reserved_testeco[100000]; +//! Available balances of wallet properties in the main ecosystem +extern std::map global_balance_money_maineco; +//! Reserved balances of wallet properties in the main ecosystem +extern std::map global_balance_reserved_maineco; +//! Available balances of wallet properties in the test ecosystem +extern std::map global_balance_money_testeco; +//! Reserved balances of wallet properties in the test ecosystem +extern std::map global_balance_reserved_testeco; int64_t getMPbalance(const string &Address, unsigned int property, TallyType ttype); int64_t getUserAvailableMPbalance(const string &Address, unsigned int property); @@ -452,6 +454,7 @@ int ClassAgnosticWalletTXBuilder(const string &senderAddress, const string &rece bool isTestEcosystemProperty(unsigned int property); bool isMainEcosystemProperty(unsigned int property); +uint32_t GetNextPropertyId(bool maineco); CMPTally *getTally(const string & address); diff --git a/src/mastercore_errors.h b/src/mastercore_errors.h index 8d7d2f7d7b377..1b2ce7e12fe7f 100644 --- a/src/mastercore_errors.h +++ b/src/mastercore_errors.h @@ -9,8 +9,8 @@ enum MPRPCErrorCode MP_INSUF_FUNDS_BPENDI = -1, // balance before pending MP_INSUF_FUNDS_APENDI = -2, // balance after pending MP_INPUT_NOT_IN_RANGE = -11, // input value larger than supported - - //ClassAgnostic_send + + //ClassAgnosticWalletTXBuilder( MP_INPUTS_INVALID = -212, MP_ENCODING_ERROR = -250, MP_REDEMP_ILLEGAL = -233, @@ -22,7 +22,7 @@ enum MPRPCErrorCode MP_ERR_INPUTSELECT_FAIL = -206, MP_ERR_CREATE_TX = -211, MP_ERR_COMMIT_TX = -213, - + //gettransaction_MP, listtransactions_MP MP_TX_NOT_FOUND = -3331, // No information available about transaction. (GetTransaction failed) MP_TX_UNCONFIRMED = -3332, // Unconfirmed transactions are not supported. (blockHash is 0) @@ -34,7 +34,7 @@ enum MPRPCErrorCode inline std::string error_str(int ec) { std::string ec_str; - + switch (ec) { case MP_INSUF_FUNDS_BPENDI: diff --git a/src/mastercore_log.cpp b/src/mastercore_log.cpp index 37968aa499bda..463a97ae3c9a1 100644 --- a/src/mastercore_log.cpp +++ b/src/mastercore_log.cpp @@ -42,6 +42,7 @@ bool msc_debug_txdb = 0; bool msc_debug_tradedb = 1; bool msc_debug_persistence = 0; bool msc_debug_ui = 0; +bool msc_debug_pending = 1; bool msc_debug_metadex1 = 0; bool msc_debug_metadex2 = 0; //! Print orderbook before and after each trade diff --git a/src/mastercore_log.h b/src/mastercore_log.h index b0fcf75bf3114..8090c58addf26 100644 --- a/src/mastercore_log.h +++ b/src/mastercore_log.h @@ -40,6 +40,7 @@ extern bool msc_debug_txdb; extern bool msc_debug_tradedb; extern bool msc_debug_persistence; extern bool msc_debug_ui; +extern bool msc_debug_pending; extern bool msc_debug_metadex1; extern bool msc_debug_metadex2; extern bool msc_debug_metadex3; diff --git a/src/mastercore_mdex.cpp b/src/mastercore_mdex.cpp index 755d7ae2e0fab..ddbd62da3454b 100644 --- a/src/mastercore_mdex.cpp +++ b/src/mastercore_mdex.cpp @@ -261,6 +261,29 @@ static MatchReturnType x_Trade(CMPMetaDEx* const pnew) return NewReturn; } +// Used for display of unit prices to 8 decimal places at UI layer - automatically returns unit or inverse price as needed +std::string CMPMetaDEx::displayUnitPrice() const +{ + rational_t tmpDisplayPrice; + if (desired_property == OMNI_PROPERTY_MSC || desired_property == OMNI_PROPERTY_TMSC) { + tmpDisplayPrice = unitPrice(); + if (isPropertyDivisible(property)) tmpDisplayPrice = tmpDisplayPrice * COIN; + } else { + tmpDisplayPrice = inversePrice(); + if (isPropertyDivisible(desired_property)) tmpDisplayPrice = tmpDisplayPrice * COIN; + } + + // offers with unit prices under 0.00000001 will be excluded from UI layer - TODO: find a better way to identify sub 0.00000001 prices + std::string tmpDisplayPriceStr = xToString(tmpDisplayPrice); + if (!tmpDisplayPriceStr.empty()) { if (tmpDisplayPriceStr.substr(0,1) == "0") return "0.00000000"; } + + // we must always round up here - for example if the actual price required is 0.3333333344444 + // round: 0.33333333 - price is insufficient and thus won't result in a trade + // round: 0.33333334 - price will be sufficient to result in a trade + std::string displayValue = FormatDivisibleMP(xToInt64(tmpDisplayPrice, true)); + return displayValue; +} + rational_t CMPMetaDEx::unitPrice() const { rational_t effectivePrice(int128_t(0)); @@ -572,6 +595,24 @@ int mastercore::MetaDEx_CANCEL_EVERYTHING(const uint256& txid, unsigned int bloc return rc; } +// searches the metadex maps to see if a trade is still open +// allows search to be optimized if propertyIdForSale is specified +bool mastercore::MetaDEx_isOpen(const uint256& txid, uint32_t propertyIdForSale) +{ + for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) { + if (propertyIdForSale != 0 && propertyIdForSale != my_it->first) continue; + md_PricesMap & prices = my_it->second; + for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) { + md_Set & indexes = (it->second); + for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) { + CMPMetaDEx obj = *it; + if( obj.getHash().GetHex() == txid.GetHex() ) return true; + } + } + } + return false; +} + void mastercore::MetaDEx_debug_print(bool bShowPriceLevel, bool bDisplay) { file_log("<<<\n"); diff --git a/src/mastercore_mdex.h b/src/mastercore_mdex.h index 06a48730fee70..df6bda33f75d1 100644 --- a/src/mastercore_mdex.h +++ b/src/mastercore_mdex.h @@ -142,6 +142,9 @@ class CMPMetaDEx rational_t unitPrice() const; rational_t inversePrice() const; + std::string displayUnitPrice() const; + std::string displayInversePrice() const; + void saveOffer(std::ofstream& file, SHA256_CTX* shaCtx) const; }; @@ -173,6 +176,7 @@ int MetaDEx_CANCEL_ALL_FOR_PAIR(const uint256&, uint32_t, const std::string&, ui int MetaDEx_CANCEL_EVERYTHING(const uint256& txid, uint32_t block, const std::string& sender_addr, unsigned char ecosystem); bool MetaDEx_INSERT(const CMPMetaDEx& objMetaDEx); void MetaDEx_debug_print(bool bShowPriceLevel = false, bool bDisplay = false); +bool MetaDEx_isOpen(const uint256& txid, uint32_t propertyIdForSale = 0); } #endif // MASTERCORE_MDEX_H diff --git a/src/mastercore_parse_string.cpp b/src/mastercore_parse_string.cpp index a33063fd1dc09..f2cd227e4e5c4 100644 --- a/src/mastercore_parse_string.cpp +++ b/src/mastercore_parse_string.cpp @@ -62,4 +62,5 @@ int64_t StrToInt64(const std::string& str, bool divisible) return nAmount; } + } // namespace mastercore diff --git a/src/mastercore_rpc.cpp b/src/mastercore_rpc.cpp index e256f06f00077..39d10457d4c19 100644 --- a/src/mastercore_rpc.cpp +++ b/src/mastercore_rpc.cpp @@ -1948,12 +1948,12 @@ Value gettrade_MP(const Array& params, bool fHelp) } // get the amount for sale in this sell offer to see if filled - uint64_t amountForSale = mp_obj.getAmount(); + int64_t amountForSale = mp_obj.getAmount(); // create array of matches Array tradeArray; - uint64_t totalBought = 0; - uint64_t totalSold = 0; + int64_t totalBought = 0; + int64_t totalSold = 0; t_tradelistdb->getMatchingTrades(hash, propertyId, &tradeArray, &totalSold, &totalBought); // get action byte diff --git a/src/mastercore_version.h b/src/mastercore_version.h index c0d795b7e464f..893223c254379 100644 --- a/src/mastercore_version.h +++ b/src/mastercore_version.h @@ -4,10 +4,10 @@ #include // Increase with every consensus affecting change -#define OMNICORE_VERSION_MAJOR 9 +#define OMNICORE_VERSION_MAJOR 10 // Increase with every non-consensus affecting feature -#define OMNICORE_VERSION_MINOR 2 +#define OMNICORE_VERSION_MINOR 0 // Increase with every patch, which is not a feature or consensus affecting #define OMNICORE_VERSION_PATCH 0 diff --git a/src/omnicore_pending.cpp b/src/omnicore_pending.cpp new file mode 100644 index 0000000000000..a0893900fd9ff --- /dev/null +++ b/src/omnicore_pending.cpp @@ -0,0 +1,101 @@ +#include "omnicore_pending.h" + +#include "mastercore.h" +#include "mastercore_log.h" + +#include "uint256.h" + +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer_template.h" + +#include + +using json_spirit::Object; +using json_spirit::Pair; +using json_spirit::Value; +using json_spirit::write_string; + +using namespace mastercore; + +/** + * Adds a transaction to the pending map using supplied parameters + */ +void PendingAdd(const uint256& txid, const std::string& sendingAddress, const std::string& refAddress, uint16_t type, uint32_t propertyId, int64_t amount, uint32_t propertyIdDesired, int64_t amountDesired, int64_t action) +{ + Object txobj; + std::string amountStr, amountDStr; + bool divisible; + bool divisibleDesired; + txobj.push_back(Pair("txid", txid.GetHex())); + txobj.push_back(Pair("sendingaddress", sendingAddress)); + if (!refAddress.empty()) txobj.push_back(Pair("referenceaddress", refAddress)); + txobj.push_back(Pair("confirmations", 0)); + txobj.push_back(Pair("type", c_strMasterProtocolTXType(type))); + switch (type) { + case MSC_TYPE_SIMPLE_SEND: + txobj.push_back(Pair("propertyid", (uint64_t)propertyId)); + divisible = isPropertyDivisible(propertyId); + txobj.push_back(Pair("divisible", divisible)); + if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } + txobj.push_back(Pair("amount", amountStr)); + break; + case MSC_TYPE_SEND_TO_OWNERS: + txobj.push_back(Pair("propertyid", (uint64_t)propertyId)); + divisible = isPropertyDivisible(propertyId); + txobj.push_back(Pair("divisible", divisible)); + if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } + txobj.push_back(Pair("amount", amountStr)); + break; + case MSC_TYPE_TRADE_OFFER: + amountStr = FormatDivisibleMP(amount); // both amount for sale (either TMSC or MSC) and BTC desired are always divisible + amountDStr = FormatDivisibleMP(amountDesired); + txobj.push_back(Pair("amountoffered", amountStr)); + txobj.push_back(Pair("propertyidoffered", (uint64_t)propertyId)); + txobj.push_back(Pair("btcamountdesired", amountDStr)); + txobj.push_back(Pair("action", action)); + break; + case MSC_TYPE_METADEX: + divisible = isPropertyDivisible(propertyId); + divisibleDesired = isPropertyDivisible(propertyIdDesired); + if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } + if (divisibleDesired) { amountDStr = FormatDivisibleMP(amountDesired); } else { amountDStr = FormatIndivisibleMP(amountDesired); } + txobj.push_back(Pair("amountoffered", amountStr)); + txobj.push_back(Pair("propertyidoffered", (uint64_t)propertyId)); + txobj.push_back(Pair("propertyidofferedisdivisible", divisible)); + txobj.push_back(Pair("amountdesired", amountDStr)); + txobj.push_back(Pair("propertyiddesired", (uint64_t)propertyIdDesired)); + txobj.push_back(Pair("propertyiddesiredisdivisible", divisibleDesired)); + txobj.push_back(Pair("action", action)); + break; + } + std::string txDesc = write_string(Value(txobj), true); + CMPPending pending; + if (msc_debug_pending) file_log("%s(%s,%s,%s,%d,%u,%ld,%u,%ld,%d,%s)\n", __FUNCTION__, txid.GetHex(), sendingAddress, refAddress, + type, propertyId, amount, propertyIdDesired, amountDesired, action, txDesc); + if (update_tally_map(sendingAddress, propertyId, -amount, PENDING)) { + pending.src = sendingAddress; + pending.amount = amount; + pending.prop = propertyId; + pending.desc = txDesc; + pending.type = type; + my_pending.insert(std::make_pair(txid, pending)); + } +} + +/** + * Deletes a transaction from the pending map and credits the amount back to the pending tally for the address + * + * NOTE: this is currently called for every bitcoin transaction prior to running through the parser + */ +void PendingDelete(const uint256& txid) +{ + PendingMap::iterator it = my_pending.find(txid); + if (it != my_pending.end()) { + CMPPending *p_pending = &(it->second); + int64_t src_amount = getMPbalance(p_pending->src, p_pending->prop, PENDING); + if (msc_debug_pending) file_log("%s(%s): amount=%d\n", __FUNCTION__, txid.GetHex(), src_amount); + if (src_amount) update_tally_map(p_pending->src, p_pending->prop, p_pending->amount, PENDING); + my_pending.erase(it); + } +} + diff --git a/src/omnicore_pending.h b/src/omnicore_pending.h new file mode 100644 index 0000000000000..61e3793cdbd45 --- /dev/null +++ b/src/omnicore_pending.h @@ -0,0 +1,21 @@ +#ifndef OMNICORE_PENDING_H +#define OMNICORE_PENDING_H + +class uint256; + +#include +#include + +/** Adds a transaction to the pending map using supplied parameters. */ +void PendingAdd(const uint256& txid, const std::string& sendingAddress, const std::string& refAddress, uint16_t type, uint32_t propertyId, int64_t amount, uint32_t propertyIdDesired = 0, int64_t amountDesired = 0, int64_t action = 0); + +/** Deletes a transaction from the pending map and credits the amount back to the pending tally for the address. */ +void PendingDelete(const uint256& txid); + +#endif // OMNICORE_PENDING_H + + + + + + diff --git a/src/omnicore_rpctx.cpp b/src/omnicore_rpctx.cpp index 956dddeaff872..24360e6fae8fd 100644 --- a/src/omnicore_rpctx.cpp +++ b/src/omnicore_rpctx.cpp @@ -10,6 +10,8 @@ #include "mastercore_dex.h" #include "mastercore_tx.h" #include "omnicore_createpayload.h" +#include "omnicore_pending.h" + #include "rpcserver.h" #include "wallet.h" @@ -36,58 +38,6 @@ using std::vector; using namespace json_spirit; using namespace mastercore; -void CreatePendingObject(uint256 txid, const string& sendingAddress, const string& referenceAddress, uint16_t type, uint32_t propertyId, int64_t amount, uint32_t propertyIdDesired = 0, int64_t amountDesired = 0, int64_t action = 0) -{ - Object txobj; - string amountStr, amountDStr; - bool divisible; - bool divisibleDesired; - txobj.push_back(Pair("txid", txid.GetHex())); - txobj.push_back(Pair("sendingaddress", sendingAddress)); - if (!referenceAddress.empty()) txobj.push_back(Pair("referenceaddress", referenceAddress)); - txobj.push_back(Pair("confirmations", 0)); - txobj.push_back(Pair("type", c_strMasterProtocolTXType(type))); - switch (type) { - case MSC_TYPE_SIMPLE_SEND: - txobj.push_back(Pair("propertyid", (uint64_t)propertyId)); - divisible = isPropertyDivisible(propertyId); - txobj.push_back(Pair("divisible", divisible)); - if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } - txobj.push_back(Pair("amount", amountStr)); - break; - case MSC_TYPE_SEND_TO_OWNERS: - txobj.push_back(Pair("propertyid", (uint64_t)propertyId)); - divisible = isPropertyDivisible(propertyId); - txobj.push_back(Pair("divisible", divisible)); - if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } - txobj.push_back(Pair("amount", amountStr)); - break; - case MSC_TYPE_TRADE_OFFER: - amountStr = FormatDivisibleMP(amount); // both amount for sale (either TMSC or MSC) and BTC desired are always divisible - amountDStr = FormatDivisibleMP(amountDesired); - txobj.push_back(Pair("amountoffered", amountStr)); - txobj.push_back(Pair("propertyidoffered", (uint64_t)propertyId)); - txobj.push_back(Pair("btcamountdesired", amountDStr)); - txobj.push_back(Pair("action", action)); - break; - case MSC_TYPE_METADEX: - divisible = isPropertyDivisible(propertyId); - divisibleDesired = isPropertyDivisible(propertyIdDesired); - if (divisible) { amountStr = FormatDivisibleMP(amount); } else { amountStr = FormatIndivisibleMP(amount); } - if (divisibleDesired) { amountDStr = FormatDivisibleMP(amountDesired); } else { amountDStr = FormatIndivisibleMP(amountDesired); } - txobj.push_back(Pair("amountoffered", amountStr)); - txobj.push_back(Pair("propertyidoffered", (uint64_t)propertyId)); - txobj.push_back(Pair("propertyidofferedisdivisible", divisible)); - txobj.push_back(Pair("amountdesired", amountDStr)); - txobj.push_back(Pair("propertyiddesired", (uint64_t)propertyIdDesired)); - txobj.push_back(Pair("propertyiddesiredisdivisible", divisibleDesired)); - txobj.push_back(Pair("action", action)); - break; - } - string txDesc = write_string(Value(txobj), false); - (void) pendingAdd(txid, sendingAddress, propertyId, amount, type, txDesc); -} - uint32_t int64Touint32Safe(int64_t sourceValue) { if ((0 > sourceValue) || (4294967295 < sourceValue)) return 0; // not safe to do conversion @@ -154,7 +104,7 @@ Value send_OMNI(const Array& params, bool fHelp) if (!autoCommit) { return rawHex; } else { - CreatePendingObject(txid, fromAddress, toAddress, MSC_TYPE_SIMPLE_SEND, propertyId, amount); + PendingAdd(txid, fromAddress, toAddress, MSC_TYPE_SIMPLE_SEND, propertyId, amount); return txid.GetHex(); } } @@ -229,7 +179,7 @@ Value senddexsell_OMNI(const Array& params, bool fHelp) if (!autoCommit) { return rawHex; } else { - CreatePendingObject(txid, fromAddress, "", MSC_TYPE_TRADE_OFFER, propertyIdForSale, amountForSale, 0, amountDesired, action); + PendingAdd(txid, fromAddress, "", MSC_TYPE_TRADE_OFFER, propertyIdForSale, amountForSale, 0, amountDesired, action); return txid.GetHex(); } } @@ -582,7 +532,7 @@ Value sendsto_OMNI(const Array& params, bool fHelp) if (!autoCommit) { return rawHex; } else { - CreatePendingObject(txid, fromAddress, "", MSC_TYPE_SEND_TO_OWNERS, propertyId, amount); + PendingAdd(txid, fromAddress, "", MSC_TYPE_SEND_TO_OWNERS, propertyId, amount); return txid.GetHex(); } } @@ -825,7 +775,7 @@ Value sendtrade_OMNI(const Array& params, bool fHelp) if (!autoCommit) { return rawHex; } else { - CreatePendingObject(txid, fromAddress, "", MSC_TYPE_METADEX, propertyIdForSale, amountForSale, propertyIdDesired, amountDesired, action); + PendingAdd(txid, fromAddress, "", MSC_TYPE_METADEX, propertyIdForSale, amountForSale, propertyIdDesired, amountDesired, action); return txid.GetHex(); } } diff --git a/src/qt/balancesdialog.cpp b/src/qt/balancesdialog.cpp new file mode 100644 index 0000000000000..3817702ad1990 --- /dev/null +++ b/src/qt/balancesdialog.cpp @@ -0,0 +1,313 @@ +// Copyright (c) 2011-2013 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "balancesdialog.h" +#include "ui_balancesdialog.h" + +#include "clientmodel.h" +#include "walletmodel.h" +#include "guiutil.h" + +#include "mastercore.h" + +#include "amount.h" +#include "sync.h" +#include "ui_interface.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; +using namespace mastercore; + +BalancesDialog::BalancesDialog(QWidget *parent) : + QDialog(parent), ui(new Ui::balancesDialog), clientModel(0), walletModel(0) +{ + // setup + ui->setupUi(this); + ui->balancesTable->setColumnCount(4); + ui->balancesTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Property ID")); + ui->balancesTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Property Name")); + ui->balancesTable->setHorizontalHeaderItem(2, new QTableWidgetItem("Reserved")); + ui->balancesTable->setHorizontalHeaderItem(3, new QTableWidgetItem("Available")); + borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->balancesTable,100,100); + // note neither resizetocontents or stretch allow user to adjust - go interactive then manually set widths + #if QT_VERSION < 0x050000 + ui->balancesTable->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setResizeMode(1, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setResizeMode(2, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setResizeMode(3, QHeaderView::Interactive); + #else + ui->balancesTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); + ui->balancesTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Interactive); + #endif + ui->balancesTable->setAlternatingRowColors(true); + + // do an initial population + UpdatePropSelector(); + PopulateBalances(2147483646); // 2147483646 = summary (last possible ID for test eco props) + + // initial resizing + ui->balancesTable->resizeColumnToContents(0); + ui->balancesTable->resizeColumnToContents(2); + ui->balancesTable->resizeColumnToContents(3); + borrowedColumnResizingFixer->stretchColumnWidth(1); + ui->balancesTable->verticalHeader()->setVisible(false); + ui->balancesTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->balancesTable->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->balancesTable->setSelectionMode(QAbstractItemView::SingleSelection); + ui->balancesTable->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + ui->balancesTable->setTabKeyNavigation(false); + ui->balancesTable->setContextMenuPolicy(Qt::CustomContextMenu); + ui->balancesTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // Actions + QAction *balancesCopyIDAction = new QAction(tr("Copy property ID"), this); + QAction *balancesCopyNameAction = new QAction(tr("Copy property name"), this); + QAction *balancesCopyAddressAction = new QAction(tr("Copy address"), this); + QAction *balancesCopyLabelAction = new QAction(tr("Copy label"), this); + QAction *balancesCopyReservedAmountAction = new QAction(tr("Copy reserved amount"), this); + QAction *balancesCopyAvailableAmountAction = new QAction(tr("Copy available amount"), this); + + contextMenu = new QMenu(); + contextMenu->addAction(balancesCopyLabelAction); + contextMenu->addAction(balancesCopyAddressAction); + contextMenu->addAction(balancesCopyReservedAmountAction); + contextMenu->addAction(balancesCopyAvailableAmountAction); + contextMenuSummary = new QMenu(); + contextMenuSummary->addAction(balancesCopyIDAction); + contextMenuSummary->addAction(balancesCopyNameAction); + contextMenuSummary->addAction(balancesCopyReservedAmountAction); + contextMenuSummary->addAction(balancesCopyAvailableAmountAction); + + // Connect actions + connect(ui->balancesTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); + connect(ui->propSelectorWidget, SIGNAL(activated(int)), this, SLOT(propSelectorChanged())); + connect(balancesCopyIDAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol0())); + connect(balancesCopyNameAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol1())); + connect(balancesCopyLabelAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol0())); + connect(balancesCopyAddressAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol1())); + connect(balancesCopyReservedAmountAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol2())); + connect(balancesCopyAvailableAmountAction, SIGNAL(triggered()), this, SLOT(balancesCopyCol3())); +} + +BalancesDialog::~BalancesDialog() +{ + delete ui; +} + +void BalancesDialog::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if (model != NULL) { + connect(model, SIGNAL(refreshOmniState()), this, SLOT(balancesUpdated())); + } +} + +void BalancesDialog::setWalletModel(WalletModel *model) +{ + this->walletModel = model; + if (model != NULL) { + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(balancesUpdated())); + } +} + +void BalancesDialog::UpdatePropSelector() +{ + set_wallet_totals(); + QString spId = ui->propSelectorWidget->itemData(ui->propSelectorWidget->currentIndex()).toString(); + ui->propSelectorWidget->clear(); + ui->propSelectorWidget->addItem("Wallet Totals (Summary)","2147483646"); //use last possible ID for summary for now + // populate property selector + unsigned int nextPropIdMainEco = GetNextPropertyId(true); // these allow us to end the for loop at the highest existing + unsigned int nextPropIdTestEco = GetNextPropertyId(false); // property ID rather than a fixed value like 100000 (optimization) + for (unsigned int propertyId = 1; propertyId < nextPropIdMainEco; propertyId++) { + if ((global_balance_money_maineco[propertyId] > 0) || (global_balance_reserved_maineco[propertyId] > 0)) { + string spName; + spName = getPropertyName(propertyId).c_str(); + if(spName.size()>20) spName=spName.substr(0,20)+"..."; + string spId = static_cast( &(ostringstream() << propertyId) )->str(); + spName += " (#" + spId + ")"; + ui->propSelectorWidget->addItem(spName.c_str(),spId.c_str()); + } + } + for (unsigned int propertyId = 2147483647; propertyId < nextPropIdTestEco; propertyId++) { + if ((global_balance_money_testeco[propertyId-2147483647] > 0) || (global_balance_reserved_testeco[propertyId-2147483647] > 0)) { + string spName; + spName = getPropertyName(propertyId).c_str(); + if(spName.size()>20) spName=spName.substr(0,20)+"..."; + string spId = static_cast( &(ostringstream() << propertyId) )->str(); + spName += " (#" + spId + ")"; + ui->propSelectorWidget->addItem(spName.c_str(),spId.c_str()); + } + } + int propIdx = ui->propSelectorWidget->findData(spId); + if (propIdx != -1) { ui->propSelectorWidget->setCurrentIndex(propIdx); } +} + +void BalancesDialog::AddRow(const std::string& label, const std::string& address, const std::string& reserved, const std::string& available) +{ + int workingRow = ui->balancesTable->rowCount(); + ui->balancesTable->insertRow(workingRow); + QTableWidgetItem *labelCell = new QTableWidgetItem(QString::fromStdString(label)); + QTableWidgetItem *addressCell = new QTableWidgetItem(QString::fromStdString(address)); + QTableWidgetItem *reservedCell = new QTableWidgetItem(QString::fromStdString(reserved)); + QTableWidgetItem *availableCell = new QTableWidgetItem(QString::fromStdString(available)); + labelCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + addressCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + reservedCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + availableCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + ui->balancesTable->setItem(workingRow, 0, labelCell); + ui->balancesTable->setItem(workingRow, 1, addressCell); + ui->balancesTable->setItem(workingRow, 2, reservedCell); + ui->balancesTable->setItem(workingRow, 3, availableCell); +} + +void BalancesDialog::PopulateBalances(unsigned int propertyId) +{ + ui->balancesTable->setRowCount(0); // fresh slate (note this will automatically cleanup all existing QWidgetItems in the table) + //are we summary? + if(propertyId==2147483646) { + ui->balancesTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Property ID")); + ui->balancesTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Property Name")); + unsigned int nextPropIdMainEco = GetNextPropertyId(true); + unsigned int nextPropIdTestEco = GetNextPropertyId(false); + for (unsigned int propertyId = 1; propertyId < nextPropIdMainEco; propertyId++) { + if ((global_balance_money_maineco[propertyId] > 0) || (global_balance_reserved_maineco[propertyId] > 0)) { + string spName = getPropertyName(propertyId).c_str(); + string spId = static_cast( &(ostringstream() << propertyId) )->str(); + string reserved; + string available; + if(isPropertyDivisible(propertyId)) { + reserved = FormatDivisibleMP(global_balance_reserved_maineco[propertyId]); + available = FormatDivisibleMP(global_balance_money_maineco[propertyId]); + } else { + reserved = FormatIndivisibleMP(global_balance_reserved_maineco[propertyId]); + available = FormatIndivisibleMP(global_balance_money_maineco[propertyId]); + } + AddRow(spId,spName,reserved,available); + } + } + for (unsigned int propertyId = 2147483647; propertyId < nextPropIdTestEco; propertyId++) { + if ((global_balance_money_testeco[propertyId-2147483647] > 0) || (global_balance_reserved_testeco[propertyId-2147483647] > 0)) { + string spName = getPropertyName(propertyId).c_str(); + string spId = static_cast( &(ostringstream() << propertyId) )->str(); + string reserved; + string available; + if(isPropertyDivisible(propertyId)) { + reserved = FormatDivisibleMP(global_balance_reserved_testeco[propertyId-2147483647]); + available = FormatDivisibleMP(global_balance_money_testeco[propertyId-2147483647]); + } else { + reserved = FormatIndivisibleMP(global_balance_reserved_testeco[propertyId-2147483647]); + available = FormatIndivisibleMP(global_balance_money_testeco[propertyId-2147483647]); + } + AddRow(spId,spName,reserved,available); + } + } + } else { + ui->balancesTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Label")); + ui->balancesTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Address")); + LOCK(cs_tally); + for(std::map::iterator my_it = mp_tally_map.begin(); my_it != mp_tally_map.end(); ++my_it) + { + string address = (my_it->first).c_str(); + unsigned int id; + bool includeAddress=false; + (my_it->second).init(); + while (0 != (id = (my_it->second).next())) { + if(id==propertyId) { includeAddress=true; break; } + } + if (!includeAddress) continue; //ignore this address, has never transacted in this propertyId + if (!IsMyAddress(address)) continue; //ignore this address, it's not ours + int64_t available = getUserAvailableMPbalance(address, propertyId); + int64_t reserved = getMPbalance(address, propertyId, METADEX_RESERVE); + if (propertyId<3) { + reserved += getMPbalance(address, propertyId, ACCEPT_RESERVE); + reserved += getMPbalance(address, propertyId, SELLOFFER_RESERVE); + } + string reservedStr; + string availableStr; + if(isPropertyDivisible(propertyId)) { + reservedStr = FormatDivisibleMP(reserved); + availableStr = FormatDivisibleMP(available); + } else { + reservedStr = FormatIndivisibleMP(reserved); + availableStr = FormatIndivisibleMP(available); + } + AddRow(getLabel(my_it->first),my_it->first,reservedStr,availableStr); + } + } +} + +void BalancesDialog::propSelectorChanged() +{ + QString spId = ui->propSelectorWidget->itemData(ui->propSelectorWidget->currentIndex()).toString(); + unsigned int propertyId = spId.toUInt(); + PopulateBalances(propertyId); +} + +void BalancesDialog::contextualMenu(const QPoint &point) +{ + QModelIndex index = ui->balancesTable->indexAt(point); + if(index.isValid()) + { + QString spId = ui->propSelectorWidget->itemData(ui->propSelectorWidget->currentIndex()).toString(); + unsigned int propertyId = spId.toUInt(); + if (propertyId == 2147483646) { + contextMenuSummary->exec(QCursor::pos()); + } else { + contextMenu->exec(QCursor::pos()); + } + } +} + +void BalancesDialog::balancesCopyCol0() +{ + GUIUtil::setClipboard(ui->balancesTable->item(ui->balancesTable->currentRow(),0)->text()); +} + +void BalancesDialog::balancesCopyCol1() +{ + GUIUtil::setClipboard(ui->balancesTable->item(ui->balancesTable->currentRow(),1)->text()); +} + +void BalancesDialog::balancesCopyCol2() +{ + GUIUtil::setClipboard(ui->balancesTable->item(ui->balancesTable->currentRow(),2)->text()); +} + +void BalancesDialog::balancesCopyCol3() +{ + GUIUtil::setClipboard(ui->balancesTable->item(ui->balancesTable->currentRow(),3)->text()); +} + +void BalancesDialog::balancesUpdated() +{ + UpdatePropSelector(); + propSelectorChanged(); // refresh the table with the currently selected property ID +} + +// We override the virtual resizeEvent of the QWidget to adjust tables column +// sizes as the tables width is proportional to the dialogs width. +void BalancesDialog::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + borrowedColumnResizingFixer->stretchColumnWidth(1); +} + diff --git a/src/qt/balancesdialog.h b/src/qt/balancesdialog.h new file mode 100644 index 0000000000000..a529f338d5e72 --- /dev/null +++ b/src/qt/balancesdialog.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BALANCESDIALOG_H +#define BALANCESDIALOG_H + +#include "guiutil.h" + +#include + +class ClientModel; +class WalletModel; + +QT_BEGIN_NAMESPACE +class QMenu; +class QPoint; +class QResizeEvent; +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class balancesDialog; +} + +class BalancesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BalancesDialog(QWidget *parent = 0); + ~BalancesDialog(); + + void setClientModel(ClientModel *model); + void setWalletModel(WalletModel *model); + void AddRow(const std::string& label, const std::string& address, const std::string& reserved, const std::string& available); + void PopulateBalances(unsigned int propertyId); + void UpdatePropSelector(); + +private: + Ui::balancesDialog *ui; + ClientModel *clientModel; + WalletModel *walletModel; + QMenu *contextMenu; + QMenu *contextMenuSummary; + + GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; + virtual void resizeEvent(QResizeEvent *event); + +public slots: + void propSelectorChanged(); + void balancesUpdated(); + +private slots: + void contextualMenu(const QPoint &point); + void balancesCopyCol0(); + void balancesCopyCol1(); + void balancesCopyCol2(); + void balancesCopyCol3(); + +signals: + /** Fired when a message should be reported to the user */ + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // BALANCESDIALOG_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index f30df348c5ffc..3b713101a3abf 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -33,6 +33,8 @@ #include "wallet.h" #endif +#include "omnicore_init.h" + #include #include @@ -159,7 +161,7 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } #endif -/** Class encapsulating Bitcoin Core startup and shutdown. +/** Class encapsulating Omni Core startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. */ class BitcoinCore: public QObject @@ -552,14 +554,14 @@ int main(int argc, char *argv[]) /// - Do not call GetDataDir(true) before this step finishes if (!boost::filesystem::is_directory(GetDataDir(false))) { - QMessageBox::critical(0, QObject::tr("Bitcoin Core"), + QMessageBox::critical(0, QObject::tr("Omni Core"), QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); return 1; } try { ReadConfigFile(mapArgs, mapMultiArgs); } catch(std::exception &e) { - QMessageBox::critical(0, QObject::tr("Bitcoin Core"), + QMessageBox::critical(0, QObject::tr("Omni Core"), QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e.what())); return false; } @@ -572,7 +574,7 @@ int main(int argc, char *argv[]) // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) if (!SelectParamsFromCommandLine()) { - QMessageBox::critical(0, QObject::tr("Bitcoin Core"), QObject::tr("Error: Invalid combination of -regtest and -testnet.")); + QMessageBox::critical(0, QObject::tr("Omni Core"), QObject::tr("Error: Invalid combination of -regtest and -testnet.")); return 1; } #ifdef ENABLE_WALLET @@ -625,12 +627,16 @@ int main(int argc, char *argv[]) if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); + // Initialize Omni Core and quit on failure + if (!OmniCore::Initialize()) + return 1; + try { app.createWindow(networkStyle.data()); app.requestInitialize(); #if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 - WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("Bitcoin Core didn't yet exit safely..."), (HWND)app.getMainWinId()); + WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("Omni Core didn't yet exit safely..."), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(); diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index 42bb091e25de5..bdb903924c8a2 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -3,7 +3,7 @@ res/icons/bitcoin.png res/icons/address-book.png res/icons/quit.png - res/icons/send.png + res/icons/mp_send.png res/icons/connect0_16.png res/icons/connect1_16.png res/icons/connect2_16.png @@ -21,14 +21,14 @@ res/icons/eye_minus.png res/icons/eye_plus.png res/icons/configure.png - res/icons/receive.png + res/icons/mp_receive.png res/icons/editpaste.png res/icons/editcopy.png res/icons/add.png res/icons/bitcoin_testnet.png res/icons/edit.png - res/icons/history.png - res/icons/overview.png + res/icons/mp_history.png + res/icons/mp_home.png res/icons/export.png res/icons/synced.png res/icons/remove.png @@ -45,6 +45,21 @@ res/icons/filesave.png res/icons/qrcode.png res/icons/debugwindow.png + + res/icons/mp_balances.png + res/icons/mp_exchange.png + res/icons/mp_meta_cancelled.png + res/icons/mp_meta_filled.png + res/icons/mp_meta_open.png + res/icons/mp_meta_partial.png + res/icons/mp_meta_partialclosed.png + res/icons/mp_meta_pending.png + res/icons/mp_sp.png + res/icons/mp_toolbox.png + res/icons/token_msc.png + res/icons/token_btc.png + res/icons/transaction_invalid.png + res/images/about.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 30b7b54de7908..2e3935662f39b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -37,7 +37,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -98,9 +100,9 @@ BitcoinGUI::BitcoinGUI(const NetworkStyle *networkStyle, QWidget *parent) : prevBlocks(0), spinnerFrame(0) { - GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 550), this); + GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 650), this); - QString windowTitle = tr("Bitcoin Core") + " - "; + QString windowTitle = tr("Omni Core") + " - "; #ifdef ENABLE_WALLET /* if compiled with wallet support, -disablewallet can still disable the wallet */ enableWallet = !GetBoolArg("-disablewallet", false); @@ -232,7 +234,7 @@ BitcoinGUI::~BitcoinGUI() trayIcon->hide(); #ifdef Q_OS_MAC delete appMenuBar; - MacDockIconHandler::cleanup(); + MacDockIconHandler::instance()->setMainWindow(NULL); #endif } @@ -247,38 +249,65 @@ void BitcoinGUI::createActions(const NetworkStyle *networkStyle) overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); + balancesAction = new QAction(QIcon(":/icons/balances"), tr("&Balances"), this); + balancesAction->setStatusTip(tr("Show Omni Layer balances")); + balancesAction->setToolTip(balancesAction->statusTip()); + balancesAction->setCheckable(true); + balancesAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); + tabGroup->addAction(balancesAction); + sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send"), this); - sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); + sendCoinsAction->setStatusTip(tr("Send Omni Layer and Bitcoin transactions")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); - sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); + sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(sendCoinsAction); receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)")); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); - receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); + receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(receiveCoinsAction); + exchangeAction = new QAction(QIcon(":/icons/exchange"), tr("&Exchange"), this); + exchangeAction->setStatusTip(tr("Trade properties on the distributed exchange")); + exchangeAction->setToolTip(exchangeAction->statusTip()); + exchangeAction->setCheckable(true); + exchangeAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5)); + tabGroup->addAction(exchangeAction); + historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); - historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); + historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_6)); tabGroup->addAction(historyAction); + toolboxAction = new QAction(QIcon(":/icons/toolbox"), tr("&Toolbox"), this); + toolboxAction->setStatusTip(tr("Tools to obtain varions Omni Layer information and transaction information")); + toolboxAction->setToolTip(toolboxAction->statusTip()); + toolboxAction->setCheckable(true); + toolboxAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_7)); + tabGroup->addAction(toolboxAction); + #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); + connect(balancesAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(balancesAction, SIGNAL(triggered()), this, SLOT(gotoBalancesPage())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); + connect(exchangeAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(exchangeAction, SIGNAL(triggered()), this, SLOT(gotoExchangePage())); + connect(toolboxAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(toolboxAction, SIGNAL(triggered()), this, SLOT(gotoToolboxPage())); #endif // ENABLE_WALLET quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this); @@ -325,7 +354,6 @@ void BitcoinGUI::createActions(const NetworkStyle *networkStyle) openAction->setStatusTip(tr("Open a bitcoin: URI or payment request")); showHelpMessageAction = new QAction(QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation), tr("&Command-line options"), this); - showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip(tr("Show the Bitcoin Core help message to get a list with possible Bitcoin command-line options")); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); @@ -401,9 +429,12 @@ void BitcoinGUI::createToolBars() QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); + toolbar->addAction(balancesAction); toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); + toolbar->addAction(exchangeAction); toolbar->addAction(historyAction); + toolbar->addAction(toolboxAction); overviewAction->setChecked(true); } } @@ -477,9 +508,12 @@ void BitcoinGUI::removeAllWallets() void BitcoinGUI::setWalletActionsEnabled(bool enabled) { overviewAction->setEnabled(enabled); + balancesAction->setEnabled(enabled); sendCoinsAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); + exchangeAction->setEnabled(enabled); historyAction->setEnabled(enabled); + toolboxAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); @@ -592,12 +626,30 @@ void BitcoinGUI::gotoOverviewPage() if (walletFrame) walletFrame->gotoOverviewPage(); } +void BitcoinGUI::gotoBalancesPage() +{ + balancesAction->setChecked(true); + if (walletFrame) walletFrame->gotoBalancesPage(); +} + void BitcoinGUI::gotoHistoryPage() { historyAction->setChecked(true); if (walletFrame) walletFrame->gotoHistoryPage(); } +void BitcoinGUI::gotoBitcoinHistoryTab() +{ + historyAction->setChecked(true); + if (walletFrame) walletFrame->gotoBitcoinHistoryTab(); +} + +void BitcoinGUI::gotoToolboxPage() +{ + toolboxAction->setChecked(true); + if (walletFrame) walletFrame->gotoToolboxPage(); +} + void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); @@ -610,6 +662,12 @@ void BitcoinGUI::gotoSendCoinsPage(QString addr) if (walletFrame) walletFrame->gotoSendCoinsPage(addr); } +void BitcoinGUI::gotoExchangePage() +{ + exchangeAction->setChecked(true); + if (walletFrame) walletFrame->gotoExchangePage(); +} + void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) walletFrame->gotoSignMessageTab(addr); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 662ef9d9e8419..c5b0f3643ceb4 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -87,8 +87,11 @@ class BitcoinGUI : public QMainWindow QMenuBar *appMenuBar; QAction *overviewAction; + QAction *balancesAction; QAction *historyAction; QAction *quitAction; + QAction *toolboxAction; + QAction *exchangeAction; QAction *sendCoinsAction; QAction *usedSendingAddressesAction; QAction *usedReceivingAddressesAction; @@ -170,8 +173,16 @@ private slots: #ifdef ENABLE_WALLET /** Switch to overview (home) page */ void gotoOverviewPage(); + /** Switch to balances page */ + void gotoBalancesPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch directly to bitcoin history tab */ + void gotoBitcoinHistoryTab(); + /** Switch to utility page */ + void gotoToolboxPage(); + /** Switch to exchange page */ + void gotoExchangePage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index aedda4907190c..953fc39a2b87e 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -127,6 +127,11 @@ void ClientModel::updateNumConnections(int numConnections) emit numConnectionsChanged(numConnections); } +void ClientModel::updateOmniState() +{ + emit refreshOmniState(); +} + void ClientModel::updateAlert(const QString &hash, int status) { // Show error message notification for new alert @@ -202,6 +207,12 @@ QString ClientModel::formatClientStartupTime() const } // Handlers for core signals +static void OmniStateChanged(ClientModel *clientmodel) +{ + // This will be triggered for each block that contains Omni layer transactions + QMetaObject::invokeMethod(clientmodel, "updateOmniState", Qt::QueuedConnection); +} + static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) { // emits signal "showProgress" @@ -231,6 +242,7 @@ void ClientModel::subscribeToCoreSignals() uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); + uiInterface.OmniStateChanged.connect(boost::bind(OmniStateChanged, this)); } void ClientModel::unsubscribeFromCoreSignals() @@ -239,4 +251,5 @@ void ClientModel::unsubscribeFromCoreSignals() uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); + uiInterface.OmniStateChanged.disconnect(boost::bind(OmniStateChanged, this)); } diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index c7a05e287d0e5..612ec46e283a0 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -89,6 +89,7 @@ class ClientModel : public QObject void numBlocksChanged(int count); void alertsChanged(const QString &warnings); void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut); + void refreshOmniState(); //! Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); @@ -100,6 +101,7 @@ public slots: void updateTimer(); void updateNumConnections(int numConnections); void updateAlert(const QString &hash, int status); + void updateOmniState(); }; #endif // BITCOIN_QT_CLIENTMODEL_H diff --git a/src/qt/forms/balancesdialog.ui b/src/qt/forms/balancesdialog.ui new file mode 100644 index 0000000000000..df0ce59b0a7fa --- /dev/null +++ b/src/qt/forms/balancesdialog.ui @@ -0,0 +1,84 @@ + + + balancesDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + 0 + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show balances for: + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + + 300 + 0 + + + + + + + + + + false + + + + + + + + + + diff --git a/src/qt/forms/lookupaddressdialog.ui b/src/qt/forms/lookupaddressdialog.ui new file mode 100644 index 0000000000000..9eb96824d205f --- /dev/null +++ b/src/qt/forms/lookupaddressdialog.ui @@ -0,0 +1,584 @@ + + + LookupAddressDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + false + + + background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; + + + true + + + 3 + + + + + + + 0 + + + + + true + + + + 0 + 0 + + + + Search Address: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + + + Search + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 6 + 20 + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 75 + true + + + + Address Information + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 96 + 96 + + + + QRCode + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + Address: + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Type: + + + + + + + + 50 + false + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + In Wallet: + + + + + + + Balance: + + + + + + + + 75 + true + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + + + + + <b>Balance Information</b> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 6 + + + + + Property1: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property2: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property3: + + + + + + + + 75 + true + + + + Qt::LeftToRight + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property4: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property5: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property6: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property7: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property8: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property9: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Property10: + + + + + + + + 75 + true + + + + 0.00 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + *Only first 10 properties shown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + + + + + diff --git a/src/qt/forms/lookupspdialog.ui b/src/qt/forms/lookupspdialog.ui new file mode 100644 index 0000000000000..f8f93af3df557 --- /dev/null +++ b/src/qt/forms/lookupspdialog.ui @@ -0,0 +1,778 @@ + + + LookupSPDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + false + + + background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; + + + true + + + 3 + + + + + + + 0 + + + + + true + + + + 0 + 0 + + + + Search: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + + + + Search + + + + + + + + 0 + 0 + + + + padding-left:15px; + + + Results: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 6 + 20 + + + + + + + + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 75 + true + + + + Smart Property Information + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 0 + + + + + Property ID: + + + + + + + + 75 + true + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Name: + + + + + + + + 75 + true + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Issuer: + + + + + + + + 0 + 0 + + + + + 75 + true + + + + color:rgb(148, 148, 148) + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + -1 + + + + + + + Category: + + + + + + + + 50 + false + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + URL: + + + + + + + + 50 + false + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Data: + + + + + + + + 50 + false + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + 0 + + + + + + + <b>Token Information</b> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 12 + + + + + + 75 + true + + + + Fixed + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Test + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Qt::LeftToRight + + + 0 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + No + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Divisible: + + + + + + + Issuance Type: + + + + + + + Ecosystem: + + + + + + + Total Tokens: + + + + + + + Wallet Balance: + + + + + + + + 75 + true + + + + 0 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + 0 + + + + + + 75 + true + + + + Crowdsale Information + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 12 + + + 0 + + + + + Desired Property: + + + + + + + + 75 + true + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Tokens Per Unit: + + + + + + + + 75 + true + + + + 0 SPT + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Deadline: + + + + + + + + 75 + true + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Weekly Bonus: + + + + + + + + 75 + true + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Issuer Percentage: + + + + + + + + 75 + true + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Active: + + + + + + + + 75 + true + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/qt/forms/lookuptxdialog.ui b/src/qt/forms/lookuptxdialog.ui new file mode 100644 index 0000000000000..bc860ec9af294 --- /dev/null +++ b/src/qt/forms/lookuptxdialog.ui @@ -0,0 +1,116 @@ + + + LookupTXDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + false + + + background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; + + + true + + + 3 + + + + + + + 0 + + + + + true + + + + 0 + 0 + + + + Search Transaction: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + + + Search + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 6 + 20 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + + + + + diff --git a/src/qt/forms/metadexcanceldialog.ui b/src/qt/forms/metadexcanceldialog.ui new file mode 100755 index 0000000000000..3e6cc1a5ba757 --- /dev/null +++ b/src/qt/forms/metadexcanceldialog.ui @@ -0,0 +1,278 @@ + + + MetaDExCancelDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + 0 + + + + + + 75 + true + + + + padding-bottom:4px; + + + Cancellation Address: + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + 75 + true + + + + padding-top:10px;padding-bottom:4px; + + + Cancellation Type: + + + + + + + padding-left:10px; + + + Cancel by pair + + + + + + + padding-left:10px; + + + Cancel by price + + + + + + + padding-left:10px; + + + Cancel everything + + + + + + + + 75 + true + + + + padding-top:10px;padding-bottom:4px; + + + Cancellation Criteria: + + + + + + + 0 + + + 10 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Send Cancel Request + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/forms/metadexdialog.ui b/src/qt/forms/metadexdialog.ui new file mode 100755 index 0000000000000..b64afae621b3d --- /dev/null +++ b/src/qt/forms/metadexdialog.ui @@ -0,0 +1,674 @@ + + + MetaDExDialog + + + + 0 + 0 + 704 + 474 + + + + Form + + + + + + false + + + background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; + + + true + + + 3 + + + + + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 10 + + + + + 6 + + + 10 + + + 0 + + + + + 6 + + + 0 + + + + + + 75 + true + + + + Trade MaidSafeCoin (#3) for Mastercoin + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 20 + + + + + + + + + 50 + false + + + + Switch Markets: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 80 + 0 + + + + Switch + + + + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 75 + true + + + + BUY SP#3 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Your Balance 0.000000000 MSC + + + + + + + + + 0 + + + + + Address: + + + + + + + + 0 + 0 + + + + + + + + + + 0 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 0 + + + + + Amount (SPT): + + + + + + + + + + + + + Price Per SPT: + + + + + + + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + SPT + + + + + + + TMSC + + + + + + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Total Price: + + + + + + + + 75 + true + + + + 0.00000000 MSC + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Buy SP#3 + + + + + + + + + 0 + + + + + + 75 + true + + + + BUY OFFERS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + 75 + true + + + + SELL SP#3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Your Balance 0.000000000 SPT + + + + + + + + + 0 + + + + + Address: + + + + + + + + 0 + 0 + + + + + + + + + + 0 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 6 + + + + + Amount (SPT): + + + + + + + + + + Price Per SPT: + + + + + + + + + + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + SPT + + + + + + + TMSC + + + + + + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Total Price: + + + + + + + + 75 + true + + + + 0.00000000 MSC + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sell SP#3 + + + + + + + + + 0 + + + + + + 75 + true + + + + SELL OFFERS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + 75 + true + + + + color:rgb(167, 125, 19) + + + * YOU HAVE TRANSACTIONS WAITING FOR CONFIRMATION * + + + Qt::AlignCenter + + + + + + + + diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index 53d416ef3801a..1d7875915958c 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -36,6 +36,12 @@ + + + 0 + 0 + + QFrame::StyledPanel @@ -93,321 +99,45 @@ - - - 12 + + + Qt::NoFocus - - - - - 75 - true - - - - IBeamCursor - - - Unconfirmed transactions to watch-only addresses - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 75 - true - - - - IBeamCursor - - - Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 75 - true - - - - IBeamCursor - - - Mined balance in watch-only addresses that has not yet matured - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 140 - 0 - - - - Qt::Horizontal - - - - - - - Total: - - - - - - - - 75 - true - - - - IBeamCursor - - - Mined balance that has not yet matured - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Immature: - - - - - - - - 75 - true - - - - IBeamCursor - - - Your current total balance - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 75 - true - - - - IBeamCursor - - - Current total balance in watch-only addresses - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Watch-only: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Available: - - - - - - - - 75 - true - - - - IBeamCursor - - - Your current spendable balance - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 75 - true - - - - IBeamCursor - - - Your current balance in watch-only addresses - - - 0.000 000 00 BTC - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Pending: - - - - - - - Spendable: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + + false + + + QListWidget { background-color:transparent } + + + QFrame::NoFrame + + + QFrame::Plain + + + QAbstractItemView::NoSelection + + + QListView::Fixed + + - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + 0 + 0 + + QFrame::StyledPanel @@ -483,26 +213,39 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + + Qt::Vertical + + + + 1 + 1 + + + + diff --git a/src/qt/forms/sendmpdialog.ui b/src/qt/forms/sendmpdialog.ui new file mode 100644 index 0000000000000..dcda48c6e8c6e --- /dev/null +++ b/src/qt/forms/sendmpdialog.ui @@ -0,0 +1,362 @@ + + + SendMPDialog + + + + 0 + 0 + 850 + 400 + + + + Send Coins + + + true + + + + 8 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 832 + 351 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + 95 + 0 + + + + Send From: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + 0 + 0 + + + + + + + + Available: 0.00000000 MSC + + + 8 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + + + + + 95 + 0 + + + + Qt::LeftToRight + + + Send To: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + + 0 + + + + + + 95 + 0 + + + + Amount: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 8 + + + + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + + 8 + + + + QLabel { +border: 1px solid rgb(142,61,0);padding:5px 5px 5px 5px;background-repeat: no-repeat;background-position: 10px center;color: #133959;background-color: rgb(255,191,142); color:rgb(142,61,0);} + + + Warning: You will not be able to send this transaction. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + + + + + 150 + 0 + + + + Confirm the send action + + + S&end + + + + :/icons/send:/icons/send + + + true + + + + + + + + 0 + 0 + + + + Clear all fields of the form. + + + Clear &All + + + + :/icons/remove:/icons/remove + + + 300 + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 3 + + + + + + 0 + 0 + + + + IBeamCursor + + + 123.456 MSC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + diff --git a/src/qt/forms/tradehistorydialog.ui b/src/qt/forms/tradehistorydialog.ui new file mode 100644 index 0000000000000..f579d456f6f41 --- /dev/null +++ b/src/qt/forms/tradehistorydialog.ui @@ -0,0 +1,31 @@ + + + tradeHistoryDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + 0 + + + + + + + + + + + diff --git a/src/qt/forms/txhistorydialog.ui b/src/qt/forms/txhistorydialog.ui new file mode 100644 index 0000000000000..941d74c579eda --- /dev/null +++ b/src/qt/forms/txhistorydialog.ui @@ -0,0 +1,31 @@ + + + txHistoryDialog + + + + 0 + 0 + 664 + 474 + + + + Form + + + + + + 0 + + + + + + + + + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index de8edef792774..902f34c69ee1a 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -535,14 +535,31 @@ void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int o } } + // When the tabless geometry is ready, we manually perform the stretch of the "Message" column, // as the "Stretch" resize mode does not allow for interactive resizing. +// Since we've borrowed TableViewLastColumnResizingFixer for some of the Omni tables too, we need to +// watch for this action and override it for Omni tables to avoid making a mess of the wrong column widths. void TableViewLastColumnResizingFixer::on_geometriesChanged() { if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) { - disconnectViewHeadersSignals(); - resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); + disconnectViewHeadersSignals(); // must disconnect signalling here + // evaluate whether this is an Omni layout + QAbstractItemModel* abstractModel = this->tableView->model(); + bool omniLayout = false; + bool omniBalanceLayout = false; + bool omniTradeHistoryLayout = false; + if (abstractModel->columnCount() > 3) { // ensure the columns are there before we try to reference them + if (abstractModel->headerData(2, Qt::Horizontal).toString()=="Reserved") { omniLayout = true; omniBalanceLayout = true; } + if (abstractModel->headerData(3, Qt::Horizontal).toString()=="Date") { omniLayout = true; omniTradeHistoryLayout = true; } + } + if (omniLayout) { // this is an Omni balance or trade layout, override column resize + if (omniBalanceLayout) resizeColumn(omniBalanceOverrideColumnIndex, getAvailableWidthForColumn(omniBalanceOverrideColumnIndex)); + if (omniTradeHistoryLayout) resizeColumn(omniTradeHistoryOverrideColumnIndex, getAvailableWidthForColumn(omniTradeHistoryOverrideColumnIndex)); + } else { // else it's an original Bitcoin layout or Omni tx history layout, leave as is + resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); + } connectViewHeadersSignals(); } } @@ -559,6 +576,8 @@ TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* t columnCount = tableView->horizontalHeader()->count(); lastColumnIndex = columnCount - 1; secondToLastColumnIndex = columnCount - 2; + omniBalanceOverrideColumnIndex = 1; // we can safely hardcode this for now + omniTradeHistoryOverrideColumnIndex = 5; // as above tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth); setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 41ff608715353..14db050553c3b 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -152,7 +152,8 @@ namespace GUIUtil int lastColumnIndex; int columnCount; int secondToLastColumnIndex; - + int omniBalanceOverrideColumnIndex; + int omniTradeHistoryOverrideColumnIndex; void adjustTableColumnsWidth(); int getAvailableWidthForColumn(int column); int getColumnsWidth(); diff --git a/src/qt/lookupaddressdialog.cpp b/src/qt/lookupaddressdialog.cpp new file mode 100644 index 0000000000000..56d8ec3b18497 --- /dev/null +++ b/src/qt/lookupaddressdialog.cpp @@ -0,0 +1,297 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "lookupaddressdialog.h" +#include "ui_lookupaddressdialog.h" + +#include "guiutil.h" + +#include "mastercore.h" + +#include "base58.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" /* for USE_QRCODE */ +#endif + +#ifdef USE_QRCODE +#include +#endif + +using std::ostringstream; +using std::string; + +using namespace mastercore; + +MPQRImageWidget::MPQRImageWidget(QWidget *parent): + QLabel(parent), contextMenu(0) +{ + contextMenu = new QMenu(); + QAction *saveImageAction = new QAction(tr("&Save Image..."), this); + connect(saveImageAction, SIGNAL(triggered()), this, SLOT(saveImage())); + contextMenu->addAction(saveImageAction); + QAction *copyImageAction = new QAction(tr("&Copy Image"), this); + connect(copyImageAction, SIGNAL(triggered()), this, SLOT(copyImage())); + contextMenu->addAction(copyImageAction); +} + +QImage MPQRImageWidget::exportImage() +{ + if(!pixmap()) + return QImage(); + return pixmap()->toImage().scaled(256,256); +} + +void MPQRImageWidget::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton && pixmap()) + { + event->accept(); + QMimeData *mimeData = new QMimeData; + mimeData->setImageData(exportImage()); + + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->exec(); + } else { + QLabel::mousePressEvent(event); + } +} + +void MPQRImageWidget::saveImage() +{ + if(!pixmap()) + return; + QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), NULL); + if (!fn.isEmpty()) + { + exportImage().save(fn); + } +} + +void MPQRImageWidget::copyImage() +{ + if(!pixmap()) + return; + QApplication::clipboard()->setImage(exportImage()); +} + +void MPQRImageWidget::contextMenuEvent(QContextMenuEvent *event) +{ + if(!pixmap()) + return; + contextMenu->exec(event->globalPos()); +} + +LookupAddressDialog::LookupAddressDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::LookupAddressDialog) +{ + ui->setupUi(this); + +#if QT_VERSION >= 0x040700 + ui->searchLineEdit->setPlaceholderText("Search address"); +#endif + + // connect actions + connect(ui->searchButton, SIGNAL(clicked()), this, SLOT(searchButtonClicked())); + + // hide balance labels + QLabel* balances[] = { ui->propertyLabel1, ui->propertyLabel2, ui->propertyLabel3, ui->propertyLabel4, ui->propertyLabel5, ui->propertyLabel6, ui->propertyLabel7, ui->propertyLabel8, ui->propertyLabel9, ui->propertyLabel10 }; + QLabel* labels[] = { ui->property1, ui->property2, ui->property3, ui->property4, ui->property5, ui->property6, ui->property7, ui->property8, ui->property9, ui->property10 }; + int pItem = 0; + for (pItem = 1; pItem < 11; pItem++) + { + labels[pItem-1]->setVisible(false); + balances[pItem-1]->setVisible(false); + } + ui->onlyLabel->setVisible(false); + ui->frame->setVisible(false); +} + +LookupAddressDialog::~LookupAddressDialog() +{ + delete ui; +} + +void LookupAddressDialog::searchAddress() +{ + // search function to lookup address + string searchText = ui->searchLineEdit->text().toStdString(); + + // first let's check if we have a searchText, if not do nothing + if (searchText.empty()) return; + + // lets see if the string is a valid bitcoin address + CBitcoinAddress address; + address.SetString(searchText); // no null check on searchText required we've already checked it's not empty above + if (address.IsValid()) //do what? + { + // update top fields + ui->addressLabel->setText(QString::fromStdString(searchText)); + if ((searchText.substr(0,1) == "1") || (searchText.substr(0,1) == "m") || (searchText.substr(0,1) == "n")) ui->addressTypeLabel->setText("Public Key Hash"); + if ((searchText.substr(0,1) == "2") || (searchText.substr(0,1) == "3")) ui->addressTypeLabel->setText("Pay to Script Hash"); + if (IsMyAddress(searchText)) { ui->isMineLabel->setText("Yes"); } else { ui->isMineLabel->setText("No"); } + ui->balanceLabel->setText(QString::fromStdString(FormatDivisibleMP(getUserAvailableMPbalance(searchText, 1)) + " MSC")); + // QR + #ifdef USE_QRCODE + ui->QRCode->setText(""); + QRcode *code = QRcode_encodeString(QString::fromStdString(searchText).toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + if (!code) + { + ui->QRCode->setText(tr("Error encoding address into QR Code.")); + } + else + { + QImage myImage = QImage(code->width + 4, code->width + 4, QImage::Format_RGB32); + myImage.fill(0xffffff); + unsigned char *p = code->data; + for (int y = 0; y < code->width; y++) + { + for (int x = 0; x < code->width; x++) + { + myImage.setPixel(x + 2, y + 2, ((*p & 1) ? 0x0 : 0xffffff)); + p++; + } + } + QRcode_free(code); + ui->QRCode->setPixmap(QPixmap::fromImage(myImage).scaled(96, 96)); + } + #endif + + //scrappy way to do this, find a more efficient way of interacting with labels + //show first 10 SPs with balances - needs to be converted to listwidget or something + unsigned int propertyId; + unsigned int lastFoundPropertyIdMainEco = 1; + unsigned int lastFoundPropertyIdTestEco = 1; + string pName[12]; // TODO: enough slots? + uint64_t pBal[12]; + bool pDivisible[12]; + bool pFound[12]; + unsigned int pItem; + bool foundProperty = false; + for (pItem = 1; pItem < 12; pItem++) + { + pFound[pItem] = false; + for (propertyId = lastFoundPropertyIdMainEco+1; propertyId<10000; propertyId++) + { + foundProperty=false; + if (getUserAvailableMPbalance(searchText, propertyId) > 0) + { + lastFoundPropertyIdMainEco = propertyId; + foundProperty=true; + pName[pItem] = getPropertyName(propertyId).c_str(); + if(pName[pItem].size()>32) pName[pItem]=pName[pItem].substr(0,32)+"..."; + pName[pItem] += " (#" + static_cast( &(ostringstream() << propertyId) )->str() + ")"; + pBal[pItem] = getUserAvailableMPbalance(searchText, propertyId); + pDivisible[pItem] = isPropertyDivisible(propertyId); + pFound[pItem] = true; + break; + } + } + + // have we found a property in main eco? If not let's try test eco + if (!foundProperty) + { + for (propertyId = lastFoundPropertyIdTestEco+1; propertyId<10000; propertyId++) + { + if (getUserAvailableMPbalance(searchText, propertyId+2147483647) > 0) + { + lastFoundPropertyIdTestEco = propertyId; + foundProperty=true; + pName[pItem] = getPropertyName(propertyId+2147483647).c_str(); + if(pName[pItem].size()>32) pName[pItem]=pName[pItem].substr(0,32)+"..."; + pName[pItem] += " (#" + static_cast( &(ostringstream() << propertyId+2147483647) )->str() + ")"; + pBal[pItem] = getUserAvailableMPbalance(searchText, propertyId+2147483647); + pDivisible[pItem] = isPropertyDivisible(propertyId+2147483647); + pFound[pItem] = true; + break; + } + } + } + } + + // set balance info + ui->frame->setVisible(true); + QLabel* balances[] = { ui->propertyLabel1, ui->propertyLabel2, ui->propertyLabel3, ui->propertyLabel4, ui->propertyLabel5, ui->propertyLabel6, ui->propertyLabel7, ui->propertyLabel8, ui->propertyLabel9, ui->propertyLabel10 }; + QLabel* labels[] = { ui->property1, ui->property2, ui->property3, ui->property4, ui->property5, ui->property6, ui->property7, ui->property8, ui->property9, ui->property10 }; + for (pItem = 1; pItem < 11; pItem++) + { + if (pFound[pItem]) + { + labels[pItem-1]->setVisible(true); + balances[pItem-1]->setVisible(true); + labels[pItem-1]->setText(pName[pItem].c_str()); + string tokenLabel = " SPT"; + if (pName[pItem]=="Test MasterCoin (#2)") { tokenLabel = " TMSC"; } + if (pDivisible[pItem]) + { + balances[pItem-1]->setText(QString::fromStdString(FormatDivisibleMP(pBal[pItem]) + tokenLabel)); + } + else + { + string balText = static_cast( &(ostringstream() << pBal[pItem]) )->str(); + balText += tokenLabel; + balances[pItem-1]->setText(balText.c_str()); + } + } + else + { + labels[pItem-1]->setVisible(false); + balances[pItem-1]->setVisible(false); + } + } + if (pFound[11]) { ui->onlyLabel->setVisible(true); } else { ui->onlyLabel->setVisible(false); } + } + else + { + // hide balance labels + QLabel* balances[] = { ui->propertyLabel1, ui->propertyLabel2, ui->propertyLabel3, ui->propertyLabel4, ui->propertyLabel5, ui->propertyLabel6, ui->propertyLabel7, ui->propertyLabel8, ui->propertyLabel9, ui->propertyLabel10 }; + QLabel* labels[] = { ui->property1, ui->property2, ui->property3, ui->property4, ui->property5, ui->property6, ui->property7, ui->property8, ui->property9, ui->property10 }; + int pItem = 0; + for (pItem = 1; pItem < 11; pItem++) + { + labels[pItem-1]->setVisible(false); + balances[pItem-1]->setVisible(false); + } + ui->addressLabel->setText("N/A"); + ui->addressTypeLabel->setText("N/A"); + ui->isMineLabel->setText("N/A"); + ui->frame->setVisible(false); + // show error message + string strText = "The address entered was not valid."; + QString strQText = QString::fromStdString(strText); + QMessageBox errorDialog; + errorDialog.setIcon(QMessageBox::Critical); + errorDialog.setWindowTitle("Address error"); + errorDialog.setText(strQText); + errorDialog.setStandardButtons(QMessageBox::Ok); + errorDialog.setDefaultButton(QMessageBox::Ok); + if(errorDialog.exec() == QMessageBox::Ok) { } // no other button to choose, acknowledged + } +} + +void LookupAddressDialog::searchButtonClicked() +{ + searchAddress(); +} diff --git a/src/qt/lookupaddressdialog.h b/src/qt/lookupaddressdialog.h new file mode 100644 index 0000000000000..cff30ec696e72 --- /dev/null +++ b/src/qt/lookupaddressdialog.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef LOOKUPADDRESSDIALOG_H +#define LOOKUPADDRESSDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QContextMenuEvent; +class QImage; +class QMenu; +class QMouseEvent; +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class LookupAddressDialog; +} + +/* Label widget for QR code. This image can be dragged, dropped, copied and saved + * to disk. + */ +class MPQRImageWidget : public QLabel +{ + Q_OBJECT + +public: + explicit MPQRImageWidget(QWidget *parent = 0); + QImage exportImage(); + +public slots: + void saveImage(); + void copyImage(); + +protected: + virtual void mousePressEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event); + +private: + QMenu *contextMenu; +}; + +/** Dialog for looking up Master Protocol address */ +class LookupAddressDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LookupAddressDialog(QWidget *parent = 0); + ~LookupAddressDialog(); + + void searchAddress(); + +public slots: + void searchButtonClicked(); + +private: + Ui::LookupAddressDialog *ui; + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // LOOKUPADDRESSDIALOG_H diff --git a/src/qt/lookupspdialog.cpp b/src/qt/lookupspdialog.cpp new file mode 100644 index 0000000000000..78b8aa2996efb --- /dev/null +++ b/src/qt/lookupspdialog.cpp @@ -0,0 +1,389 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "lookupspdialog.h" +#include "ui_lookupspdialog.h" + +#include "guiutil.h" + +#include "mastercore.h" +#include "mastercore_sp.h" + +#include "base58.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using std::ostringstream; +using std::string; + +using namespace mastercore; + +LookupSPDialog::LookupSPDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::LookupSPDialog), + model(0) +{ + ui->setupUi(this); + +#if QT_VERSION >= 0x040700 + ui->searchLineEdit->setPlaceholderText("ID, name or issuer"); +#endif + + // connect actions + connect(ui->matchingComboBox, SIGNAL(activated(int)), this, SLOT(matchingComboBoxChanged(int))); + connect(ui->searchButton, SIGNAL(clicked()), this, SLOT(searchButtonClicked())); + + // hide crowd info + ui->desired->setVisible(false); + ui->tokensperunit->setVisible(false); + ui->deadline->setVisible(false); + ui->bonus->setVisible(false); + ui->issuerperc->setVisible(false); + ui->desiredLabel->setVisible(false); + ui->tokensPerUnitLabel->setVisible(false); + ui->deadlineLabel->setVisible(false); + ui->bonusLabel->setVisible(false); + ui->issuerPercLabel->setVisible(false); + ui->activeLabel->setVisible(false); + ui->active->setText("Not applicable."); + ui->topFrame->setVisible(false); + ui->leftFrame->setVisible(false); + ui->rightFrame->setVisible(false); +} + +LookupSPDialog::~LookupSPDialog() +{ + delete ui; +} + +void LookupSPDialog::searchSP() +{ + // search function to lookup properties, we want this search function to be as capable as possible to + // help users find the property they're looking for via search terms they may want to use + int searchParamType = 0; + string searchText = ui->searchLineEdit->text().toStdString(); + unsigned int searchPropertyId = 0; + + // first let's check if we have a searchText, if not do nothing + if (searchText.empty()) return; + + // try seeing if we have a numerical search string, if so treat it as a property ID search + try + { + searchPropertyId = boost::lexical_cast(searchText); + searchParamType = 1; // search by propertyId + } + catch(const boost::bad_lexical_cast &e) { } + if (searchParamType == 1 && 0 >= searchPropertyId) searchParamType = 0; // we got a number but it's <=0 + + // next if not positive numerical, lets see if the string is a valid bitcoin address for issuer search + if (searchParamType == 0) + { + CBitcoinAddress address; + address.SetString(searchText); // no null check on searchText required we've already checked it's not empty above + if (address.IsValid()) searchParamType = 2; // search by address; + } + + // next if we have a "*" only, we'll assume the user wants to request all properties + // Znote - unsure about this, adding for now but may be removed in future when number of properties is + // higher because it will get out of hand to add 100,000 properties to a single combo unfiltered + if ((searchParamType == 0) && (searchText == "*")) { searchParamType = 4; } + + // if we still don't have a param we'll search against free text in the name + if (searchParamType == 0) searchParamType = 3; // search by free text + + // clear matching results combo + ui->matchingComboBox->clear(); + bool spExists; + unsigned int tmpPropertyId; + unsigned int nextSPID; + unsigned int nextTestSPID; + unsigned int propertyId; + QString strId; + switch(searchParamType) + { + case 1: //search by property Id + // convert search string to ID + strId = QString::fromStdString(searchText); + propertyId = strId.toUInt(); + // check if this property ID exists, if not no match to populate and just return + spExists = _my_sps->hasSP(propertyId); + if (spExists) + { + addSPToMatchingResults(propertyId); + updateDisplayedProperty(); //show straight away, only one to select + } + else + { + return; + } + break; + case 2: //search by address + // iterate through my_sps looking for the issuer address and add any properties issued by said address to matchingcombo + // talk with @Michael @Bart to see if perhaps a more efficient way to do this, but not major issue as only run on user request + nextSPID = _my_sps->peekNextSPID(1); + nextTestSPID = _my_sps->peekNextSPID(2); + for (tmpPropertyId = 1; tmpPropertyIdgetSP(tmpPropertyId, sp)) + { + if (sp.issuer == searchText) + { + addSPToMatchingResults(tmpPropertyId); + } + } + } + for (tmpPropertyId = TEST_ECO_PROPERTY_1; tmpPropertyIdgetSP(tmpPropertyId, sp)) + { + if (sp.issuer == searchText) + { + addSPToMatchingResults(tmpPropertyId); + } + } + } + break; + case 3: //search by freetext + // iterate through my_sps and see if property name contains the search text + nextSPID = _my_sps->peekNextSPID(1); + nextTestSPID = _my_sps->peekNextSPID(2); + for (tmpPropertyId = 1; tmpPropertyIdgetSP(tmpPropertyId, sp)) + { + // make the search case insensitive + string lowerName = sp.name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + string lowerSearch = searchText; + std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), ::tolower); + size_t loc = lowerName.find(lowerSearch); + if (loc!=std::string::npos) + { + addSPToMatchingResults(tmpPropertyId); + } + } + } + for (tmpPropertyId = TEST_ECO_PROPERTY_1; tmpPropertyIdgetSP(tmpPropertyId, sp)) + { + // make the search case insensitive + string lowerName = sp.name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + string lowerSearch = searchText; + std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), ::tolower); + size_t loc = lowerName.find(lowerSearch); + if (loc!=std::string::npos) + { + addSPToMatchingResults(tmpPropertyId); + } + } + } + break; + case 4: // grab everything + nextSPID = _my_sps->peekNextSPID(1); + for (tmpPropertyId = 1; tmpPropertyIdgetSP(tmpPropertyId, sp)) { addSPToMatchingResults(tmpPropertyId); } + } + nextTestSPID = _my_sps->peekNextSPID(2); + for (tmpPropertyId = TEST_ECO_PROPERTY_1; tmpPropertyIdgetSP(tmpPropertyId, sp)) { addSPToMatchingResults(tmpPropertyId); } + } + break; + } + +} + +void LookupSPDialog::addSPToMatchingResults(unsigned int propertyId) +{ + // verify the supplied property exists (sanity check) then populate the matching results combo box + bool spExists = _my_sps->hasSP(propertyId); + if (spExists) + { + string spName; + spName = getPropertyName(propertyId).c_str(); + if(spName.size()>40) spName=spName.substr(0,40)+"..."; + string spId = static_cast( &(ostringstream() << propertyId) )->str(); + spName += " (#" + spId + ")"; + ui->matchingComboBox->addItem(spName.c_str(),spId.c_str()); + } + else + { + return; + } +} + +void LookupSPDialog::updateDisplayedProperty() +{ + uint64_t maxLabelWidth=70; // fairly safe value for now, next version consider wrapping + // instead of truncation and evaluate effects on vertical layout + QString strId = ui->matchingComboBox->itemData(ui->matchingComboBox->currentIndex()).toString(); + // protect against an empty matchedComboBox + if (strId.toStdString().empty()) return; + + // map property Id + unsigned int propertyId = strId.toUInt(); + CMPSPInfo::Entry sp; + if (false == _my_sps->getSP(propertyId, sp)) { return; } // something has gone wrong, don't attempt to display non-existent property + + // populate the fields + bool divisible=sp.isDivisible(); + if (divisible) { ui->divisibleLabel->setText("Yes"); } else { ui->divisibleLabel->setText("No"); } + if (isTestEcosystemProperty(propertyId)) { ui->ecosystemLabel->setText("Test"); } else { ui->ecosystemLabel->setText("Production"); } + ui->propertyIDLabel->setText(QString::fromStdString(FormatIndivisibleMP(propertyId))); + if(sp.name.size()>maxLabelWidth) { + ui->nameLabel->setText(QString::fromStdString(sp.name.substr(0,maxLabelWidth)+"...")); + } else { + ui->nameLabel->setText(QString::fromStdString(sp.name)); + } + std::string dispCat; + dispCat = sp.category + " > " + sp.subcategory; + if(dispCat.size()>maxLabelWidth) { + if(sp.category.size()>maxLabelWidth/2) { + dispCat = sp.category.substr(0,maxLabelWidth/2)+"..."; + } else { + dispCat = sp.category; + } + dispCat += " > "; + if(sp.subcategory.size()>maxLabelWidth/2) { + dispCat += sp.subcategory.substr(0,maxLabelWidth/2)+"..."; + } else { + dispCat += sp.subcategory; + } + } + ui->categoryLabel->setText(QString::fromStdString(dispCat)); + if(sp.data.size()>maxLabelWidth) { + ui->dataLabel->setText(QString::fromStdString(sp.data.substr(0,maxLabelWidth)+"...")); + } else { + ui->dataLabel->setText(QString::fromStdString(sp.data)); + } + if(sp.url.size()>maxLabelWidth) { + ui->urlLabel->setText(QString::fromStdString(sp.url.substr(0,maxLabelWidth)+"...")); + } else { + ui->urlLabel->setText(QString::fromStdString(sp.url)); + } + // overrides for MSC and TMSC - temporary code can be removed when metadata is modified + if(propertyId==1) { + ui->dataLabel->setText(QString::fromStdString("MasterCoin serve as the binding between Bitcoin, smart properties and contracts created on the Omni Layer")); + ui->urlLabel->setText(QString::fromStdString("http://www.mastercoin.org")); + } + if(propertyId==2) { + ui->dataLabel->setText(QString::fromStdString("Test MasterCoin serve as the binding between Bitcoin, smart properties and contracts created on the Omni Layer")); + ui->urlLabel->setText(QString::fromStdString("http://www.mastercoin.org")); + } + + string strTotalTokens; + string strWalletTokens; + int64_t totalTokens = getTotalTokens(propertyId); + int64_t walletTokens = 0; + if (propertyId<2147483648) + { walletTokens = global_balance_money_maineco[propertyId]; } + else + { walletTokens = global_balance_money_testeco[propertyId-2147483647]; } + string tokenLabel; + if (propertyId > 2) + { + tokenLabel = " SPT"; + } + else + { + if (propertyId == 1) { tokenLabel = " MSC"; } else { tokenLabel = " TMSC"; } + } + if (divisible) { strTotalTokens = FormatDivisibleMP(totalTokens); } else { strTotalTokens = FormatIndivisibleMP(totalTokens); } + if (divisible) { strWalletTokens = FormatDivisibleMP(walletTokens); } else { strWalletTokens = FormatIndivisibleMP(walletTokens); } + ui->totalTokensLabel->setText(QString::fromStdString(strTotalTokens + tokenLabel)); + ui->walletBalanceLabel->setText(QString::fromStdString(strWalletTokens + tokenLabel)); + ui->issuerLabel->setText(QString::fromStdString(sp.issuer)); + bool fixedIssuance = sp.fixed; + bool manualIssuance = sp.manual; + if ((!fixedIssuance) && (!manualIssuance) && (propertyId > 2)) + { + ui->issuanceTypeLabel->setText("Crowdsale"); + // obtain crowdinfo + bool active = isCrowdsaleActive(propertyId); + int64_t deadline = sp.deadline; + uint8_t earlyBonus = sp.early_bird; + uint8_t percentToIssuer = sp.percentage; + int64_t tokensPerUnit = sp.num_tokens; + int64_t propertyIdDesired = sp.property_desired; + QDateTime qDeadline; + qDeadline.setTime_t(deadline); + string desiredText = getPropertyName(propertyIdDesired).c_str(); + if(desiredText.size()>22) desiredText=desiredText.substr(0,22)+"..."; + string spId = static_cast( &(ostringstream() << propertyIdDesired) )->str(); + desiredText += " (#" + spId + ")"; + string tokensPerUnitText; + if (divisible) { tokensPerUnitText = FormatDivisibleMP(tokensPerUnit); } else { tokensPerUnitText = FormatIndivisibleMP(tokensPerUnit); } + if (active) { ui->activeLabel->setText("Yes"); } else { ui->activeLabel->setText("No"); } + // populate crowdinfo + ui->desiredLabel->setText(QString::fromStdString(desiredText)); + ui->tokensPerUnitLabel->setText(QString::fromStdString(tokensPerUnitText)); + ui->deadlineLabel->setText(qDeadline.toString(Qt::SystemLocaleShortDate)); + ui->bonusLabel->setText(QString::fromStdString(FormatIndivisibleMP((int64_t)earlyBonus) + "%")); + ui->issuerPercLabel->setText(QString::fromStdString(FormatIndivisibleMP((int64_t)percentToIssuer) + "%")); + // show crowdinfo + ui->desired->setVisible(true); + ui->tokensperunit->setVisible(true); + ui->deadline->setVisible(true); + ui->bonus->setVisible(true); + ui->issuerperc->setVisible(true); + ui->desiredLabel->setVisible(true); + ui->tokensPerUnitLabel->setVisible(true); + ui->deadlineLabel->setVisible(true); + ui->bonusLabel->setVisible(true); + ui->issuerPercLabel->setVisible(true); + ui->activeLabel->setVisible(true); + ui->active->setText("Active:"); + } + else + { + ui->issuanceTypeLabel->setText("Exodus"); + if (fixedIssuance) ui->issuanceTypeLabel->setText("Fixed"); + if (manualIssuance) ui->issuanceTypeLabel->setText("Manual"); + // hide crowdinfo + ui->desired->setVisible(false); + ui->tokensperunit->setVisible(false); + ui->deadline->setVisible(false); + ui->bonus->setVisible(false); + ui->issuerperc->setVisible(false); + ui->desiredLabel->setVisible(false); + ui->tokensPerUnitLabel->setVisible(false); + ui->deadlineLabel->setVisible(false); + ui->bonusLabel->setVisible(false); + ui->issuerPercLabel->setVisible(false); + ui->activeLabel->setVisible(false); + ui->active->setText("Not applicable."); + } + ui->topFrame->setVisible(true); + ui->leftFrame->setVisible(true); + ui->rightFrame->setVisible(true); +} + +void LookupSPDialog::searchButtonClicked() +{ + searchSP(); +} + +void LookupSPDialog::matchingComboBoxChanged(int idx) +{ + updateDisplayedProperty(); +} diff --git a/src/qt/lookupspdialog.h b/src/qt/lookupspdialog.h new file mode 100644 index 0000000000000..c6387a22fc9e2 --- /dev/null +++ b/src/qt/lookupspdialog.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef LOOKUPSPDIALOG_H +#define LOOKUPSPDIALOG_H + +#include + +class WalletModel; + +QT_BEGIN_NAMESPACE +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class LookupSPDialog; +} + +/** Dialog for looking up Master Protocol tokens */ +class LookupSPDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LookupSPDialog(QWidget *parent = 0); + ~LookupSPDialog(); + + void searchSP(); + void updateDisplayedProperty(); + void addSPToMatchingResults(unsigned int propertyId); + +public slots: + void searchButtonClicked(); + void matchingComboBoxChanged(int idx); + +private: + Ui::LookupSPDialog *ui; + WalletModel *model; + +//private slots: + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // LOOKUPSPDIALOG_H diff --git a/src/qt/lookuptxdialog.cpp b/src/qt/lookuptxdialog.cpp new file mode 100644 index 0000000000000..3aaf101a4d20f --- /dev/null +++ b/src/qt/lookuptxdialog.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "lookuptxdialog.h" +#include "ui_lookuptxdialog.h" + +#include "mastercore_errors.h" +#include "mastercore_rpc.h" + +#include "uint256.h" + +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer_template.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +using std::string; +using namespace json_spirit; + +LookupTXDialog::LookupTXDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::LookupTXDialog) +{ + ui->setupUi(this); + +#if QT_VERSION >= 0x040700 + ui->searchLineEdit->setPlaceholderText("Search transaction"); +#endif + + // connect actions + connect(ui->searchButton, SIGNAL(clicked()), this, SLOT(searchButtonClicked())); +} + +LookupTXDialog::~LookupTXDialog() +{ + delete ui; +} + +void LookupTXDialog::searchTX() +{ + // search function to lookup address + string searchText = ui->searchLineEdit->text().toStdString(); + + // first let's check if we have a searchText, if not do nothing + if (searchText.empty()) return; + + uint256 hash; + hash.SetHex(searchText); + Object txobj; + // make a request to new RPC populator function to populate a transaction object + int populateResult = populateRPCTransactionObject(hash, &txobj, ""); + if (0<=populateResult) + { + std::string strTXText = write_string(Value(txobj), false) + "\n"; + // clean up + string from = ","; + string to = ",\n "; + size_t start_pos = 0; + while((start_pos = strTXText.find(from, start_pos)) != std::string::npos) + { + strTXText.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + from = ":"; + to = " : "; + start_pos = 0; + while((start_pos = strTXText.find(from, start_pos)) != std::string::npos) + { + strTXText.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + from = "{"; + to = "{\n "; + start_pos = 0; + while((start_pos = strTXText.find(from, start_pos)) != std::string::npos) + { + strTXText.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + from = "}"; + to = "\n}"; + start_pos = 0; + while((start_pos = strTXText.find(from, start_pos)) != std::string::npos) + { + strTXText.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + + QString txText = QString::fromStdString(strTXText); + QDialog *txDlg = new QDialog; + QLayout *dlgLayout = new QVBoxLayout; + dlgLayout->setSpacing(12); + dlgLayout->setMargin(12); + QTextEdit *dlgTextEdit = new QTextEdit; + dlgTextEdit->setText(txText); + dlgTextEdit->setStatusTip("Transaction Information"); + dlgLayout->addWidget(dlgTextEdit); + txDlg->setWindowTitle("Transaction Information"); + QPushButton *closeButton = new QPushButton(tr("&Close")); + closeButton->setDefault(true); + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(closeButton, QDialogButtonBox::AcceptRole); + dlgLayout->addWidget(buttonBox); + txDlg->setLayout(dlgLayout); + txDlg->resize(700, 360); + connect(buttonBox, SIGNAL(accepted()), txDlg, SLOT(accept())); + txDlg->setAttribute(Qt::WA_DeleteOnClose); //delete once it's closed + if (txDlg->exec() == QDialog::Accepted) { } else { } //do nothing but close + } + else + { + // show error message + string strText = "The transaction hash entered is "; + switch(populateResult) { + case MP_TX_NOT_FOUND: + strText += "not a valid Bitcoin or Omni transaction. Please check the transaction hash " + "entered and try again."; + break; + case MP_TX_UNCONFIRMED: + strText += "unconfirmed. Toolbox lookup of transactions is currently only available for " + "confirmed transactions.\n\nTip: You can view your own outgoing unconfirmed " + "transactions in the transactions tab."; + break; + case MP_TX_IS_NOT_MASTER_PROTOCOL: + strText += "a Bitcoin transaction only.\n\nTip: You can use the debug console " + "'gettransaction' command to lookup specific Bitcoin transactions."; + break; + } + QString strQText = QString::fromStdString(strText); + QMessageBox errorDialog; + errorDialog.setIcon(QMessageBox::Critical); + errorDialog.setWindowTitle("TXID error"); + errorDialog.setText(strQText); + errorDialog.setStandardButtons(QMessageBox::Ok); + errorDialog.setDefaultButton(QMessageBox::Ok); + if(errorDialog.exec() == QMessageBox::Ok) { } // no other button to choose, acknowledged + } +} + +void LookupTXDialog::searchButtonClicked() +{ + searchTX(); +} diff --git a/src/qt/lookuptxdialog.h b/src/qt/lookuptxdialog.h new file mode 100644 index 0000000000000..858d5c3260b4d --- /dev/null +++ b/src/qt/lookuptxdialog.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef LOOKUPTXDIALOG_H +#define LOOKUPTXDIALOG_H + +#include + +QT_BEGIN_NAMESPACE +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class LookupTXDialog; +} + +/** Dialog for looking up Omni Layer transactions */ +class LookupTXDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LookupTXDialog(QWidget *parent = 0); + ~LookupTXDialog(); + + void searchTX(); + +public slots: + void searchButtonClicked(); + +private: + Ui::LookupTXDialog *ui; + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // LOOKUPTXDIALOG_H diff --git a/src/qt/metadexcanceldialog.cpp b/src/qt/metadexcanceldialog.cpp new file mode 100755 index 0000000000000..1de013dbc144a --- /dev/null +++ b/src/qt/metadexcanceldialog.cpp @@ -0,0 +1,353 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "metadexcanceldialog.h" +#include "ui_metadexcanceldialog.h" + +#include "mastercore.h" +#include "mastercore_errors.h" +#include "mastercore_mdex.h" +#include "omnicore_createpayload.h" +#include "omnicore_qtutils.h" + +#include "clientmodel.h" +#include "ui_interface.h" +#include "walletmodel.h" + +#include +#include // printf! +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; +using namespace mastercore; + +MetaDExCancelDialog::MetaDExCancelDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MetaDExCancelDialog), + clientModel(0), + walletModel(0) +{ + ui->setupUi(this); + + connect(ui->radioCancelPair, SIGNAL(clicked()),this, SLOT(UpdateCancelCombo())); + connect(ui->radioCancelPrice, SIGNAL(clicked()),this, SLOT(UpdateCancelCombo())); + connect(ui->radioCancelEverything, SIGNAL(clicked()),this, SLOT(UpdateCancelCombo())); + connect(ui->cancelButton, SIGNAL(clicked()),this, SLOT(SendCancelTransaction())); + connect(ui->fromCombo, SIGNAL(activated(int)), this, SLOT(fromAddressComboBoxChanged(int))); + + // perform initial from address population + UpdateAddressSelector(); +} + +MetaDExCancelDialog::~MetaDExCancelDialog() +{ + delete ui; +} + +/** + * Sets the client model. + */ +void MetaDExCancelDialog::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if (model != NULL) { + connect(model, SIGNAL(refreshOmniState()), this, SLOT(RefreshUI())); + } +} + +/** + * Sets the wallet model. + */ +void MetaDExCancelDialog::setWalletModel(WalletModel *model) +{ + this->walletModel = model; +} + +/** + * Refreshes the cancellation address selector + * + * Note: only addresses that have a currently open MetaDEx trade (determined by + * the metadex map) will be shown in the address selector (cancellations sent from + * addresses without an open MetaDEx trade are invalid). + */ +void MetaDExCancelDialog::UpdateAddressSelector() +{ + for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) { + md_PricesMap & prices = my_it->second; + for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) { + md_Set & indexes = (it->second); + for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) { + CMPMetaDEx obj = *it; + if(IsMyAddress(obj.getAddr())) { // this address is ours and has an active MetaDEx trade + int idx = ui->fromCombo->findText(QString::fromStdString(obj.getAddr())); // avoid adding duplicates + if (idx == -1) ui->fromCombo->addItem(QString::fromStdString(obj.getAddr())); + } + } + } + } +} + +/** + * Refreshes the cancel combo when the address selector is changed + */ +void MetaDExCancelDialog::fromAddressComboBoxChanged(int) +{ + UpdateCancelCombo(); // all that's needed at this stage +} + +/** + * Refreshes the cancel combo with the latest data based on the currently selected + * radio button. + */ +void MetaDExCancelDialog::UpdateCancelCombo() +{ + string senderAddress = ui->fromCombo->currentText().toStdString(); + QString existingSelection = ui->cancelCombo->currentText(); + + if (senderAddress.empty()) { + return; // no sender address selected, likely no wallet addresses have open MetaDEx trades + } + + if ((!ui->radioCancelPair->isChecked()) && (!ui->radioCancelPrice->isChecked()) && (!ui->radioCancelEverything->isChecked())) { + return; // no radio button is selected + } + + if (ui->radioCancelEverything->isChecked()) { + ui->cancelCombo->clear(); + ui->cancelCombo->addItem("All currently active sell orders","ALL"); + return; // don't waste time on work we don't need to do for this case + } + + ui->cancelCombo->clear(); + + for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) { + md_PricesMap & prices = my_it->second; + for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) { + md_Set & indexes = it->second; + for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) { + CMPMetaDEx obj = *it; + if(senderAddress == obj.getAddr()) { + bool isBuy = false; // sell or buy? (from UI perspective) + if ((obj.getProperty() == OMNI_PROPERTY_MSC) || (obj.getProperty() == OMNI_PROPERTY_TMSC)) isBuy = true; + string sellToken = getPropertyName(obj.getProperty()).c_str(); + string desiredToken = getPropertyName(obj.getDesProperty()).c_str(); + string sellId = static_cast( &(ostringstream() << obj.getProperty()) )->str(); + string desiredId = static_cast( &(ostringstream() << obj.getDesProperty()) )->str(); + if(sellToken.size()>30) sellToken=sellToken.substr(0,30)+"..."; + sellToken += " (#" + sellId + ")"; + if(desiredToken.size()>30) desiredToken=desiredToken.substr(0,30)+"..."; + desiredToken += " (#" + desiredId + ")"; + string comboStr = "Cancel all orders "; + if (isBuy) { comboStr += "buying " + desiredToken; } else { comboStr += "selling " + sellToken; } + string dataStr = sellId + "/" + desiredId; + if (ui->radioCancelPrice->isChecked()) { // append price if needed + comboStr += " priced at " + StripTrailingZeros(obj.displayUnitPrice()); + if ((obj.getProperty() == OMNI_PROPERTY_MSC) || (obj.getDesProperty() == OMNI_PROPERTY_MSC)) { comboStr += " MSC/SPT"; } else { comboStr += " TMSC/SPT"; } + dataStr += ":" + obj.displayUnitPrice(); + } + int index = ui->cancelCombo->findText(QString::fromStdString(comboStr)); + if ( index == -1 ) { ui->cancelCombo->addItem(QString::fromStdString(comboStr),QString::fromStdString(dataStr)); } + } + } + } + } + int idx = ui->cancelCombo->findText(existingSelection, Qt::MatchExactly); + if (idx != -1) ui->cancelCombo->setCurrentIndex(idx); // if value selected before update and it still exists, reselect it +} + +/** + * Refreshes the UI fields with the most current data - called when the + * refreshOmniState() signal is received. + */ +void MetaDExCancelDialog::RefreshUI() +{ + UpdateAddressSelector(); + UpdateCancelCombo(); +} + + +/** + * Takes the data from the fields in the cancellation UI and asks the wallet to construct a + * MetaDEx cancel transaction. Then commits & broadcast the created transaction. + */ +void MetaDExCancelDialog::SendCancelTransaction() +{ + std::string fromAddress = ui->fromCombo->currentText().toStdString(); + if (fromAddress.empty()) { + // no sender address selected + QMessageBox::critical( this, "Unable to send transaction", + "Please select the address you would like to send the cancellation transaction from." ); + return; + } + + uint8_t action = 0; + /* + * 1 = NEW + * 2 = CANCEL_AT_PRICE + * 3 = CANCEL_ALL_FOR_PAIR + * 4 = CANCEL_EVERYTHING + */ + if (ui->radioCancelPrice->isChecked()) action = 2; + if (ui->radioCancelPair->isChecked()) action = 3; + if (ui->radioCancelEverything->isChecked()) action = 4; + if (action == 0) { + // no cancellation method selected + QMessageBox::critical( this, "Unable to send transaction", + "Please ensure you have selected a cancellation method and valid cancellation criteria." ); + return; + } + + std::string dataStr = ui->cancelCombo->itemData(ui->cancelCombo->currentIndex()).toString().toStdString(); + size_t slashPos = dataStr.find("/"); + size_t colonPos = dataStr.find(":"); + if ((slashPos==std::string::npos) && (action !=4)) { + // cancelCombo does not contain valid data - error out and abort + QMessageBox::critical( this, "Unable to send transaction", + "Please ensure you have selected valid cancellation criteria." ); + return; + } + + uint32_t intBlockDate = GetLatestBlockTime(); + QDateTime currentDate = QDateTime::currentDateTime(); + int secs = QDateTime::fromTime_t(intBlockDate).secsTo(currentDate); + if(secs > 90*60) + { + // wallet is still synchronizing, potential lockup if we try to create a transaction now + QMessageBox::critical( this, "Unable to send transaction", + "The client is still synchronizing. Sending transactions can currently be performed only when the client has completed synchronizing." ); + return; + } + + std::string propertyIdForSaleStr = dataStr.substr(0,slashPos); + std::string propertyIdDesiredStr = dataStr.substr(slashPos+1,std::string::npos); + std::string priceStr; + if (colonPos!=std::string::npos) { + propertyIdDesiredStr = dataStr.substr(slashPos+1,colonPos-slashPos-1); + priceStr = dataStr.substr(colonPos+1,std::string::npos); + } + + uint32_t propertyIdForSale = 0; + uint32_t propertyIdDesired = 0; + + if (action != 4) { + try { + propertyIdForSale = boost::lexical_cast(propertyIdForSaleStr); + propertyIdDesired = boost::lexical_cast(propertyIdDesiredStr); + } catch(boost::bad_lexical_cast& e) { + printf("Failed to parse property identifiers: %s\n", e.what()); // TODO: print to log instead + } + } + + int64_t amountForSale = 0, amountDesired = 0; + + if (action == 2) { // do not attempt to reverse calc values from price, pull suitable ForSale/Desired amounts from metadex map + bool matched = false; + for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) { + if (my_it->first != propertyIdForSale) { continue; } // move along, this isn't the prop you're looking for + md_PricesMap & prices = my_it->second; + for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) { + md_Set & indexes = it->second; + for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) { + CMPMetaDEx obj = *it; + if (obj.displayUnitPrice() == priceStr) { + amountForSale = obj.getAmountForSale(); + amountDesired = obj.getAmountDesired(); + matched = true; + break; + } + } + if (matched) break; + } + if (matched) break; + } + } + + // TODO: print to log (?) + printf("ForSale \"%s\"=%u Desired \"%s\"=%u Price \"%s\" Action %d AmountForSale %lu AmountDesired %lu\n", propertyIdForSaleStr.c_str(), propertyIdForSale, propertyIdDesiredStr.c_str(), propertyIdDesired, priceStr.c_str(), (int)action, amountForSale, amountDesired); + + // confirmation dialog + string strMsgText = "You are about to send the following MetaDEx trade cancellation transaction, please check the details thoroughly:\n\n"; + strMsgText += "Type: Cancel Trade Request\nFrom: " + fromAddress + "\nAction: "; + switch (action) { + case 2: strMsgText += "2 (Cancel by price)\n"; break; + case 3: strMsgText += "3 (Cancel by pair)\n"; break; + case 4: strMsgText += "4 (Cancel all)\n"; break; + } + string sellToken = getPropertyName(propertyIdForSale).c_str(); + string desiredToken = getPropertyName(propertyIdDesired).c_str(); + string sellId = static_cast( &(ostringstream() << propertyIdForSale) )->str(); + string desiredId = static_cast( &(ostringstream() << propertyIdDesired) )->str(); + if(sellToken.size()>30) sellToken=sellToken.substr(0,30)+"..."; + sellToken += " (#" + sellId + ")"; + if(desiredToken.size()>30) desiredToken=desiredToken.substr(0,30)+"..."; + desiredToken += " (#" + desiredId + ")"; + string messageStr = "Cancel all orders "; + if ((propertyIdForSale == OMNI_PROPERTY_MSC) || (propertyIdForSale == OMNI_PROPERTY_TMSC)) { // "buy" order + messageStr += "buying " + desiredToken; + } else { + messageStr += "selling " + sellToken; + } + if (action == 2) { // append price if needed - display the first 24 digits + std::string displayPrice = StripTrailingZeros(priceStr); + if (displayPrice.size()>24) displayPrice = displayPrice.substr(0,24)+"..."; + messageStr += " priced at " + displayPrice; + if ((propertyIdForSale == OMNI_PROPERTY_MSC) || (propertyIdDesired == OMNI_PROPERTY_MSC)) { messageStr += " MSC/SPT"; } else { messageStr += " TMSC/SPT"; } + } + strMsgText += "Message: " + messageStr; + strMsgText += "\n\nAre you sure you wish to send this transaction?"; + QString msgText = QString::fromStdString(strMsgText); + QMessageBox::StandardButton responseClick; + responseClick = QMessageBox::question(this, "Confirm transaction", msgText, QMessageBox::Yes|QMessageBox::No); + if (responseClick == QMessageBox::No) + { + QMessageBox::critical( this, "MetaDEx cancel aborted", + "The MetaDEx trade cancellation transaction has been aborted.\n\nPlease double-check the transction details thoroughly before retrying your transaction." ); + return; + } + + // unlock the wallet + WalletModel::UnlockContext ctx(walletModel->requestUnlock()); + if(!ctx.isValid()) + { + // Unlock wallet was cancelled/failed + QMessageBox::critical( this, "MetaDEx cancel transaction failed", + "The MetaDEx cancel transaction has been aborted.\n\nThe wallet unlock process must be completed to send a transaction." ); + return; + } + + // create a payload for the transaction + std::vector payload = CreatePayload_MetaDExTrade(propertyIdForSale, amountForSale, propertyIdDesired, amountDesired, action); + + // request the wallet build the transaction (and if needed commit it) + uint256 txid = 0; + std::string rawHex; + int result = ClassAgnosticWalletTXBuilder(fromAddress, "", "", 0, payload, txid, rawHex, autoCommit); + + // check error and return the txid (or raw hex depending on autocommit) + if (result != 0) { + string strError = error_str(result); + QMessageBox::critical( this, "MetaDEx cancel transaction failed", + "The MetaDEx cancel transaction has failed.\n\nThe error code was: " + QString::number(result) + "\nThe error message was:\n" + QString::fromStdString(strError)); + return; + } else { + if (!autoCommit) { + PopulateSimpleDialog(rawHex, "Raw Hex (auto commit is disabled)", "Raw transaction hex"); + } else { + PopulateTXSentDialog(txid.GetHex()); + // no need for a pending object for now, no available balances will be affected until confirmation + } + } +} + + diff --git a/src/qt/metadexcanceldialog.h b/src/qt/metadexcanceldialog.h new file mode 100755 index 0000000000000..fc86a7dade7c4 --- /dev/null +++ b/src/qt/metadexcanceldialog.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef METADEXCANCELDIALOG_H +#define METADEXCANCELDIALOG_H + +#include + +class ClientModel; +class WalletModel; + +QT_BEGIN_NAMESPACE +class QWidget; +class QString; +QT_END_NAMESPACE + +namespace Ui { + class MetaDExCancelDialog; +} + +/** Dialog for sending Master Protocol tokens */ +class MetaDExCancelDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MetaDExCancelDialog(QWidget *parent = 0); + ~MetaDExCancelDialog(); + + void setClientModel(ClientModel *model); + void setWalletModel(WalletModel *model); + +public slots: + void SendCancelTransaction(); + void UpdateAddressSelector(); + void UpdateCancelCombo(); + void RefreshUI(); + void fromAddressComboBoxChanged(int); + +private: + Ui::MetaDExCancelDialog *ui; + ClientModel *clientModel; + WalletModel *walletModel; + +private slots: + // None! + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // METADEXCANCELDIALOG_H diff --git a/src/qt/metadexdialog.cpp b/src/qt/metadexdialog.cpp new file mode 100755 index 0000000000000..1e1af2ef8eaf9 --- /dev/null +++ b/src/qt/metadexdialog.cpp @@ -0,0 +1,634 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "metadexdialog.h" +#include "ui_metadexdialog.h" + +#include "omnicore_qtutils.h" + +#include "walletmodel.h" + +#include "mastercore.h" +#include "mastercore_errors.h" +#include "mastercore_mdex.h" +#include "mastercore_parse_string.h" +#include "mastercore_sp.h" +#include "omnicore_createpayload.h" +#include "omnicore_pending.h" + +#include "amount.h" +#include "sync.h" +#include "uint256.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; + +using namespace mastercore; + +MetaDExDialog::MetaDExDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MetaDExDialog), + model(0) +{ + ui->setupUi(this); + //open + global_metadex_market = 3; + + //prep lists + ui->buyList->setColumnCount(3); + ui->sellList->setColumnCount(3); + ui->buyList->verticalHeader()->setVisible(false); + #if QT_VERSION < 0x050000 + ui->buyList->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + #else + ui->buyList->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + #endif + ui->buyList->setShowGrid(false); + ui->buyList->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->buyList->setSelectionMode(QAbstractItemView::NoSelection); + ui->buyList->setFocusPolicy(Qt::NoFocus); + ui->buyList->setAlternatingRowColors(true); + ui->sellList->verticalHeader()->setVisible(false); + #if QT_VERSION < 0x050000 + ui->sellList->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + #else + ui->sellList->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + #endif + ui->sellList->setShowGrid(false); + ui->sellList->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->sellList->setSelectionMode(QAbstractItemView::NoSelection); + ui->sellList->setFocusPolicy(Qt::NoFocus); + ui->sellList->setAlternatingRowColors(true); + + ui->pendingLabel->setVisible(false); + + connect(ui->switchButton, SIGNAL(clicked()), this, SLOT(switchButtonClicked())); + connect(ui->buyButton, SIGNAL(clicked()), this, SLOT(buyTrade())); + connect(ui->sellButton, SIGNAL(clicked()), this, SLOT(sellTrade())); + connect(ui->sellAddressCombo, SIGNAL(activated(int)), this, SLOT(sellAddressComboBoxChanged(int))); + connect(ui->buyAddressCombo, SIGNAL(activated(int)), this, SLOT(buyAddressComboBoxChanged(int))); + connect(ui->sellAmountLE, SIGNAL(textEdited(const QString &)), this, SLOT(recalcSellTotal())); + connect(ui->sellPriceLE, SIGNAL(textEdited(const QString &)), this, SLOT(recalcSellTotal())); + connect(ui->buyAmountLE, SIGNAL(textEdited(const QString &)), this, SLOT(recalcBuyTotal())); + connect(ui->buyPriceLE, SIGNAL(textEdited(const QString &)), this, SLOT(recalcBuyTotal())); + connect(ui->sellList, SIGNAL(cellClicked(int,int)), this, SLOT(sellClicked(int,int))); + connect(ui->buyList, SIGNAL(cellClicked(int,int)), this, SLOT(buyClicked(int,int))); + + FullRefresh(); + +} + +MetaDExDialog::~MetaDExDialog() +{ + delete ui; +} + +void MetaDExDialog::setModel(WalletModel *model) +{ + this->model = model; + if (NULL != model) { + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(OrderRefresh())); + } +} + +void MetaDExDialog::OrderRefresh() +{ + UpdateOffers(); + // check for pending transactions, could be more filtered to just trades here + bool pending = false; + for(PendingMap::iterator my_it = my_pending.begin(); my_it != my_pending.end(); ++my_it) + { + // if we get here there are pending transactions in the wallet, flag warning to MetaDEx + pending = true; + } + if(pending) { ui->pendingLabel->setVisible(true); } else { ui->pendingLabel->setVisible(false); } +} + +// Executed when the switch market button is clicked +void MetaDExDialog::SwitchMarket() +{ + // perform some checks on the input data before attempting to switch market + int64_t searchPropertyId = 0; + string searchText = ui->switchLineEdit->text().toStdString(); + // check for empty field + if (searchText.empty()) { + QMessageBox::critical( this, "Unable to switch market", + "You must enter a property ID to switch the market." ); + return; + } + // check that we can cast the field to numerical OK + try { + searchPropertyId = boost::lexical_cast(searchText); + } catch(const boost::bad_lexical_cast &e) { // error casting, searchText likely not numerical + QMessageBox::critical( this, "Unable to switch market", + "The property ID entered was not a valid number." ); + return; + } + // check that the value is in range + if ((searchPropertyId < 0) || (searchPropertyId > 4294967290)) { + QMessageBox::critical( this, "Unable to switch market", + "The property ID entered is outside the allowed range." ); + return; + } + // check that not trying to trade primary for primary (eg market 1 or 2) + if ((searchPropertyId == 1) || (searchPropertyId == 2)) { + QMessageBox::critical( this, "Unable to switch market", + "You cannot trade MSC/TMSC against itself." ); + return; + } + // check that the property exists + bool spExists = _my_sps->hasSP(searchPropertyId); + if (!spExists) { + QMessageBox::critical( this, "Unable to switch market", + "The property ID entered was not found." ); + return; + } + + // with checks complete change the market to the entered property ID and perform a full refresh + global_metadex_market = searchPropertyId; + ui->buyAmountLE->clear(); + ui->buyPriceLE->clear(); + ui->sellAmountLE->clear(); + ui->sellPriceLE->clear(); + FullRefresh(); +} + +/** + * When a row on the buy side is clicked, populate the sell fields to allow easy selling into the offer accordingly + */ +void MetaDExDialog::buyClicked(int row, int col) +{ + // Populate the sell price field with the price clicked + QTableWidgetItem* priceCell = ui->buyList->item(row,2); + ui->sellPriceLE->setText(priceCell->text()); + + // If the cheapest price is not chosen, make the assumption that user wants to sell down to that price point + if (row != 0) { + int64_t totalAmount = 0; + bool divisible = isPropertyDivisible(global_metadex_market); + for (int i = 0; i <= row; i++) { + QTableWidgetItem* amountCell = ui->buyList->item(i,1); + int64_t amount = StrToInt64(amountCell->text().toStdString(), divisible); + totalAmount += amount; + } + if (divisible) { + ui->sellAmountLE->setText(QString::fromStdString(FormatDivisibleShortMP(totalAmount))); + } else { + ui->sellAmountLE->setText(QString::fromStdString(FormatIndivisibleMP(totalAmount))); + } + } else { + QTableWidgetItem* amountCell = ui->buyList->item(row,1); + ui->sellAmountLE->setText(amountCell->text()); + } + + // Update the total + recalcTotal(false); +} + +/** + * When a row on the sell side is clicked, populate the buy fields to allow easy buying into the offer accordingly + */ +void MetaDExDialog::sellClicked(int row, int col) +{ + // Populate the buy price field with the price clicked + QTableWidgetItem* priceCell = ui->sellList->item(row,0); + ui->buyPriceLE->setText(priceCell->text()); + + // If the cheapest price is not chosen, make the assumption that user wants to buy all available up to that price point + if (row != 0) { + int64_t totalAmount = 0; + bool divisible = isPropertyDivisible(global_metadex_market); + for (int i = 0; i <= row; i++) { + QTableWidgetItem* amountCell = ui->sellList->item(i,1); + int64_t amount = StrToInt64(amountCell->text().toStdString(), divisible); + totalAmount += amount; + } + if (divisible) { + ui->buyAmountLE->setText(QString::fromStdString(FormatDivisibleShortMP(totalAmount))); + } else { + ui->buyAmountLE->setText(QString::fromStdString(FormatIndivisibleMP(totalAmount))); + } + } else { + QTableWidgetItem* amountCell = ui->sellList->item(row,1); + ui->buyAmountLE->setText(amountCell->text()); + } + + // Update the total + recalcTotal(true); +} + +// This function adds a row to the buy or sell offer list +void MetaDExDialog::AddRow(bool useBuyList, bool includesMe, const string& price, const string& available, const string& total) +{ + int workingRow; + if (useBuyList) { + workingRow = ui->buyList->rowCount(); + ui->buyList->insertRow(workingRow); + } else { + workingRow = ui->sellList->rowCount(); + ui->sellList->insertRow(workingRow); + } + QTableWidgetItem *priceCell = new QTableWidgetItem(QString::fromStdString(price)); + QTableWidgetItem *availableCell = new QTableWidgetItem(QString::fromStdString(available)); + QTableWidgetItem *totalCell = new QTableWidgetItem(QString::fromStdString(total)); + if(includesMe) { + QFont font; + font.setBold(true); + priceCell->setFont(font); + availableCell->setFont(font); + totalCell->setFont(font); + } + if (useBuyList) { + priceCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + availableCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + totalCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + ui->buyList->setItem(workingRow, 0, totalCell); + ui->buyList->setItem(workingRow, 1, availableCell); + ui->buyList->setItem(workingRow, 2, priceCell); + } else { + priceCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + availableCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + totalCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + ui->sellList->setItem(workingRow, 0, priceCell); + ui->sellList->setItem(workingRow, 1, availableCell); + ui->sellList->setItem(workingRow, 2, totalCell); + } +} + +// This function loops through the MetaDEx and updates the list of buy/sell offers +void MetaDExDialog::UpdateOffers() +{ + for (int useBuyList = 0; useBuyList < 2; ++useBuyList) { + if (useBuyList) { ui->buyList->setRowCount(0); } else { ui->sellList->setRowCount(0); } + bool testeco = isTestEcosystemProperty(global_metadex_market); + for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) { + if ((!useBuyList) && (my_it->first != global_metadex_market)) { continue; } // not the property we're looking for, don't waste any more work + if ((useBuyList) && (((!testeco) && (my_it->first != OMNI_PROPERTY_MSC)) || ((testeco) && (my_it->first != OMNI_PROPERTY_TMSC)))) continue; + md_PricesMap & prices = my_it->second; + for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) { // loop through the sell prices for the property + std::string unitPriceStr; + int64_t available = 0, total = 0; + bool includesMe = false; + md_Set & indexes = (it->second); + for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) { // multiple sell offers can exist at the same price, sum them for the UI + const CMPMetaDEx& obj = *it; + if (obj.displayUnitPrice() == "0.00000000") continue; // hide trades that have a lower than 0.00000001 MSC unit price + if(IsMyAddress(obj.getAddr())) includesMe = true; + unitPriceStr = obj.displayUnitPrice(); + if (useBuyList) { + if (obj.getDesProperty()==global_metadex_market) { + available += obj.getAmountDesired(); + total += obj.getAmountForSale(); + } + } else { + if ( ((testeco) && (obj.getDesProperty() == 2)) || ((!testeco) && (obj.getDesProperty() == 1)) ) { + available += obj.getAmountForSale(); + total += obj.getAmountDesired(); + } + } + } + if ((available > 0) && (total > 0)) { // if there are any available at this price, add to the sell list + string strAvail; + if (isPropertyDivisible(global_metadex_market)) { strAvail = FormatDivisibleShortMP(available); } else { strAvail = FormatIndivisibleMP(available); } + AddRow(useBuyList, includesMe, StripTrailingZeros(unitPriceStr), strAvail, FormatDivisibleShortMP(total)); + } + } + } + } + // update the balances for the buy and sell addreses + UpdateBuyAddressBalance(); + UpdateSellAddressBalance(); + +} + +// This function updates the balance for the currently selected sell address +void MetaDExDialog::UpdateSellAddressBalance() +{ + QString currentSetSellAddress = ui->sellAddressCombo->currentText(); + unsigned int propertyId = global_metadex_market; + int64_t balanceAvailable = getUserAvailableMPbalance(currentSetSellAddress.toStdString(), propertyId); + string sellBalStr; + if (isPropertyDivisible(propertyId)) { sellBalStr = FormatDivisibleMP(balanceAvailable); } else { sellBalStr = FormatIndivisibleMP(balanceAvailable); } + ui->yourSellBalanceLabel->setText(QString::fromStdString("Your balance: " + sellBalStr + " SPT")); +} + +// This function updates the balance for the currently selected buy address +void MetaDExDialog::UpdateBuyAddressBalance() +{ + QString currentSetBuyAddress = ui->buyAddressCombo->currentText(); + unsigned int propertyId = OMNI_PROPERTY_MSC; + if (global_metadex_market >= TEST_ECO_PROPERTY_1) propertyId = OMNI_PROPERTY_TMSC; + int64_t balanceAvailable = getUserAvailableMPbalance(currentSetBuyAddress.toStdString(), propertyId); + ui->yourBuyBalanceLabel->setText(QString::fromStdString("Your balance: " + FormatDivisibleMP(balanceAvailable) + getTokenLabel(propertyId))); +} + +// This function performs a full refresh of all elements - for example when switching markets +void MetaDExDialog::FullRefresh() +{ + // populate market information + unsigned int propertyId = global_metadex_market; + string propNameStr = getPropertyName(propertyId); + bool testeco = false; + if (propertyId >= TEST_ECO_PROPERTY_1) testeco = true; + if(testeco) { + ui->marketLabel->setText(QString::fromStdString("Trade " + propNameStr + " (#" + FormatIndivisibleMP(propertyId) + ") for Test Mastercoin")); + } else { + ui->marketLabel->setText(QString::fromStdString("Trade " + propNameStr + " (#" + FormatIndivisibleMP(propertyId) + ") for Mastercoin")); + } + + // TODO: take a look at locks, what do we need here? + LOCK(cs_tally); + + // get currently selected addresses + QString currentSetBuyAddress = ui->buyAddressCombo->currentText(); + QString currentSetSellAddress = ui->sellAddressCombo->currentText(); + + // clear address selectors + ui->buyAddressCombo->clear(); + ui->sellAddressCombo->clear(); + + // update form labels to reflect market + string primaryToken; + if (testeco) { primaryToken = "TMSC"; } else { primaryToken = "MSC"; } + ui->exchangeLabel->setText("Exchange - SP#" + QString::fromStdString(FormatIndivisibleMP(propertyId) + "/" + primaryToken)); + ui->buyTotalLabel->setText("0.00000000 " + QString::fromStdString(primaryToken)); + ui->sellTotalLabel->setText("0.00000000 " + QString::fromStdString(primaryToken)); + ui->sellTM->setText(QString::fromStdString(primaryToken)); + ui->buyTM->setText(QString::fromStdString(primaryToken)); + ui->buyList->setHorizontalHeaderItem(0, new QTableWidgetItem("Total " + QString::fromStdString(primaryToken))); + ui->buyList->setHorizontalHeaderItem(1, new QTableWidgetItem("Total SP#" + QString::fromStdString(FormatIndivisibleMP(global_metadex_market)))); + ui->buyList->setHorizontalHeaderItem(2, new QTableWidgetItem("Unit Price (" + QString::fromStdString(primaryToken) + ")")); + ui->sellList->setHorizontalHeaderItem(0, new QTableWidgetItem("Unit Price (" + QString::fromStdString(primaryToken) + ")")); + ui->sellList->setHorizontalHeaderItem(1, new QTableWidgetItem("Total SP#" + QString::fromStdString(FormatIndivisibleMP(global_metadex_market)))); + ui->sellList->setHorizontalHeaderItem(2, new QTableWidgetItem("Total " + QString::fromStdString(primaryToken))); + ui->buyMarketLabel->setText("BUY SP#" + QString::fromStdString(FormatIndivisibleMP(propertyId))); + ui->sellMarketLabel->setText("SELL SP#" + QString::fromStdString(FormatIndivisibleMP(propertyId))); + ui->sellButton->setText("Sell SP#" + QString::fromStdString(FormatIndivisibleMP(propertyId))); + ui->buyButton->setText("Buy SP#" + QString::fromStdString(FormatIndivisibleMP(propertyId))); + + // populate buy and sell addresses + for (std::map::iterator my_it = mp_tally_map.begin(); my_it != mp_tally_map.end(); ++my_it) { + string address = (my_it->first).c_str(); + unsigned int id; + (my_it->second).init(); + while (0 != (id = (my_it->second).next())) { + if(id==propertyId) { + if (IsMyAddress(address)) ui->sellAddressCombo->addItem((my_it->first).c_str()); // only include wallet addresses + } + if (((id==OMNI_PROPERTY_MSC) && (!testeco)) || ((id==OMNI_PROPERTY_TMSC) && (testeco))) { + if (IsMyAddress(address)) ui->buyAddressCombo->addItem((my_it->first).c_str()); + } + } + } + + // attempt to set buy and sell addresses back to values before refresh - may not be possible if it's a new market + int sellIdx = ui->sellAddressCombo->findText(currentSetSellAddress); + if (sellIdx != -1) { ui->sellAddressCombo->setCurrentIndex(sellIdx); } // -1 means the new prop doesn't have the previously selected address + int buyIdx = ui->buyAddressCombo->findText(currentSetBuyAddress); + if (buyIdx != -1) { ui->buyAddressCombo->setCurrentIndex(buyIdx); } + + // update the balances for the buy and sell addreses + UpdateBuyAddressBalance(); + UpdateSellAddressBalance(); + + // update the buy and sell offers + UpdateOffers(); +} + +void MetaDExDialog::buyAddressComboBoxChanged(int idx) +{ + UpdateBuyAddressBalance(); +} + +void MetaDExDialog::sellAddressComboBoxChanged(int idx) +{ + UpdateSellAddressBalance(); +} + +void MetaDExDialog::switchButtonClicked() +{ + SwitchMarket(); +} + +void MetaDExDialog::sellTrade() +{ + sendTrade(true); +} + +void MetaDExDialog::buyTrade() +{ + sendTrade(false); +} + +void MetaDExDialog::recalcBuyTotal() +{ + recalcTotal(true); +} + +void MetaDExDialog::recalcSellTotal() +{ + recalcTotal(false); +} + +// This function recalulates a total price display from user fields +void MetaDExDialog::recalcTotal(bool useBuyFields) +{ + unsigned int propertyId = global_metadex_market; + bool divisible = isPropertyDivisible(propertyId); + bool testeco = isTestEcosystemProperty(propertyId); + int64_t price = 0, amount = 0; + int64_t totalPrice = 0; + + if (useBuyFields) { + amount = StrToInt64(ui->buyAmountLE->text().toStdString(),divisible); + price = StrToInt64(ui->buyPriceLE->text().toStdString(),true); + } else { + amount = StrToInt64(ui->sellAmountLE->text().toStdString(),divisible); + price = StrToInt64(ui->sellPriceLE->text().toStdString(),true); + } + totalPrice = amount * price; + + // error and overflow detection + if (0 >= amount || 0 >= price || totalPrice < amount || totalPrice < price || (totalPrice / amount != price)) { + if (useBuyFields) { ui->buyTotalLabel->setText("N/A"); return; } else { ui->sellTotalLabel->setText("N/A"); return; } + } + + if (divisible) totalPrice = totalPrice/COIN; + QString totalLabel = QString::fromStdString(FormatDivisibleMP(totalPrice)); + if (testeco) { + if (useBuyFields) { ui->buyTotalLabel->setText(totalLabel + " TMSC"); } else { ui->sellTotalLabel->setText(totalLabel + " TMSC"); } + } else { + if (useBuyFields) { ui->buyTotalLabel->setText(totalLabel + " MSC"); } else { ui->sellTotalLabel->setText(totalLabel + " MSC"); } + } +} + +void MetaDExDialog::sendTrade(bool sell) +{ + unsigned int propertyId = global_metadex_market; + bool divisible = isPropertyDivisible(propertyId); + bool testeco = false; + if (propertyId >= TEST_ECO_PROPERTY_1) testeco = true; + + // obtain the selected sender address + string strFromAddress; + if (!sell) { strFromAddress = ui->buyAddressCombo->currentText().toStdString(); } else { strFromAddress = ui->sellAddressCombo->currentText().toStdString(); } + + // warn if we have to truncate the amount due to a decimal amount for an indivisible property, but allow send to continue + string strAmount; + if (!sell) { strAmount = ui->buyAmountLE->text().toStdString(); } else { strAmount = ui->sellAmountLE->text().toStdString(); } + if (!divisible) { + size_t pos = strAmount.find("."); + if (pos!=std::string::npos) { + string tmpStrAmount = strAmount.substr(0,pos); + string strMsgText = "The amount entered contains a decimal however the property being transacted is indivisible.\n\nThe amount entered will be truncated as follows:\n"; + strMsgText += "Original amount entered: " + strAmount + "\nAmount that will be used: " + tmpStrAmount + "\n\n"; + strMsgText += "Do you still wish to proceed with the transaction?"; + QString msgText = QString::fromStdString(strMsgText); + QMessageBox::StandardButton responseClick; + responseClick = QMessageBox::question(this, "Amount truncation warning", msgText, QMessageBox::Yes|QMessageBox::No); + if (responseClick == QMessageBox::No) { + QMessageBox::critical( this, "MetaDEx transaction cancelled", + "The MetaDEx transaction has been cancelled.\n\nPlease double-check the transction details thoroughly before retrying your transaction." ); + return; + } + strAmount = tmpStrAmount; + if (!sell) { ui->buyAmountLE->setText(QString::fromStdString(strAmount)); } else { ui->sellAmountLE->setText(QString::fromStdString(strAmount)); } + } + } + + // use strToInt64 function to get the amounts, using divisibility of the property + int64_t amountDes; + int64_t amountSell; + int64_t price; + unsigned int propertyIdDes; + unsigned int propertyIdSell; + if(sell) { + amountSell = StrToInt64(ui->sellAmountLE->text().toStdString(),divisible); + price = StrToInt64(ui->sellPriceLE->text().toStdString(),true); + if(divisible) { amountDes = (amountSell * price)/COIN; } else { amountDes = amountSell * price; } + if(testeco) { propertyIdDes = 2; } else { propertyIdDes = 1; } + propertyIdSell = global_metadex_market; + } else { + amountDes = StrToInt64(ui->buyAmountLE->text().toStdString(),divisible); + price = StrToInt64(ui->buyPriceLE->text().toStdString(),true); + if(divisible) { amountSell = (amountDes * price)/COIN; } else { amountSell = amountDes * price; } + if(testeco) { propertyIdSell = 2; } else { propertyIdSell = 1; } + propertyIdDes = global_metadex_market; + } + if ((0>=amountDes) || (0>=amountSell) || (0>=propertyIdDes) || (0>=propertyIdSell)) { + QMessageBox::critical( this, "Unable to send MetaDEx transaction", + "The amount entered is not valid.\n\nPlease double-check the transction details thoroughly before retrying your transaction." ); + return; + } + + // check if sending address has enough funds + int64_t balanceAvailable = 0; + balanceAvailable = getUserAvailableMPbalance(strFromAddress, propertyIdSell); + if (amountSell>balanceAvailable) { + QMessageBox::critical( this, "Unable to send MetaDEx transaction", + "The selected sending address does not have a sufficient balance to cover the amount entered.\n\nPlease double-check the transction details thoroughly before retrying your transaction." ); + return; + } + + // check if wallet is still syncing, as this will currently cause a lockup if we try to send - compare our chain to peers to see if we're up to date + // Bitcoin Core devs have removed GetNumBlocksOfPeers, switching to a time based best guess scenario + uint32_t intBlockDate = GetLatestBlockTime(); // uint32, not using time_t for portability + QDateTime currentDate = QDateTime::currentDateTime(); + int secs = QDateTime::fromTime_t(intBlockDate).secsTo(currentDate); + if(secs > 90*60) { + QMessageBox::critical( this, "Unable to send MetaDEx transaction", + "The client is still synchronizing. Sending transactions can currently be performed only when the client has completed synchronizing." ); + return; + } + + // validation checks all look ok, let's throw up a confirmation dialog + string strMsgText = "You are about to send the following MetaDEx transaction, please check the details thoroughly:\n\n"; + string spNum = static_cast( &(ostringstream() << propertyId) )->str(); + string propDetails = "#" + spNum; + string buyStr; + string sellStr; + if (!sell) strMsgText += "Your buy will be inverted into a sell offer.\n\n"; + strMsgText += "Type: Trade Request\nFrom: " + strFromAddress + "\n\n"; + if (!sell) { // clicked buy + if (divisible) { buyStr = FormatDivisibleMP(amountDes); } else { buyStr = FormatIndivisibleMP(amountDes); } + buyStr += " SPT " + propDetails + ""; + sellStr = FormatDivisibleMP(amountSell); + if (testeco) { sellStr += " TMSC"; } else { sellStr += " MSC"; } + strMsgText += "Buying: " + buyStr + "\nPrice: " + FormatDivisibleMP(price) + " SP" + propDetails + "/"; + if (testeco) { strMsgText += "TMSC"; } else { strMsgText += "MSC"; } + strMsgText += "\nTotal: " + sellStr; + } else { // clicked sell + buyStr = FormatDivisibleMP(amountDes); + if (divisible) { sellStr = FormatDivisibleMP(amountSell); } else { sellStr = FormatIndivisibleMP(amountSell); } + if (testeco) { buyStr += " TMSC"; } else { buyStr += " MSC"; } + sellStr += " SPT " + propDetails + ""; + strMsgText += "Selling: " + sellStr + "\nPrice: " + FormatDivisibleMP(price) + " SP" + propDetails + "/"; + if (testeco) { strMsgText += "TMSC"; } else { strMsgText += "MSC"; } + strMsgText += "\nTotal: " + buyStr; + } + strMsgText += "\n\nAre you sure you wish to send this transaction?"; + QString msgText = QString::fromStdString(strMsgText); + QMessageBox::StandardButton responseClick; + responseClick = QMessageBox::question(this, "Confirm MetaDEx transaction", msgText, QMessageBox::Yes|QMessageBox::No); + if (responseClick == QMessageBox::No) + { + QMessageBox::critical( this, "MetaDEx transaction cancelled", + "The transaction has been cancelled.\n\nPlease double-check the transction details thoroughly before retrying your transaction." ); + return; + } + + // unlock the wallet + WalletModel::UnlockContext ctx(model->requestUnlock()); + if(!ctx.isValid()) + { + // Unlock wallet was cancelled/failed + QMessageBox::critical( this, "MetaDEx transaction failed", + "The transaction has been cancelled.\n\nThe wallet unlock process must be completed to send a transaction." ); + return; + } + + // create a payload for the transaction + std::vector payload = CreatePayload_MetaDExTrade(propertyIdSell, amountSell, propertyIdDes, amountDes, 1); + + // request the wallet build the transaction (and if needed commit it) + uint256 txid = 0; + std::string rawHex; + int result = ClassAgnosticWalletTXBuilder(strFromAddress, "", "", 0, payload, txid, rawHex, autoCommit); + + // check error and return the txid (or raw hex depending on autocommit) + if (result != 0) { + string strError = error_str(result); + QMessageBox::critical( this, "MetaDEx transaction failed", + "The MetaDEx transaction has failed.\n\nThe error code was: " + QString::number(result) + "\nThe error message was:\n" + QString::fromStdString(strError)); + return; + } else { + if (!autoCommit) { + PopulateSimpleDialog(rawHex, "Raw Hex (auto commit is disabled)", "Raw transaction hex"); + } else { + PendingAdd(txid, strFromAddress, "", MSC_TYPE_METADEX, propertyIdSell, amountSell, propertyIdDes, amountDes, 1); + PopulateTXSentDialog(txid.GetHex()); + } + } +} + + + diff --git a/src/qt/metadexdialog.h b/src/qt/metadexdialog.h new file mode 100755 index 0000000000000..19bae4e148277 --- /dev/null +++ b/src/qt/metadexdialog.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef METADEXDIALOG_H +#define METADEXDIALOG_H + +#include + +#include + +class WalletModel; + +QT_BEGIN_NAMESPACE +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class MetaDExDialog; +} + +/** Dialog for looking up Master Protocol tokens */ +class MetaDExDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MetaDExDialog(QWidget *parent = 0); + ~MetaDExDialog(); + + void FullRefresh(); + void SwitchMarket(); + void AddRow(bool useBuyList, bool includesMe, const std::string& price, const std::string& available, const std::string& total); + void UpdateSellAddressBalance(); + void UpdateBuyAddressBalance(); + void UpdateOffers(); + void UpdateSellOffers(); + void UpdateBuyOffers(); + void setModel(WalletModel *model); + void recalcTotal(bool useBuyFields); + +public slots: + void switchButtonClicked(); + void sellAddressComboBoxChanged(int idx); + void buyAddressComboBoxChanged(int idx); + void sellClicked(int row, int col); + void buyClicked(int row, int col); + void sendTrade(bool sell); + void OrderRefresh(); + +private: + Ui::MetaDExDialog *ui; + WalletModel *model; + +private slots: + void buyTrade(); + void sellTrade(); + void recalcBuyTotal(); + void recalcSellTotal(); + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // METADEXDIALOG_H diff --git a/src/qt/omnicore_init.cpp b/src/qt/omnicore_init.cpp new file mode 100644 index 0000000000000..06450df472684 --- /dev/null +++ b/src/qt/omnicore_init.cpp @@ -0,0 +1,119 @@ +#include "omnicore_init.h" + +#include "chainparams.h" +#include "util.h" + +#include + +#include +#include + +namespace OmniCore { + +/** + * Creates a message box with general warnings and potential risks + * + * @return True if the user acknowledged the warnings + */ +static bool getDisclaimerDialogResult() +{ + QString qstrTitle("WARNING - Experimental Software"); + + QString qstrText( + "This software is EXPERIMENTAL software. USE ON MAINNET AT YOUR OWN RISK."); + + QString qstrInformativeText( + "By default this software will use your existing Bitcoin wallet, including spending " + "bitcoins contained therein (for example for transaction fees or trading).\n\n" + "The protocol and transaction processing rules for the Omni Layer are still under " + "active development and are subject to change in future.\n\n" + "Omni Core should be considered an alpha-level product, and you use it at your " + "own risk. Neither the Omni Foundation nor the Omni Core developers assumes " + "any responsibility for funds misplaced, mishandled, lost, or misallocated.\n\n" + "Further, please note that this installation of Omni Core should be viewed as " + "EXPERIMENTAL. Your wallet data, bitcoins and Omni Layer tokens may be lost, " + "deleted, or corrupted, with or without warning due to bugs or glitches. " + "Please take caution.\n\n" + "This software is provided open-source at no cost. You are responsible for " + "knowing the law in your country and determining if your use of this software " + "contravenes any local laws.\n\n" + "PLEASE DO NOT use wallet(s) with significant amounts of bitcoins or Omni Layer " + "tokens while testing!"); + + QMessageBox msgBoxDisclaimer; + msgBoxDisclaimer.setIcon(QMessageBox::Warning); + msgBoxDisclaimer.setWindowTitle(qstrTitle); + msgBoxDisclaimer.setText(qstrText); + msgBoxDisclaimer.setInformativeText(qstrInformativeText); + msgBoxDisclaimer.setStandardButtons(QMessageBox::Abort|QMessageBox::Ok); + msgBoxDisclaimer.setDefaultButton(QMessageBox::Ok); + msgBoxDisclaimer.setButtonText(QMessageBox::Abort, "&Exit"); + msgBoxDisclaimer.setButtonText(QMessageBox::Ok, "Acknowledge + &Continue"); + + return msgBoxDisclaimer.exec() == QMessageBox::Ok; +} + +/** + * Determines if a disclaimer about the use of this software should be shown + * + * @see AskUserToAcknowledgeRisks() + * @return True if the dialog should be shown + */ +static bool showDisclaimer() +{ + // Don't skip, if disclaimer was requested explicitly + if (GetBoolArg("-disclaimer", false)) + return true; + + // Skip when starting minimized + if (GetBoolArg("-min", false)) + return false; + + // Only mainnet users face the risk of monetary loss + if (Params().NetworkIDString() != "main") + return false; + + QSettings settings; + + // Check whether the disclaimer was acknowledged before + return settings.value("OmniCore/fShowDisclaimer", true).toBool(); +} + +/** + * Shows an user dialog with general warnings and potential risks + * + * The user is asked to acknowledge the risks related to the use of + * this software. The decision of the user is stored and can be used + * to skip the dialog during initialization. + * + * @see getDisclaimerDialogResult() + * @see Initialize() + * @return True if the user acknowledged the warnings + */ +bool AskUserToAcknowledgeRisks() +{ + QSettings settings; + + // Show message box + bool fAccepted = getDisclaimerDialogResult(); + + // Store decision and show next time, if declined + settings.setValue("OmniCore/fShowDisclaimer", !fAccepted); + + return fAccepted; +} + +/** + * Setup and initialization related to Omni Core Qt + * + * @return True if the initialization was successful + */ +bool Initialize() +{ + if (showDisclaimer()) + return AskUserToAcknowledgeRisks(); + + return true; +} + +} // namespace OmniCore diff --git a/src/qt/omnicore_init.h b/src/qt/omnicore_init.h new file mode 100644 index 0000000000000..c442779cc495b --- /dev/null +++ b/src/qt/omnicore_init.h @@ -0,0 +1,13 @@ +#ifndef OMNICORE_QT_INIT_H +#define OMNICORE_QT_INIT_H + +namespace OmniCore +{ + //! Shows an user dialog with general warnings and potential risks + bool AskUserToAcknowledgeRisks(); + + //! Setup and initialization related to Omni Core Qt + bool Initialize(); +} + +#endif // OMNICORE_QT_INIT_H diff --git a/src/qt/omnicore_qtutils.cpp b/src/qt/omnicore_qtutils.cpp new file mode 100644 index 0000000000000..a470092a7d038 --- /dev/null +++ b/src/qt/omnicore_qtutils.cpp @@ -0,0 +1,112 @@ +#include "omnicore_qtutils.h" + +#include "guiutil.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mastercore +{ + +/** + * Displays a 'transaction sent' message box containing the transaction ID and an extra button to copy txid to clipboard + */ +void PopulateTXSentDialog(const std::string& txidStr) +{ + std::string strSentText = "Your Omni Layer transaction has been sent.\n\nThe transaction ID is:\n\n" + txidStr + "\n\n"; + QMessageBox sentDialog; + sentDialog.setIcon(QMessageBox::Information); + sentDialog.setWindowTitle("Transaction broadcast successfully"); + sentDialog.setText(QString::fromStdString(strSentText)); + sentDialog.setStandardButtons(QMessageBox::Yes|QMessageBox::Ok); + sentDialog.setDefaultButton(QMessageBox::Ok); + sentDialog.setButtonText( QMessageBox::Yes, "Copy TXID to clipboard" ); + if(sentDialog.exec() == QMessageBox::Yes) GUIUtil::setClipboard(QString::fromStdString(txidStr)); +} + +/** + * Displays a simple dialog layout that can be used to provide selectable text to the user + * + * Note: used in place of standard dialogs in cases where text selection & copy to clipboard functions are useful + */ +void PopulateSimpleDialog(const std::string& content, const std::string& title, const std::string& tooltip) +{ + QDialog *simpleDlg = new QDialog; + QLayout *dlgLayout = new QVBoxLayout; + dlgLayout->setSpacing(12); + dlgLayout->setMargin(12); + QTextEdit *dlgTextEdit = new QTextEdit; + dlgTextEdit->setText(QString::fromStdString(content)); + dlgTextEdit->setStatusTip(QString::fromStdString(tooltip)); + dlgTextEdit->setReadOnly(true); + dlgTextEdit->setTextInteractionFlags(dlgTextEdit->textInteractionFlags() | Qt::TextSelectableByKeyboard); + dlgLayout->addWidget(dlgTextEdit); + QPushButton *closeButton = new QPushButton(QObject::tr("&Close")); + closeButton->setDefault(true); + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(closeButton, QDialogButtonBox::AcceptRole); + dlgLayout->addWidget(buttonBox); + QObject::connect(buttonBox, SIGNAL(accepted()), simpleDlg, SLOT(accept())); + simpleDlg->setAttribute(Qt::WA_DeleteOnClose); + simpleDlg->setWindowTitle(QString::fromStdString(title)); + simpleDlg->setLayout(dlgLayout); + simpleDlg->resize(700, 360); + if (simpleDlg->exec() == QDialog::Accepted) { } //do nothing but close +} + +/** + * Strips trailing zeros from a string containing a divisible value + */ +std::string StripTrailingZeros(const std::string& inputStr) +{ + size_t dot = inputStr.find("."); + std::string outputStr = inputStr; // make a copy we will manipulate and return + if (dot==std::string::npos) { // could not find a decimal marker, unsafe - return original input string + return inputStr; + } + size_t lastZero = outputStr.find_last_not_of('0') + 1; + if (lastZero > dot) { // trailing zeros are after decimal marker, safe to remove + outputStr.erase ( lastZero, std::string::npos ); + if (outputStr.length() > 0) { std::string::iterator it = outputStr.end() - 1; if (*it == '.') { outputStr.erase(it); } } //get rid of trailing dot if needed + } else { // last non-zero is before the decimal marker, this is a whole number + outputStr.erase ( dot, std::string::npos ); + } + return outputStr; +} + +/** + * Truncates a string at n digits and adds "..." to indicate that display is incomplete + */ +std::string TruncateString(const std::string& inputStr, unsigned int length) +{ + if (inputStr.empty()) return ""; + std::string outputStr = inputStr; + if (length > 0 && inputStr.length() > length) { + outputStr = inputStr.substr(0, length) + "..."; + } + return outputStr; +} + +/** + * Variable length find and replace. Find all iterations of findText within inputStr and replace them + * with replaceText. + */ +std::string ReplaceStr(const std::string& findText, const std::string& replaceText, const std::string& inputStr) +{ + size_t start_pos = 0; + std::string outputStr = inputStr; + while((start_pos = outputStr.find(findText, start_pos)) != std::string::npos) { + outputStr.replace(start_pos, findText.length(), replaceText); + start_pos += replaceText.length(); + } + return outputStr; +} + +} // end namespace diff --git a/src/qt/omnicore_qtutils.h b/src/qt/omnicore_qtutils.h new file mode 100644 index 0000000000000..7121d59375486 --- /dev/null +++ b/src/qt/omnicore_qtutils.h @@ -0,0 +1,35 @@ +#ifndef OMNICORE_QTUTILS +#define OMNICORE_QTUTILS + +#include + +namespace mastercore +{ + /** + * Sets up a simple dialog layout that can be used to provide selectable text to the user + */ + void PopulateSimpleDialog(const std::string& content, const std::string& title, const std::string& tooltip); + + /** + * Truncates a string at n digits and adds "..." to indicate that display is incomplete + */ + std::string TruncateString(const std::string& inputStr, unsigned int length); + + /** + * Strips trailing zeros from a string containing a divisible value + */ + std::string StripTrailingZeros(const std::string& inputStr); + + /** + * Displays a 'transaction sent' message box containing the transaction ID and an extra button to copy txid to clipboard + */ + void PopulateTXSentDialog(const std::string& txidStr); + + /** + * Variable length find and replace. Find all iterations of findText within inputStr and replace them + * with replaceText. + */ + std::string ReplaceStr(const std::string& findText, const std::string& replaceText, const std::string& inputStr); +} + +#endif // OMNICORE_QTUTILS diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 669d5474fd695..57c89ddd37934 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -14,11 +14,36 @@ #include "transactiontablemodel.h" #include "walletmodel.h" +#include "mastercore.h" + +#include "main.h" + +#include +#include + #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; + +using namespace mastercore; #define DECORATION_SIZE 64 -#define NUM_ITEMS 3 +#define NUM_ITEMS 6 // 3 - number of recent transactions to display class TxViewDelegate : public QAbstractItemDelegate { @@ -131,6 +156,12 @@ OverviewPage::OverviewPage(QWidget *parent) : ui->labelWalletStatus->setText("(" + tr("out of sync") + ")"); ui->labelTransactionsStatus->setText("(" + tr("out of sync") + ")"); + // make sure BTC and MSC are always first in the list by adding them first + UpdatePropertyBalance(0,0,0); + UpdatePropertyBalance(1,0,0); + + updateOmni(); + // start with displaying the "out of sync" warnings showOutOfSyncWarning(true); } @@ -146,15 +177,147 @@ OverviewPage::~OverviewPage() delete ui; } +void OverviewPage::UpdatePropertyBalance(unsigned int propertyId, uint64_t available, uint64_t reserved) +{ + // look for this property, does it already exist in overview and if so are the balances correct? + int existingItem = -1; + for(int i=0; i < ui->overviewLW->count(); i++) { + uint64_t itemPropertyId = ui->overviewLW->item(i)->data(Qt::UserRole + 1).value(); + if (itemPropertyId == propertyId) { + uint64_t itemAvailableBalance = ui->overviewLW->item(i)->data(Qt::UserRole + 2).value(); + uint64_t itemReservedBalance = ui->overviewLW->item(i)->data(Qt::UserRole + 3).value(); + if ((available == itemAvailableBalance) && (reserved == itemReservedBalance)) { + return; // norhing more to do, balance exists and is up to date + } else { + existingItem = i; + break; + } + } + } + + // this property doesn't exist in overview, create an entry for it + QWidget *listItem = new QWidget(); + QVBoxLayout *vlayout = new QVBoxLayout(); + QHBoxLayout *hlayout = new QHBoxLayout(); + bool divisible = false; + string tokenStr; + // property label + string spName = getPropertyName(propertyId).c_str(); + if(spName.size()>22) spName=spName.substr(0,22)+"..."; + spName += " (#" + static_cast( &(ostringstream() << propertyId) )->str() + ")"; + QLabel *propLabel = new QLabel(QString::fromStdString(spName)); + propLabel->setStyleSheet("QLabel { font-weight:bold; }"); + vlayout->addWidget(propLabel); + // customizations based on property + if(propertyId == 0) { divisible = true; } else { divisible = isPropertyDivisible(propertyId); } // override for bitcoin + if(propertyId == 0) {tokenStr = " BTC";} else {if(propertyId == 1) {tokenStr = " MSC";} else {if(propertyId ==2) {tokenStr = " TMSC";} else {tokenStr = " SPT";}}} + // Left Panel + QVBoxLayout *vlayoutleft = new QVBoxLayout(); + QLabel *balReservedLabel = new QLabel; + if(propertyId != 0) { balReservedLabel->setText("Reserved:"); } else { balReservedLabel->setText("Pending:"); propLabel->setText("Bitcoin"); } // override for bitcoin + QLabel *balAvailableLabel = new QLabel("Available:"); + QLabel *balTotalLabel = new QLabel("Total:"); + vlayoutleft->addWidget(balReservedLabel); + vlayoutleft->addWidget(balAvailableLabel); + vlayoutleft->addWidget(balTotalLabel); + // Right panel + QVBoxLayout *vlayoutright = new QVBoxLayout(); + QLabel *balReservedLabelAmount = new QLabel(); + QLabel *balAvailableLabelAmount = new QLabel(); + QLabel *balTotalLabelAmount = new QLabel(); + if(divisible) { + balReservedLabelAmount->setText(QString::fromStdString(FormatDivisibleMP(reserved) + tokenStr)); + balAvailableLabelAmount->setText(QString::fromStdString(FormatDivisibleMP(available) + tokenStr)); + balTotalLabelAmount->setText(QString::fromStdString(FormatDivisibleMP(available+reserved) + tokenStr)); + } else { + balReservedLabelAmount->setText(QString::fromStdString(FormatIndivisibleMP(reserved) + tokenStr)); + balAvailableLabelAmount->setText(QString::fromStdString(FormatIndivisibleMP(available) + tokenStr)); + balTotalLabelAmount->setText(QString::fromStdString(FormatIndivisibleMP(available+reserved) + tokenStr)); + } + balReservedLabelAmount->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + balAvailableLabelAmount->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + balTotalLabelAmount->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + balReservedLabelAmount->setStyleSheet("QLabel { padding-right:2px; }"); + balAvailableLabelAmount->setStyleSheet("QLabel { padding-right:2px; }"); + balTotalLabelAmount->setStyleSheet("QLabel { padding-right:2px; font-weight:bold; }"); + vlayoutright->addWidget(balReservedLabelAmount); + vlayoutright->addWidget(balAvailableLabelAmount); + vlayoutright->addWidget(balTotalLabelAmount); + // put together + vlayoutleft->addSpacerItem(new QSpacerItem(1,1,QSizePolicy::Fixed,QSizePolicy::Expanding)); + vlayoutright->addSpacerItem(new QSpacerItem(1,1,QSizePolicy::Fixed,QSizePolicy::Expanding)); + vlayoutleft->setContentsMargins(0,0,0,0); + vlayoutright->setContentsMargins(0,0,0,0); + vlayoutleft->setMargin(0); + vlayoutright->setMargin(0); + vlayoutleft->setSpacing(3); + vlayoutright->setSpacing(3); + hlayout->addLayout(vlayoutleft); + hlayout->addSpacerItem(new QSpacerItem(1,1,QSizePolicy::Expanding,QSizePolicy::Fixed)); + hlayout->addLayout(vlayoutright); + hlayout->setContentsMargins(0,0,0,0); + vlayout->addLayout(hlayout); + vlayout->addSpacerItem(new QSpacerItem(1,10,QSizePolicy::Fixed,QSizePolicy::Fixed)); + vlayout->setMargin(0); + vlayout->setSpacing(3); + listItem->setLayout(vlayout); + listItem->setContentsMargins(0,0,0,0); + listItem->layout()->setContentsMargins(0,0,0,0); + // set data + if(existingItem == -1) { // new + QListWidgetItem *item = new QListWidgetItem(); + item->setData(Qt::UserRole + 1, QVariant::fromValue(propertyId)); + item->setData(Qt::UserRole + 2, QVariant::fromValue(available)); + item->setData(Qt::UserRole + 3, QVariant::fromValue(reserved)); + item->setSizeHint(QSize(0,listItem->sizeHint().height())); // resize + // add the entry + ui->overviewLW->addItem(item); + ui->overviewLW->setItemWidget(item, listItem); + } else { + ui->overviewLW->item(existingItem)->setData(Qt::UserRole + 2, QVariant::fromValue(available)); + ui->overviewLW->item(existingItem)->setData(Qt::UserRole + 3, QVariant::fromValue(reserved)); + ui->overviewLW->setItemWidget(ui->overviewLW->item(existingItem), listItem); + } +} + +void OverviewPage::updateOmni() +{ + // force a refresh of wallet totals + set_wallet_totals(); + // always show MSC + UpdatePropertyBalance(1,global_balance_money_maineco[1],global_balance_reserved_maineco[1]); + // loop properties and update overview + unsigned int propertyId; + unsigned int maxPropIdMainEco = GetNextPropertyId(true); // these allow us to end the for loop at the highest existing + unsigned int maxPropIdTestEco = GetNextPropertyId(false); // property ID rather than a fixed value like 100000 (optimization) + // main eco + for (propertyId = 2; propertyId < maxPropIdMainEco; propertyId++) { + if ((global_balance_money_maineco[propertyId] > 0) || (global_balance_reserved_maineco[propertyId] > 0)) { + UpdatePropertyBalance(propertyId,global_balance_money_maineco[propertyId],global_balance_reserved_maineco[propertyId]); + } + } + // test eco + for (propertyId = 2147483647; propertyId < maxPropIdTestEco; propertyId++) { + if ((global_balance_money_testeco[propertyId-2147483647] > 0) || (global_balance_reserved_testeco[propertyId-2147483647] > 0)) { + UpdatePropertyBalance(propertyId,global_balance_money_testeco[propertyId-2147483647],global_balance_reserved_testeco[propertyId-2147483647]); + } + } +} + void OverviewPage::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, const CAmount& watchOnlyBalance, const CAmount& watchUnconfBalance, const CAmount& watchImmatureBalance) { - int unit = walletModel->getOptionsModel()->getDisplayUnit(); + // Mastercore alerts come as block transactions and do not trip bitcoin alertsChanged() signal so let's check the + // alert status with the update balance signal that comes in after each block to see if it had any alerts in it + updateAlerts(); + + //int unit = walletModel->getOptionsModel()->getDisplayUnit(); //only needed if we decide not to use new overview property list currentBalance = balance; currentUnconfirmedBalance = unconfirmedBalance; currentImmatureBalance = immatureBalance; currentWatchOnlyBalance = watchOnlyBalance; currentWatchUnconfBalance = watchUnconfBalance; currentWatchImmatureBalance = watchImmatureBalance; +/* ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways)); ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance, false, BitcoinUnits::separatorAlways)); ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance, false, BitcoinUnits::separatorAlways)); @@ -163,21 +326,23 @@ void OverviewPage::setBalance(const CAmount& balance, const CAmount& unconfirmed ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, watchUnconfBalance, false, BitcoinUnits::separatorAlways)); ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, watchImmatureBalance, false, BitcoinUnits::separatorAlways)); ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance + watchUnconfBalance + watchImmatureBalance, false, BitcoinUnits::separatorAlways)); - // only show immature (newly mined) balance if it's non-zero, so as not to complicate things // for the non-mining users bool showImmature = immatureBalance != 0; bool showWatchOnlyImmature = watchImmatureBalance != 0; - // for symmetry reasons also show immature label when the watch-only one is shown ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature); ui->labelImmatureText->setVisible(showImmature || showWatchOnlyImmature); ui->labelWatchImmature->setVisible(showWatchOnlyImmature); // show watch-only immature balance +*/ + // instead simply pass the values to UpdatePropertyBalance - no support for watch-only yet + UpdatePropertyBalance(0,balance,unconfirmedBalance); } // show/hide watch-only labels void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly) { +/* ui->labelSpendable->setVisible(showWatchOnly); // show spendable label (only when watch-only is active) ui->labelWatchonly->setVisible(showWatchOnly); // show watch-only label ui->lineWatchBalance->setVisible(showWatchOnly); // show watch-only balance separator line @@ -187,6 +352,7 @@ void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly) if (!showWatchOnly) ui->labelWatchImmature->hide(); +*/ } void OverviewPage::setClientModel(ClientModel *model) @@ -196,7 +362,10 @@ void OverviewPage::setClientModel(ClientModel *model) { // Show warning if this is a prerelease version connect(model, SIGNAL(alertsChanged(QString)), this, SLOT(updateAlerts(QString))); - updateAlerts(model->getStatusBarWarnings()); + updateAlerts(); + + // Refresh Omni info if there have been Omni layer transactions + connect(model, SIGNAL(refreshOmniState()), this, SLOT(updateOmni())); } } @@ -222,6 +391,9 @@ void OverviewPage::setWalletModel(WalletModel *model) model->getWatchBalance(), model->getWatchUnconfirmedBalance(), model->getWatchImmatureBalance()); connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount))); + // Refresh Omni information in case this was an internal Omni transaction + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(updateOmni())); + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); updateWatchOnlyLabels(model->haveWatchOnly()); @@ -247,10 +419,24 @@ void OverviewPage::updateDisplayUnit() } } -void OverviewPage::updateAlerts(const QString &warnings) +void OverviewPage::updateAlerts() { - this->ui->labelAlerts->setVisible(!warnings.isEmpty()); - this->ui->labelAlerts->setText(warnings); + // init variables + bool showAlert = false; + QString totalMessage; + // override to check alert directly rather than passing in param as we won't always be calling from bitcoin in + // the clientmodel emit for alertsChanged + QString warnings = QString::fromStdString(GetWarnings("statusbar")); // get current bitcoin alert/warning directly + QString alertMessage = QString::fromStdString(getMasterCoreAlertTextOnly()); // just return the text message from alert + // any BitcoinCore or MasterCore alerts to display? + if((!alertMessage.isEmpty()) || (!warnings.isEmpty())) showAlert = true; + this->ui->labelAlerts->setVisible(showAlert); + // check if we have a Bitcoin alert to display + if(!warnings.isEmpty()) totalMessage = warnings + "\n"; + // check if we have a MasterProtocol alert to display + if(!alertMessage.isEmpty()) totalMessage += alertMessage; + // display the alert if needed + if(showAlert) { this->ui->labelAlerts->setText(totalMessage); } } void OverviewPage::showOutOfSyncWarning(bool fShow) diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index e889eae8be0fb..afa1874905634 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -34,10 +34,12 @@ class OverviewPage : public QWidget void setClientModel(ClientModel *clientModel); void setWalletModel(WalletModel *walletModel); void showOutOfSyncWarning(bool fShow); + void UpdatePropertyBalance(unsigned int propertyId, uint64_t available, uint64_t reserved); public slots: void setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, const CAmount& watchOnlyBalance, const CAmount& watchUnconfBalance, const CAmount& watchImmatureBalance); + void updateOmni(); signals: void transactionClicked(const QModelIndex &index); @@ -59,7 +61,7 @@ public slots: private slots: void updateDisplayUnit(); void handleTransactionClicked(const QModelIndex &index); - void updateAlerts(const QString &warnings); + void updateAlerts(); void updateWatchOnlyLabels(bool showWatchOnly); }; diff --git a/src/qt/res/icons/bitcoin.ico b/src/qt/res/icons/bitcoin.ico index 8f5045015d25e..f99f9df9ab0e4 100755 Binary files a/src/qt/res/icons/bitcoin.ico and b/src/qt/res/icons/bitcoin.ico differ diff --git a/src/qt/res/icons/bitcoin.png b/src/qt/res/icons/bitcoin.png index ce5fbb0c2c57d..89df9ef6db0af 100644 Binary files a/src/qt/res/icons/bitcoin.png and b/src/qt/res/icons/bitcoin.png differ diff --git a/src/qt/res/icons/bitcoin_testnet.ico b/src/qt/res/icons/bitcoin_testnet.ico index d67d9d5ce5f16..206889950c86d 100755 Binary files a/src/qt/res/icons/bitcoin_testnet.ico and b/src/qt/res/icons/bitcoin_testnet.ico differ diff --git a/src/qt/res/icons/bitcoin_testnet.png b/src/qt/res/icons/bitcoin_testnet.png index 1202021f538ea..f5b5bd5a63dcd 100644 Binary files a/src/qt/res/icons/bitcoin_testnet.png and b/src/qt/res/icons/bitcoin_testnet.png differ diff --git a/src/qt/res/icons/mp_balances.png b/src/qt/res/icons/mp_balances.png new file mode 100644 index 0000000000000..c329cf360f34f Binary files /dev/null and b/src/qt/res/icons/mp_balances.png differ diff --git a/src/qt/res/icons/mp_exchange.png b/src/qt/res/icons/mp_exchange.png new file mode 100644 index 0000000000000..6432e7606a040 Binary files /dev/null and b/src/qt/res/icons/mp_exchange.png differ diff --git a/src/qt/res/icons/mp_history.png b/src/qt/res/icons/mp_history.png new file mode 100644 index 0000000000000..425e9f4403015 Binary files /dev/null and b/src/qt/res/icons/mp_history.png differ diff --git a/src/qt/res/icons/mp_home.png b/src/qt/res/icons/mp_home.png new file mode 100644 index 0000000000000..c3e21dec316a7 Binary files /dev/null and b/src/qt/res/icons/mp_home.png differ diff --git a/src/qt/res/icons/mp_meta_cancelled.png b/src/qt/res/icons/mp_meta_cancelled.png new file mode 100644 index 0000000000000..837ddec05929e Binary files /dev/null and b/src/qt/res/icons/mp_meta_cancelled.png differ diff --git a/src/qt/res/icons/mp_meta_filled.png b/src/qt/res/icons/mp_meta_filled.png new file mode 100644 index 0000000000000..8ebcd598e00a7 Binary files /dev/null and b/src/qt/res/icons/mp_meta_filled.png differ diff --git a/src/qt/res/icons/mp_meta_open.png b/src/qt/res/icons/mp_meta_open.png new file mode 100644 index 0000000000000..cd38c32f30067 Binary files /dev/null and b/src/qt/res/icons/mp_meta_open.png differ diff --git a/src/qt/res/icons/mp_meta_partial.png b/src/qt/res/icons/mp_meta_partial.png new file mode 100644 index 0000000000000..22c9873a7a74b Binary files /dev/null and b/src/qt/res/icons/mp_meta_partial.png differ diff --git a/src/qt/res/icons/mp_meta_partialclosed.png b/src/qt/res/icons/mp_meta_partialclosed.png new file mode 100644 index 0000000000000..bd5c3a38addcb Binary files /dev/null and b/src/qt/res/icons/mp_meta_partialclosed.png differ diff --git a/src/qt/res/icons/mp_meta_pending.png b/src/qt/res/icons/mp_meta_pending.png new file mode 100644 index 0000000000000..1d49d27d7ecae Binary files /dev/null and b/src/qt/res/icons/mp_meta_pending.png differ diff --git a/src/qt/res/icons/mp_receive.png b/src/qt/res/icons/mp_receive.png new file mode 100644 index 0000000000000..33ca130c6f884 Binary files /dev/null and b/src/qt/res/icons/mp_receive.png differ diff --git a/src/qt/res/icons/mp_send.png b/src/qt/res/icons/mp_send.png new file mode 100644 index 0000000000000..1cf395f5d5c7d Binary files /dev/null and b/src/qt/res/icons/mp_send.png differ diff --git a/src/qt/res/icons/mp_sp.png b/src/qt/res/icons/mp_sp.png new file mode 100644 index 0000000000000..ad3c728dc6a48 Binary files /dev/null and b/src/qt/res/icons/mp_sp.png differ diff --git a/src/qt/res/icons/mp_toolbox.png b/src/qt/res/icons/mp_toolbox.png new file mode 100644 index 0000000000000..a56df6e912d8c Binary files /dev/null and b/src/qt/res/icons/mp_toolbox.png differ diff --git a/src/qt/res/icons/token_btc.png b/src/qt/res/icons/token_btc.png new file mode 100644 index 0000000000000..95f19cd112594 Binary files /dev/null and b/src/qt/res/icons/token_btc.png differ diff --git a/src/qt/res/icons/token_msc.png b/src/qt/res/icons/token_msc.png new file mode 100644 index 0000000000000..6a37089039c96 Binary files /dev/null and b/src/qt/res/icons/token_msc.png differ diff --git a/src/qt/res/icons/transaction_invalid.png b/src/qt/res/icons/transaction_invalid.png new file mode 100644 index 0000000000000..767cd194e3fd8 Binary files /dev/null and b/src/qt/res/icons/transaction_invalid.png differ diff --git a/src/qt/res/images/splash.png b/src/qt/res/images/splash.png index 3f2b2fb2bfdb9..87653c177dae0 100644 Binary files a/src/qt/res/images/splash.png and b/src/qt/res/images/splash.png differ diff --git a/src/qt/res/images/splash_testnet.png b/src/qt/res/images/splash_testnet.png index 786dc9c3bb054..3e89642f1b6bc 100644 Binary files a/src/qt/res/images/splash_testnet.png and b/src/qt/res/images/splash_testnet.png differ diff --git a/src/qt/sendmpdialog.cpp b/src/qt/sendmpdialog.cpp new file mode 100644 index 0000000000000..0a258ed673b16 --- /dev/null +++ b/src/qt/sendmpdialog.cpp @@ -0,0 +1,360 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "sendmpdialog.h" +#include "ui_sendmpdialog.h" + +#include "omnicore_qtutils.h" + +#include "clientmodel.h" +#include "walletmodel.h" + +#include "mastercore.h" +#include "mastercore_errors.h" +#include "mastercore_parse_string.h" +#include "omnicore_createpayload.h" +#include "omnicore_pending.h" + +#include "amount.h" +#include "base58.h" +#include "main.h" +#include "sync.h" +#include "uint256.h" +#include "wallet.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; + +using namespace mastercore; + +SendMPDialog::SendMPDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SendMPDialog), + clientModel(0), + walletModel(0) +{ + ui->setupUi(this); + +#ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac + ui->clearButton->setIcon(QIcon()); + ui->sendButton->setIcon(QIcon()); +#endif +#if QT_VERSION >= 0x040700 // populate placeholder text + ui->sendToLineEdit->setPlaceholderText("Enter a Omni Layer address (e.g. 1oMn1LaYeRADDreSShef77z6A5S4P)"); + ui->amountLineEdit->setPlaceholderText("Enter Amount"); +#endif + + // connect actions + connect(ui->propertyComboBox, SIGNAL(activated(int)), this, SLOT(propertyComboBoxChanged(int))); + connect(ui->sendFromComboBox, SIGNAL(activated(int)), this, SLOT(sendFromComboBoxChanged(int))); + connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clearButtonClicked())); + connect(ui->sendButton, SIGNAL(clicked()), this, SLOT(sendButtonClicked())); + + // initial update + balancesUpdated(); +} + +SendMPDialog::~SendMPDialog() +{ + delete ui; +} + +void SendMPDialog::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if (model != NULL) { + connect(model, SIGNAL(refreshOmniState()), this, SLOT(balancesUpdated())); + } +} + +void SendMPDialog::setWalletModel(WalletModel *model) +{ + this->walletModel = model; + if (model != NULL) { + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(balancesUpdated())); + } +} + +void SendMPDialog::updatePropSelector() +{ + uint32_t nextPropIdMainEco = GetNextPropertyId(true); // these allow us to end the for loop at the highest existing + uint32_t nextPropIdTestEco = GetNextPropertyId(false); // property ID rather than a fixed value like 100000 (optimization) + QString spId = ui->propertyComboBox->itemData(ui->propertyComboBox->currentIndex()).toString(); + ui->propertyComboBox->clear(); + for (unsigned int propertyId = 1; propertyId < nextPropIdMainEco; propertyId++) { + if ((global_balance_money_maineco[propertyId] > 0) || (global_balance_reserved_maineco[propertyId] > 0)) { + std::string spName = getPropertyName(propertyId); + std::string spId = static_cast( &(ostringstream() << propertyId) )->str(); + if(spName.size()>23) spName=spName.substr(0,23) + "..."; + spName += " (#" + spId + ")"; + if (isPropertyDivisible(propertyId)) { spName += " [D]"; } else { spName += " [I]"; } + ui->propertyComboBox->addItem(spName.c_str(),spId.c_str()); + } + } + for (unsigned int propertyId = 2147483647; propertyId < nextPropIdTestEco; propertyId++) { + if ((global_balance_money_testeco[propertyId-2147483647] > 0) || (global_balance_reserved_testeco[propertyId-2147483647] > 0)) { + std::string spName = getPropertyName(propertyId); + std::string spId = static_cast( &(ostringstream() << propertyId) )->str(); + if(spName.size()>23) spName=spName.substr(0,23)+"..."; + spName += " (#" + spId + ")"; + if (isPropertyDivisible(propertyId)) { spName += " [D]"; } else { spName += " [I]"; } + ui->propertyComboBox->addItem(spName.c_str(),spId.c_str()); + } + } + int propIdx = ui->propertyComboBox->findData(spId); + if (propIdx != -1) { ui->propertyComboBox->setCurrentIndex(propIdx); } +} + +void SendMPDialog::clearFields() +{ + ui->sendToLineEdit->setText(""); + ui->amountLineEdit->setText(""); +} + +void SendMPDialog::updateFrom() +{ + // check if this from address has sufficient fees for a send, if not light up warning label + std::string currentSetFromAddress = ui->sendFromComboBox->currentText().toStdString(); + size_t spacer = currentSetFromAddress.find(" "); + if (spacer!=std::string::npos) { + currentSetFromAddress = currentSetFromAddress.substr(0,spacer); + ui->sendFromComboBox->setEditable(true); + QLineEdit *comboDisplay = ui->sendFromComboBox->lineEdit(); + comboDisplay->setText(QString::fromStdString(currentSetFromAddress)); + comboDisplay->setReadOnly(true); + } else { + currentSetFromAddress = ""; + } + + // warning label will be lit if insufficient fees for a typical simple send of 2KB or less + int64_t inputTotal = feeCheck(currentSetFromAddress); + int64_t minWarn = 3 * minRelayTxFee.GetFee(200) + CWallet::minTxFee.GetFee(2000); // based on 3x <200 byte outputs (change/reference/data) & total tx size of <2KB + if (inputTotal >= minWarn) { + ui->feeWarningLabel->setVisible(false); + } else { + ui->feeWarningLabel->setText("WARNING: The sending address is low on BTC for transaction fees. Please topup the BTC balance for the sending address to send Omni Layer transactions."); + ui->feeWarningLabel->setVisible(true); + } + + // update the balance for the selected address + QString spId = ui->propertyComboBox->itemData(ui->propertyComboBox->currentIndex()).toString(); + uint32_t propertyId = spId.toUInt(); + if (propertyId > 0) { + ui->balanceLabel->setText(QString::fromStdString("Address Balance: " + FormatMP(propertyId, getUserAvailableMPbalance(currentSetFromAddress, propertyId)) + getTokenLabel(propertyId))); + } +} + +void SendMPDialog::updateProperty() +{ + // cache currently selected from address & clear address selector + std::string currentSetFromAddress = ui->sendFromComboBox->currentText().toStdString(); + ui->sendFromComboBox->clear(); + + // populate from address selector + QString spId = ui->propertyComboBox->itemData(ui->propertyComboBox->currentIndex()).toString(); + uint32_t propertyId = spId.toUInt(); + LOCK(cs_tally); + for (std::map::iterator my_it = mp_tally_map.begin(); my_it != mp_tally_map.end(); ++my_it) { + string address = (my_it->first).c_str(); + uint32_t id = 0; + bool includeAddress=false; + (my_it->second).init(); + while (0 != (id = (my_it->second).next())) { + if(id == propertyId) { includeAddress=true; break; } + } + if (!includeAddress) continue; //ignore this address, has never transacted in this propertyId + if (!IsMyAddress(address)) continue; //ignore this address, it's not ours + if ((address.substr(0,1)=="2") || (address.substr(0,3)=="3")) continue; //quick hack to not show P2SH addresses in from selector (can't be sent from UI) + ui->sendFromComboBox->addItem(QString::fromStdString(address + " \t" + FormatMP(propertyId, getUserAvailableMPbalance(address, propertyId)) + getTokenLabel(propertyId))); + } + + // attempt to set from address back to cached value + int fromIdx = ui->sendFromComboBox->findText(QString::fromStdString(currentSetFromAddress), Qt::MatchContains); + if (fromIdx != -1) { ui->sendFromComboBox->setCurrentIndex(fromIdx); } // -1 means the cached from address doesn't have a balance in the newly selected property + + // populate balance for global wallet + int64_t globalAvailable = 0; + if (propertyId<2147483648) { globalAvailable = global_balance_money_maineco[propertyId]; } else { globalAvailable = global_balance_money_testeco[propertyId-2147483647]; } + ui->globalBalanceLabel->setText(QString::fromStdString("Wallet Balance (Available): " + FormatMP(propertyId, globalAvailable) + getTokenLabel(propertyId))); + +#if QT_VERSION >= 0x040700 + // update placeholder text + if (isPropertyDivisible(propertyId)) { ui->amountLineEdit->setPlaceholderText("Enter Divisible Amount"); } else { ui->amountLineEdit->setPlaceholderText("Enter Indivisible Amount"); } +#endif +} + +void SendMPDialog::sendMPTransaction() +{ + // get the property being sent and get divisibility + QString spId = ui->propertyComboBox->itemData(ui->propertyComboBox->currentIndex()).toString(); + if (spId.toStdString().empty()) { + QMessageBox::critical( this, "Unable to send transaction", + "The property selected is not valid.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + uint32_t propertyId = spId.toUInt(); + bool divisible = isPropertyDivisible(propertyId); + + // obtain the selected sender address + string strFromAddress = ui->sendFromComboBox->currentText().toStdString(); + + // push recipient address into a CBitcoinAddress type and check validity + CBitcoinAddress fromAddress; + if (false == strFromAddress.empty()) { fromAddress.SetString(strFromAddress); } + if (!fromAddress.IsValid()) { + QMessageBox::critical( this, "Unable to send transaction", + "The sender address selected is not valid.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + + // obtain the entered recipient address + string strRefAddress = ui->sendToLineEdit->text().toStdString(); + // push recipient address into a CBitcoinAddress type and check validity + CBitcoinAddress refAddress; + if (false == strRefAddress.empty()) { refAddress.SetString(strRefAddress); } + if (!refAddress.IsValid()) { + QMessageBox::critical( this, "Unable to send transaction", + "The recipient address entered is not valid.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + + // warn if we have to truncate the amount due to a decimal amount for an indivisible property, but allow send to continue + string strAmount = ui->amountLineEdit->text().toStdString(); + if (!divisible) { + size_t pos = strAmount.find("."); + if (pos!=std::string::npos) { + string tmpStrAmount = strAmount.substr(0,pos); + string strMsgText = "The amount entered contains a decimal however the property being sent is indivisible.\n\nThe amount entered will be truncated as follows:\n"; + strMsgText += "Original amount entered: " + strAmount + "\nAmount that will be sent: " + tmpStrAmount + "\n\n"; + strMsgText += "Do you still wish to proceed with the transaction?"; + QString msgText = QString::fromStdString(strMsgText); + QMessageBox::StandardButton responseClick; + responseClick = QMessageBox::question(this, "Amount truncation warning", msgText, QMessageBox::Yes|QMessageBox::No); + if (responseClick == QMessageBox::No) { + QMessageBox::critical( this, "Send transaction cancelled", + "The send transaction has been cancelled.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + strAmount = tmpStrAmount; + ui->amountLineEdit->setText(QString::fromStdString(strAmount)); + } + } + + // use strToInt64 function to get the amount, using divisibility of the property + int64_t sendAmount = StrToInt64(strAmount, divisible); + if (0>=sendAmount) { + QMessageBox::critical( this, "Unable to send transaction", + "The amount entered is not valid.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + + // check if sending address has enough funds + int64_t balanceAvailable = getUserAvailableMPbalance(fromAddress.ToString(), propertyId); //getMPbalance(fromAddress.ToString(), propertyId, MONEY); + if (sendAmount>balanceAvailable) { + QMessageBox::critical( this, "Unable to send transaction", + "The selected sending address does not have a sufficient balance to cover the amount entered.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + + // check if wallet is still syncing, as this will currently cause a lockup if we try to send - compare our chain to peers to see if we're up to date + // Bitcoin Core devs have removed GetNumBlocksOfPeers, switching to a time based best guess scenario + uint32_t intBlockDate = GetLatestBlockTime(); // uint32, not using time_t for portability + QDateTime currentDate = QDateTime::currentDateTime(); + int secs = QDateTime::fromTime_t(intBlockDate).secsTo(currentDate); + if(secs > 90*60) { + QMessageBox::critical( this, "Unable to send transaction", + "The client is still synchronizing. Sending transactions can currently be performed only when the client has completed synchronizing." ); + return; + } + + // validation checks all look ok, let's throw up a confirmation dialog + string strMsgText = "You are about to send the following transaction, please check the details thoroughly:\n\n"; + string propDetails = getPropertyName(propertyId).c_str(); + string spNum = static_cast( &(ostringstream() << propertyId) )->str(); + propDetails += " (#" + spNum + ")"; + strMsgText += "From: " + fromAddress.ToString() + "\nTo: " + refAddress.ToString() + "\nProperty: " + propDetails + "\nAmount that will be sent: "; + if (divisible) { strMsgText += FormatDivisibleMP(sendAmount); } else { strMsgText += FormatIndivisibleMP(sendAmount); } + strMsgText += "\n\nAre you sure you wish to send this transaction?"; + QString msgText = QString::fromStdString(strMsgText); + QMessageBox::StandardButton responseClick; + responseClick = QMessageBox::question(this, "Confirm send transaction", msgText, QMessageBox::Yes|QMessageBox::No); + if (responseClick == QMessageBox::No) { + QMessageBox::critical( this, "Send transaction cancelled", + "The send transaction has been cancelled.\n\nPlease double-check the transction details thoroughly before retrying your send transaction." ); + return; + } + + // unlock the wallet + WalletModel::UnlockContext ctx(walletModel->requestUnlock()); + if(!ctx.isValid()) { + return; // unlock wallet was cancelled/failed + } + + // create a payload for the transaction + std::vector payload = CreatePayload_SimpleSend(propertyId, sendAmount); + + // request the wallet build the transaction (and if needed commit it) - note UI does not support added reference amounts currently + uint256 txid = 0; + std::string rawHex; + int result = ClassAgnosticWalletTXBuilder(fromAddress.ToString(), refAddress.ToString(), "", 0, payload, txid, rawHex, autoCommit); + + // check error and return the txid (or raw hex depending on autocommit) + if (result != 0) { + string strError = error_str(result); + QMessageBox::critical( this, "Send transaction failed", + "The send transaction has been cancelled.\n\nThe wallet unlock process must be completed to send a transaction." ); + return; + } else { + if (!autoCommit) { + PopulateSimpleDialog(rawHex, "Raw Hex (auto commit is disabled)", "Raw transaction hex"); + } else { + PendingAdd(txid, fromAddress.ToString(), refAddress.ToString(), MSC_TYPE_SIMPLE_SEND, propertyId, sendAmount); + PopulateTXSentDialog(txid.GetHex()); + } + } + clearFields(); +} + +void SendMPDialog::sendFromComboBoxChanged(int idx) +{ + updateFrom(); +} + +void SendMPDialog::propertyComboBoxChanged(int idx) +{ + updateProperty(); + updateFrom(); +} + +void SendMPDialog::clearButtonClicked() +{ + clearFields(); +} + +void SendMPDialog::sendButtonClicked() +{ + sendMPTransaction(); +} + +void SendMPDialog::balancesUpdated() +{ + set_wallet_totals(); + + updatePropSelector(); + updateProperty(); + updateFrom(); +} diff --git a/src/qt/sendmpdialog.h b/src/qt/sendmpdialog.h new file mode 100644 index 0000000000000..20a7b4ed2074e --- /dev/null +++ b/src/qt/sendmpdialog.h @@ -0,0 +1,59 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef SENDMPDIALOG_H +#define SENDMPDIALOG_H + +#include "walletmodel.h" + +#include +#include + +class ClientModel; + +QT_BEGIN_NAMESPACE +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class SendMPDialog; +} + +/** Dialog for sending Master Protocol tokens */ +class SendMPDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SendMPDialog(QWidget *parent = 0); + ~SendMPDialog(); + + void setClientModel(ClientModel *model); + void setWalletModel(WalletModel *model); + + void clearFields(); + void sendMPTransaction(); + void updateFrom(); + void updateProperty(); + void updatePropSelector(); + +public slots: +// void setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance); + void propertyComboBoxChanged(int idx); + void sendFromComboBoxChanged(int idx); + void clearButtonClicked(); + void sendButtonClicked(); + void balancesUpdated(); + +private: + Ui::SendMPDialog *ui; + ClientModel *clientModel; + WalletModel *walletModel; + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // SENDMPDIALOG_H diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index fed8e15fb52b2..e66536f53062a 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -15,6 +15,8 @@ #include "wallet.h" #endif +#include "mastercore_version.h" + #include #include #include @@ -24,17 +26,19 @@ SplashScreen::SplashScreen(Qt::WindowFlags f, const NetworkStyle *networkStyle) QWidget(0, f), curAlignment(0) { // set reference point, paddings - int paddingRight = 50; - int paddingTop = 50; - int titleVersionVSpace = 17; - int titleCopyrightVSpace = 40; + int paddingLeft = 33; + int paddingTop = 245; + int titleVersionVSpace = 40; + int titleCopyrightVSpace = 58; float fontFactor = 1.0; // define text to place - QString titleText = tr("Bitcoin Core"); - QString versionText = QString("Version %1").arg(QString::fromStdString(FormatFullVersion())); + QString titleText = tr("Omni Core"); + QString versionText = QString("Experimental UI %1").arg(QString::fromStdString(OmniCoreVersion())); QString copyrightText = QChar(0xA9)+QString(" 2009-%1 ").arg(COPYRIGHT_YEAR) + QString(tr("The Bitcoin Core developers")); + copyrightText += QString(", "); + copyrightText += QChar(0xA9)+QString(" 2013-%1 ").arg(COPYRIGHT_YEAR) + QString(tr("The Omni Core developers")); QString titleAddText = networkStyle->getTitleAddText(); QString font = QApplication::font().toString(); @@ -54,25 +58,11 @@ SplashScreen::SplashScreen(Qt::WindowFlags f, const NetworkStyle *networkStyle) fontFactor = 0.75; } - pixPaint.setFont(QFont(font, 33*fontFactor)); + pixPaint.setFont(QFont(font, 20*fontFactor)); fm = pixPaint.fontMetrics(); - titleTextWidth = fm.width(titleText); - pixPaint.drawText(pixmap.width()-titleTextWidth-paddingRight,paddingTop,titleText); - - pixPaint.setFont(QFont(font, 15*fontFactor)); - - // if the version string is to long, reduce size - fm = pixPaint.fontMetrics(); - int versionTextWidth = fm.width(versionText); - if(versionTextWidth > titleTextWidth+paddingRight-10) { - pixPaint.setFont(QFont(font, 10*fontFactor)); - titleVersionVSpace -= 5; - } - pixPaint.drawText(pixmap.width()-titleTextWidth-paddingRight+2,paddingTop+titleVersionVSpace,versionText); - - // draw copyright stuff + pixPaint.drawText(paddingLeft,paddingTop+titleVersionVSpace,versionText); pixPaint.setFont(QFont(font, 10*fontFactor)); - pixPaint.drawText(pixmap.width()-titleTextWidth-paddingRight,paddingTop+titleCopyrightVSpace,copyrightText); + pixPaint.drawText(paddingLeft,paddingTop+titleCopyrightVSpace,copyrightText); // draw additional text if special network if(!titleAddText.isEmpty()) { @@ -113,9 +103,9 @@ static void InitMessage(SplashScreen *splash, const std::string &message) { QMetaObject::invokeMethod(splash, "showMessage", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(message)), - Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter), - Q_ARG(QColor, QColor(55,55,55))); + Q_ARG(QString, QString::fromStdString("\n\n\n\n" + message)), // shift down a little from absolute center + Q_ARG(int, Qt::AlignCenter), + Q_ARG(QColor, QColor(100,100,100))); } static void ShowProgress(SplashScreen *splash, const std::string &title, int nProgress) diff --git a/src/qt/tradehistorydialog.cpp b/src/qt/tradehistorydialog.cpp new file mode 100755 index 0000000000000..bac784ebee558 --- /dev/null +++ b/src/qt/tradehistorydialog.cpp @@ -0,0 +1,605 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "tradehistorydialog.h" +#include "ui_tradehistorydialog.h" + +#include "omnicore_qtutils.h" + +#include "mastercore.h" +#include "mastercore_mdex.h" +#include "mastercore_rpc.h" +#include "mastercore_tx.h" +#include "omnicore_pending.h" + +#include "guiutil.h" +#include "ui_interface.h" +#include "walletmodel.h" + +#include "amount.h" +#include "init.h" +#include "main.h" +#include "primitives/transaction.h" +#include "sync.h" +#include "txdb.h" +#include "uint256.h" +#include "wallet.h" + +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer_template.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::ostringstream; +using std::string; + +using namespace json_spirit; +using namespace mastercore; + +TradeHistoryDialog::TradeHistoryDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::tradeHistoryDialog), + model(0) +{ + // Setup the UI + ui->setupUi(this); + ui->tradeHistoryTable->setColumnCount(8); + // Note there are two hidden fields in tradeHistoryTable: 0=txid, 1=lastUpdateBlock + ui->tradeHistoryTable->setHorizontalHeaderItem(2, new QTableWidgetItem(" ")); + ui->tradeHistoryTable->setHorizontalHeaderItem(3, new QTableWidgetItem("Date")); + ui->tradeHistoryTable->setHorizontalHeaderItem(4, new QTableWidgetItem("Status")); + ui->tradeHistoryTable->setHorizontalHeaderItem(5, new QTableWidgetItem("Trade Details")); + ui->tradeHistoryTable->setHorizontalHeaderItem(6, new QTableWidgetItem("Sold")); + ui->tradeHistoryTable->setHorizontalHeaderItem(7, new QTableWidgetItem("Received")); + borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->tradeHistoryTable,100,100); + #if QT_VERSION < 0x050000 + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(3, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(4, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(5, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(6, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setResizeMode(7, QHeaderView::Interactive); + #else + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(6, QHeaderView::Interactive); + ui->tradeHistoryTable->horizontalHeader()->setSectionResizeMode(7, QHeaderView::Interactive); + #endif + ui->tradeHistoryTable->setAlternatingRowColors(true); + ui->tradeHistoryTable->verticalHeader()->setVisible(false); + ui->tradeHistoryTable->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->tradeHistoryTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->tradeHistoryTable->setSelectionMode(QAbstractItemView::SingleSelection); + ui->tradeHistoryTable->setContextMenuPolicy(Qt::CustomContextMenu); + ui->tradeHistoryTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->tradeHistoryTable->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + UpdateTradeHistoryTable(); // make sure we're populated before attempting to resize to contents + ui->tradeHistoryTable->setColumnHidden(0, true); + ui->tradeHistoryTable->setColumnHidden(1, true); + ui->tradeHistoryTable->setColumnWidth(2, 23); + ui->tradeHistoryTable->resizeColumnToContents(3); + ui->tradeHistoryTable->resizeColumnToContents(4); + ui->tradeHistoryTable->resizeColumnToContents(6); + ui->tradeHistoryTable->resizeColumnToContents(7); + borrowedColumnResizingFixer->stretchColumnWidth(5); + ui->tradeHistoryTable->setSortingEnabled(true); + ui->tradeHistoryTable->horizontalHeader()->setSortIndicator(3, Qt::DescendingOrder); + QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); + QAction *showDetailsAction = new QAction(tr("Show trade details"), this); + contextMenu = new QMenu(); + contextMenu->addAction(copyTxIDAction); + contextMenu->addAction(showDetailsAction); + connect(ui->tradeHistoryTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); + connect(ui->tradeHistoryTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(showDetails())); + connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); + connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); +} + +TradeHistoryDialog::~TradeHistoryDialog() +{ + delete ui; +} + +// The main function to update the UI tradeHistoryTable +void TradeHistoryDialog::UpdateTradeHistoryTable() +{ + // Populate tradeHistoryMap + int newTXCount = PopulateTradeHistoryMap(); + + // Process any new transactions that were added to the map + if (newTXCount > 0) { + ui->tradeHistoryTable->setSortingEnabled(false); // disable sorting while we update the table + QAbstractItemModel* tradeHistoryAbstractModel = ui->tradeHistoryTable->model(); + int chainHeight = chainActive.Height(); + + // Loop through tradeHistoryMap and search tradeHistoryTable for the transaction, adding it if not already there + for (TradeHistoryMap::iterator it = tradeHistoryMap.begin(); it != tradeHistoryMap.end(); ++it) { + uint256 txid = it->first; + QSortFilterProxyModel tradeHistoryProxy; + tradeHistoryProxy.setSourceModel(tradeHistoryAbstractModel); + tradeHistoryProxy.setFilterKeyColumn(0); + tradeHistoryProxy.setFilterFixedString(QString::fromStdString(txid.GetHex())); + QModelIndex rowIndex = tradeHistoryProxy.mapToSource(tradeHistoryProxy.index(0,0)); + if (rowIndex.isValid()) continue; // Found tx in tradeHistoryTable already, do nothing + + TradeHistoryObject objTH = it->second; + int newRow = ui->tradeHistoryTable->rowCount(); + ui->tradeHistoryTable->insertRow(newRow); // append a new row (sorting will take care of ordering) + + // Create the cells to be added to the new row and setup their formatting + QTableWidgetItem *dateCell = new QTableWidgetItem; + QDateTime txTime; + if (objTH.blockHeight > 0) { + CBlockIndex* pBlkIdx = chainActive[objTH.blockHeight]; + if (NULL != pBlkIdx) txTime.setTime_t(pBlkIdx->GetBlockTime()); + dateCell->setData(Qt::DisplayRole, txTime); + } else { + dateCell->setData(Qt::DisplayRole, QString::fromStdString("Unconfirmed")); + } + QTableWidgetItem *lastUpdateBlockCell = new QTableWidgetItem(QString::fromStdString(FormatIndivisibleMP(chainHeight))); + QTableWidgetItem *statusCell = new QTableWidgetItem(QString::fromStdString(objTH.status)); + QTableWidgetItem *infoCell = new QTableWidgetItem(QString::fromStdString(objTH.info)); + QTableWidgetItem *amountOutCell = new QTableWidgetItem(QString::fromStdString(objTH.amountOut)); + QTableWidgetItem *amountInCell = new QTableWidgetItem(QString::fromStdString(objTH.amountIn)); + QTableWidgetItem *txidCell = new QTableWidgetItem(QString::fromStdString(txid.GetHex())); + QTableWidgetItem *iconCell = new QTableWidgetItem; + QIcon ic = QIcon(":/icons/transaction_0"); + if (objTH.status == "Cancelled") ic =QIcon(":/icons/meta_cancelled"); + if (objTH.status == "Part Cancel") ic = QIcon(":/icons/meta_partialclosed"); + if (objTH.status == "Filled") ic = QIcon(":/icons/meta_filled"); + if (objTH.status == "Open") ic = QIcon(":/icons/meta_open"); + if (objTH.status == "Part Filled") ic = QIcon(":/icons/meta_partial"); + if (!objTH.valid) ic = QIcon(":/icons/transaction_invalid"); + iconCell->setIcon(ic); + amountOutCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + amountOutCell->setForeground(QColor("#EE0000")); + amountInCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + amountInCell->setForeground(QColor("#00AA00")); + if (objTH.status == "Cancelled" || objTH.status == "Filled") { + // dull the colors for non-active trades + dateCell->setForeground(QColor("#707070")); + statusCell->setForeground(QColor("#707070")); + infoCell->setForeground(QColor("#707070")); + amountOutCell->setForeground(QColor("#993333")); + amountInCell->setForeground(QColor("#006600")); + } + if(objTH.amountIn.substr(0,2) == "0 " || objTH.amountIn == "---" ) amountInCell->setForeground(QColor("#000000")); + if(objTH.amountOut.substr(0,2) == "0 " || objTH.amountOut == "---" ) amountOutCell->setForeground(QColor("#000000")); + + // Set the cells in the new row accordingly + ui->tradeHistoryTable->setItem(newRow, 0, txidCell); + ui->tradeHistoryTable->setItem(newRow, 1, lastUpdateBlockCell); + ui->tradeHistoryTable->setItem(newRow, 2, iconCell); + ui->tradeHistoryTable->setItem(newRow, 3, dateCell); + ui->tradeHistoryTable->setItem(newRow, 4, statusCell); + ui->tradeHistoryTable->setItem(newRow, 5, infoCell); + ui->tradeHistoryTable->setItem(newRow, 6, amountOutCell); + ui->tradeHistoryTable->setItem(newRow, 7, amountInCell); + } + ui->tradeHistoryTable->setSortingEnabled(true); // re-enable sorting + } + + // Update any existing rows that may have changed data + UpdateData(); +} + +// Used to cache trades so we don't need to reparse all our transactions on every update +int TradeHistoryDialog::PopulateTradeHistoryMap() +{ + // Attempt to obtain locks + CWallet *wallet = pwalletMain; + if (NULL == wallet) return -1; + TRY_LOCK(cs_main,lckMain); + if (!lckMain) return -1; + TRY_LOCK(wallet->cs_wallet, lckWallet); + if (!lckWallet) return -1; + + int64_t nProcessed = 0; // number of new entries, forms return code + + // ### START PENDING TRANSACTIONS PROCESSING ### + for(PendingMap::iterator it = my_pending.begin(); it != my_pending.end(); ++it) { + uint256 txid = it->first; + + // check historyMap, if this tx exists don't waste resources doing anymore work on it + TradeHistoryMap::iterator hIter = tradeHistoryMap.find(txid); + if (hIter != tradeHistoryMap.end()) continue; + + // grab pending object, extract details and skip if not a metadex trade + CMPPending *p_pending = &(it->second); + if (p_pending->type != MSC_TYPE_METADEX) continue; + uint32_t propertyId = p_pending->prop; + int64_t amount = p_pending->amount; + + // create a TradeHistoryObject and populate it + TradeHistoryObject objTH; + objTH.blockHeight = 0; + objTH.valid = true; // all pending transactions are assumed to be valid + objTH.propertyIdForSale = propertyId; + objTH.propertyIdDesired = 0; // unknown at this stage & not needed for pending + objTH.amountForSale = amount; + objTH.status = "Pending"; + objTH.amountIn = "---"; + objTH.amountOut = "---"; + objTH.info = "Sell "; + if (isPropertyDivisible(propertyId)) { + objTH.info += FormatDivisibleShortMP(amount) + getTokenLabel(propertyId) + " (awaiting confirmation)"; + } else { + objTH.info += FormatIndivisibleMP(amount) + getTokenLabel(propertyId) + " (awaiting confirmation)"; + } + + // add the new TradeHistoryObject to the map + tradeHistoryMap.insert(std::make_pair(txid, objTH)); + nProcessed += 1; + } + // ### END PENDING TRANSACTIONS PROCESSING ### + + // ### START WALLET TRANSACTIONS PROCESSING ### + std::list acentries; + CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, "*"); + for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx == 0) continue; + uint256 hash = pwtx->GetHash(); + + // use levelDB to perform a fast check on whether it's a bitcoin or Omni tx and whether it's a trade + std::string tempStrValue; + if (!p_txlistdb->getTX(hash, tempStrValue)) continue; + std::vector vstr; + boost::split(vstr, tempStrValue, boost::is_any_of(":"), boost::token_compress_on); + if (vstr.size() > 2) { + if (atoi(vstr[2]) != MSC_TYPE_METADEX) continue; + } + + // check historyMap, if this tx exists don't waste resources doing anymore work on it + TradeHistoryMap::iterator hIter = tradeHistoryMap.find(hash); + if (hIter != tradeHistoryMap.end()) { + // the tx is in historyMap, check if it has a blockheight of 0 (means a pending has confirmed & we should delete pending tx and add it again via parsing) + TradeHistoryObject *objTH = &(hIter->second); + if (objTH->blockHeight != 0) { + continue; + } else { + tradeHistoryMap.erase(hIter); + ui->tradeHistoryTable->setSortingEnabled(false); // disable sorting temporarily while we update the table (leaving enabled gives unexpected results) + QAbstractItemModel* tradeHistoryAbstractModel = ui->tradeHistoryTable->model(); + QSortFilterProxyModel tradeHistoryProxy; + tradeHistoryProxy.setSourceModel(tradeHistoryAbstractModel); + tradeHistoryProxy.setFilterKeyColumn(0); + tradeHistoryProxy.setFilterFixedString(QString::fromStdString(hash.GetHex())); + QModelIndex rowIndex = tradeHistoryProxy.mapToSource(tradeHistoryProxy.index(0,0)); // map to the row in the actual table + if(rowIndex.isValid()) ui->tradeHistoryTable->removeRow(rowIndex.row()); // delete the pending tx row, it'll be readded as a proper confirmed transaction + ui->tradeHistoryTable->setSortingEnabled(true); // re-enable sorting + } + } + + // tx not in historyMap, retrieve the transaction object + CTransaction wtx; + uint256 blockHash = 0; + if (!GetTransaction(hash, wtx, blockHash, true)) continue; + blockHash = pwtx->hashBlock; + if ((0 == blockHash) || (NULL == GetBlockIndex(blockHash))) continue; + CBlockIndex* pBlockIndex = GetBlockIndex(blockHash); + if (NULL == pBlockIndex) continue; + int blockHeight = pBlockIndex->nHeight; + + // setup some variables + CMPTransaction mp_obj; + CMPMetaDEx temp_metadexoffer; + std::string statusText; + uint32_t propertyIdForSale = 0; + uint32_t propertyIdDesired = 0; + int64_t amountForSale = 0; + int64_t amountDesired = 0; + bool divisibleForSale = false; + bool divisibleDesired = false; + Array tradeArray; + int64_t totalBought = 0; + int64_t totalSold = 0; + bool orderOpen = false; + bool valid = false; + + // parse the transaction + int parseRC = parseTransaction(true, wtx, blockHeight, 0, &mp_obj); + if (0 != parseRC) continue; + if (0<=mp_obj.step1()) { + int tmpblock=0; + uint32_t tmptype=0; + uint64_t amountNew=0; + valid = getValidMPTX(hash, &tmpblock, &tmptype, &amountNew); + if (0 == mp_obj.step2_Value()) { + propertyIdForSale = mp_obj.getProperty(); + amountForSale = mp_obj.getAmount(); + divisibleForSale = isPropertyDivisible(propertyIdForSale); + if (0 <= mp_obj.interpretPacket(NULL,&temp_metadexoffer)) { + uint8_t mdex_action = temp_metadexoffer.getAction(); + if (mdex_action != 1) continue; // cancels aren't trades + propertyIdDesired = temp_metadexoffer.getDesProperty(); + divisibleDesired = isPropertyDivisible(propertyIdDesired); + amountDesired = temp_metadexoffer.getAmountDesired(); + t_tradelistdb->getMatchingTrades(hash, propertyIdForSale, &tradeArray, &totalSold, &totalBought); + orderOpen = MetaDEx_isOpen(hash, propertyIdForSale); + } + } + } + + // work out status + bool partialFilled = false; + bool filled = false; + statusText = "Unknown"; + if (totalSold > 0) partialFilled = true; + if (totalSold >= amountForSale) filled = true; + if (!orderOpen && !partialFilled) statusText = "Cancelled"; + if (!orderOpen && partialFilled) statusText = "Part Cancel"; + if (!orderOpen && filled) statusText = "Filled"; + if (orderOpen && !partialFilled) statusText = "Open"; + if (orderOpen && partialFilled) statusText = "Part Filled"; + + // prepare display values + std::string displayText = "Sell "; + if (divisibleForSale) { displayText += FormatDivisibleShortMP(amountForSale); } else { displayText += FormatIndivisibleMP(amountForSale); } + displayText += getTokenLabel(propertyIdForSale) + " for "; + if (divisibleDesired) { displayText += FormatDivisibleShortMP(amountDesired); } else { displayText += FormatIndivisibleMP(amountDesired); } + displayText += getTokenLabel(propertyIdDesired); + std::string displayIn = ""; + std::string displayOut = "-"; + if(divisibleDesired) { displayIn += FormatDivisibleShortMP(totalBought); } else { displayIn += FormatIndivisibleMP(totalBought); } + if(divisibleForSale) { displayOut += FormatDivisibleShortMP(totalSold); } else { displayOut += FormatIndivisibleMP(totalSold); } + if(totalBought == 0) displayIn = "0"; + if(totalSold == 0) displayOut = "0"; + displayIn += getTokenLabel(propertyIdDesired); + displayOut += getTokenLabel(propertyIdForSale); + + // create a TradeHistoryObject and populate it + TradeHistoryObject objTH; + objTH.blockHeight = blockHeight; + objTH.valid = valid; + objTH.propertyIdForSale = propertyIdForSale; + objTH.propertyIdDesired = propertyIdDesired; + objTH.amountForSale = amountForSale; + objTH.status = statusText; + objTH.amountIn = displayIn; + objTH.amountOut = displayOut; + objTH.info = displayText; + + // add the new TradeHistoryObject to the map + tradeHistoryMap.insert(std::make_pair(hash, objTH)); + nProcessed += 1; + } + // ### END WALLET TRANSACTIONS PROCESSING ### + + return nProcessed; +} + +// Each time an update is called it's feasible that the status and amounts traded for open trades may have changed +// This function will loop through each of the rows in tradeHistoryTable and check if the details need updating +void TradeHistoryDialog::UpdateData() +{ + int chainHeight = chainActive.Height(); + int rowCount = ui->tradeHistoryTable->rowCount(); + for (int row = 0; row < rowCount; row++) { + // check if we need to refresh the details for this row + int lastUpdateBlock = ui->tradeHistoryTable->item(row,1)->text().toInt(); + uint256 txid; + txid.SetHex(ui->tradeHistoryTable->item(row,0)->text().toStdString()); + TradeHistoryMap::iterator hIter = tradeHistoryMap.find(txid); + if (hIter == tradeHistoryMap.end()) { + file_log("UI Error: Transaction %s appears in tradeHistoryTable but cannot be found in tradeHistoryMap.\n", txid.GetHex()); + continue; + } + TradeHistoryObject *tmpObjTH = &(hIter->second); + if (tmpObjTH->status == "Filled" || tmpObjTH->status == "Cancelled") continue; // once a trade hits this status the details should never change + if (tmpObjTH->blockHeight == 0) continue; // do not attempt to refresh details for a trade that's still pending + if (lastUpdateBlock == chainHeight) continue; // no new blocks since last update, don't waste compute looking for updates + + // at this point we have an active trade and there have been new block(s) since the last update - refresh status and amounts + uint32_t propertyIdForSale = tmpObjTH->propertyIdForSale; + uint32_t propertyIdDesired = tmpObjTH->propertyIdDesired; + int64_t amountForSale = tmpObjTH->amountForSale; + Array tradeArray; + int64_t totalBought = 0; + int64_t totalSold = 0; + bool orderOpen = false; + t_tradelistdb->getMatchingTrades(txid, propertyIdForSale, &tradeArray, &totalSold, &totalBought); + orderOpen = MetaDEx_isOpen(txid, propertyIdForSale); + + // work out new status & icon + bool partialFilled = false; + bool filled = false; + if (totalSold > 0) partialFilled = true; + if (totalSold >= amountForSale) filled = true; + std::string statusText = "Unknown"; + QIcon ic = QIcon(":/icons/transaction_0"); + if (!orderOpen && !partialFilled) { statusText = "Cancelled"; ic = QIcon(":/icons/meta_cancelled"); } + if (!orderOpen && partialFilled) { statusText = "Part Cancel"; ic = QIcon(":/icons/meta_partialclosed"); } + if (!orderOpen && filled) { statusText = "Filled"; ic = QIcon(":/icons/meta_filled"); } + if (orderOpen && !partialFilled) { statusText = "Open"; ic = QIcon(":/icons/meta_open"); } + if (orderOpen && partialFilled) { statusText = "Part Filled"; ic = QIcon(":/icons/meta_partial"); } + + // format new amounts + std::string displayIn = ""; + std::string displayOut = "-"; + if (isPropertyDivisible(propertyIdDesired)) { displayIn += FormatDivisibleShortMP(totalBought); } else { displayIn += FormatIndivisibleMP(totalBought); } + if (isPropertyDivisible(propertyIdForSale)) { displayOut += FormatDivisibleShortMP(totalSold); } else { displayOut += FormatIndivisibleMP(totalSold); } + if (totalBought == 0) displayIn = "0"; + if (totalSold == 0) displayOut = "0"; + displayIn += getTokenLabel(propertyIdDesired); + displayOut += getTokenLabel(propertyIdForSale); + + // create and format replacement cells + QTableWidgetItem *lastUpdateBlockCell = new QTableWidgetItem(QString::fromStdString(FormatIndivisibleMP(chainHeight))); + QTableWidgetItem *statusCell = new QTableWidgetItem(QString::fromStdString(statusText)); + QTableWidgetItem *amountOutCell = new QTableWidgetItem(QString::fromStdString(displayOut)); + QTableWidgetItem *amountInCell = new QTableWidgetItem(QString::fromStdString(displayIn)); + QTableWidgetItem *iconCell = new QTableWidgetItem; + QTableWidgetItem *dateCell = ui->tradeHistoryTable->item(row, 3); // values don't change so can be copied + QTableWidgetItem *infoCell = ui->tradeHistoryTable->item(row, 5); // as above + iconCell->setIcon(ic); + amountOutCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + amountOutCell->setForeground(QColor("#EE0000")); + amountInCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + amountInCell->setForeground(QColor("#00AA00")); + if (statusText == "Cancelled" || statusText == "Filled") { + // dull the colors for non-active trades + dateCell->setForeground(QColor("#707070")); + statusCell->setForeground(QColor("#707070")); + infoCell->setForeground(QColor("#707070")); + amountOutCell->setForeground(QColor("#993333")); + amountInCell->setForeground(QColor("#006600")); + } + if(displayIn.substr(0,2) == "0 ") amountInCell->setForeground(QColor("#000000")); + if(displayOut.substr(0,2) == "0 ") amountOutCell->setForeground(QColor("#000000")); + + // replace cells in row accordingly + ui->tradeHistoryTable->setItem(row, 1, lastUpdateBlockCell); + ui->tradeHistoryTable->setItem(row, 2, iconCell); + ui->tradeHistoryTable->setItem(row, 3, dateCell); + ui->tradeHistoryTable->setItem(row, 4, statusCell); + ui->tradeHistoryTable->setItem(row, 5, infoCell); + ui->tradeHistoryTable->setItem(row, 6, amountOutCell); + ui->tradeHistoryTable->setItem(row, 7, amountInCell); + } +} + +void TradeHistoryDialog::setModel(WalletModel *model) +{ + this->model = model; + if (NULL != model) { + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(UpdateTradeHistoryTable())); + } +} + +void TradeHistoryDialog::contextualMenu(const QPoint &point) +{ + QModelIndex index = ui->tradeHistoryTable->indexAt(point); + if(index.isValid()) contextMenu->exec(QCursor::pos()); +} + +void TradeHistoryDialog::copyTxID() +{ + GUIUtil::setClipboard(ui->tradeHistoryTable->item(ui->tradeHistoryTable->currentRow(),6)->text()); +} + +/* Opens a dialog containing the details of the selected trade and any associated matches + */ +void TradeHistoryDialog::showDetails() +{ + Object txobj; + uint256 txid; + txid.SetHex(ui->tradeHistoryTable->item(ui->tradeHistoryTable->currentRow(),0)->text().toStdString()); + std::string strTXText; + + // first of all check if the TX is a pending tx, if so grab details from pending map + PendingMap::iterator it = my_pending.find(txid); + if (it != my_pending.end()) { + CMPPending *p_pending = &(it->second); + strTXText = "*** THIS TRANSACTION IS UNCONFIRMED ***\n" + p_pending->desc; + } else { + int pop = populateRPCTransactionObject(txid, &txobj, ""); + if (0<=pop) { + Object tradeobj; + CMPMetaDEx temp_metadexoffer; + string senderAddress; + unsigned int propertyId = 0; + CTransaction wtx; + uint256 blockHash = 0; + if (!GetTransaction(txid, wtx, blockHash, true)) { return; } + CMPTransaction mp_obj; + int parseRC = parseTransaction(true, wtx, 0, 0, &mp_obj); + if (0 <= parseRC) { //negative RC means no MP content/badly encoded TX, we shouldn't see this if TX in levelDB but check for sanity + if (0<=mp_obj.step1()) { + senderAddress = mp_obj.getSender(); + if (0 == mp_obj.step2_Value()) propertyId = mp_obj.getProperty(); + } + } + int64_t amountForSale = mp_obj.getAmount(); + + // obtain action byte + int actionByte = 0; + if (0 <= mp_obj.interpretPacket(NULL,&temp_metadexoffer)) { actionByte = (int)temp_metadexoffer.getAction(); } + + // obtain an array of matched trades (action 1) or array of cancelled trades (action 2/3/4) + Array tradeArray, cancelArray; + int64_t totalBought = 0, totalSold = 0; + if (actionByte == 1) { + t_tradelistdb->getMatchingTrades(txid, propertyId, &tradeArray, &totalSold, &totalBought); + } else { + int numberOfCancels = p_txlistdb->getNumberOfMetaDExCancels(txid); + if (numberOfCancels > 0) { + for(int refNumber = 1; refNumber <= numberOfCancels; refNumber++) { + Object cancelTx; + string strValue = p_txlistdb->getKeyValue(txid.ToString() + "-C" + static_cast( &(ostringstream() << refNumber) )->str() ); + if (!strValue.empty()) { + std::vector vstr; + boost::split(vstr, strValue, boost::is_any_of(":"), boost::token_compress_on); + if (3 <= vstr.size()) { + uint64_t propId = boost::lexical_cast(vstr[1]); + cancelTx.push_back(Pair("txid", vstr[0])); + cancelTx.push_back(Pair("propertyid", propId)); + cancelTx.push_back(Pair("amountunreserved", FormatMP(propId, boost::lexical_cast(vstr[2])))); + cancelArray.push_back(cancelTx); + } + } + } + } + } + + // obtain the status of the trade + bool orderOpen = isMetaDExOfferActive(txid, propertyId); + bool partialFilled = false, filled = false; + string statusText = "unknown"; + if (totalSold>0) partialFilled = true; + if (totalSold>=amountForSale) filled = true; + if (orderOpen) { + if (!partialFilled) { statusText = "open"; } else { statusText = "open part filled"; } + } else { + if (!partialFilled) { statusText = "cancelled"; } else { statusText = "cancelled part filled"; } + if (filled) statusText = "filled"; + } + if (actionByte == 1) { txobj.push_back(Pair("status", statusText)); } else { txobj.push_back(Pair("status", "N/A")); } + + // add the appropriate array based on action byte + if(actionByte == 1) { + txobj.push_back(Pair("matches", tradeArray)); + if((statusText == "cancelled") || (statusText == "cancelled part filled")) txobj.push_back(Pair("canceltxid", p_txlistdb->findMetaDExCancel(txid).GetHex())); + } else { + txobj.push_back(Pair("cancelledtransactions", cancelArray)); + } + + strTXText = write_string(Value(txobj), true); + } + } + + if (!strTXText.empty()) { + PopulateSimpleDialog(strTXText, "Trade Information", "Trade Information"); + } +} + +void TradeHistoryDialog::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + borrowedColumnResizingFixer->stretchColumnWidth(5); +} + diff --git a/src/qt/tradehistorydialog.h b/src/qt/tradehistorydialog.h new file mode 100755 index 0000000000000..121f74b6587d3 --- /dev/null +++ b/src/qt/tradehistorydialog.h @@ -0,0 +1,78 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef TRADEHISTORYDIALOG_H +#define TRADEHISTORYDIALOG_H + +#include "guiutil.h" +#include "uint256.h" + +#include + +class WalletModel; + +QT_BEGIN_NAMESPACE +class QMenu; +class QPoint; +class QResizeEvent; +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class tradeHistoryDialog; +} + +class TradeHistoryObject +{ +public: + TradeHistoryObject() + : blockHeight(-1), valid(false) {}; + int blockHeight; // block transaction was mined in + bool valid; // whether the transaction is valid from an Omni perspective + uint32_t propertyIdForSale; // the property being sold + uint32_t propertyIdDesired; // the property being requested + int64_t amountForSale; // the amount being sold + std::string status; // string containing status of trade + std::string info; // string containing human readable description of trade + std::string amountOut; // string containing formatted amount out + std::string amountIn; // string containing formatted amount in +}; + +typedef std::map TradeHistoryMap; + +/** Dialog for looking up Master Protocol tokens */ +class TradeHistoryDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TradeHistoryDialog(QWidget *parent = 0); + ~TradeHistoryDialog(); + void setModel(WalletModel *model); + GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; + virtual void resizeEvent(QResizeEvent* event); + +private: + Ui::tradeHistoryDialog *ui; + WalletModel *model; + QMenu *contextMenu; + TradeHistoryMap tradeHistoryMap; + +public slots: + void contextualMenu(const QPoint &point); + void showDetails(); + void copyTxID(); + +private slots: + int PopulateTradeHistoryMap(); + void UpdateData(); + void UpdateTradeHistoryTable(); + +signals: + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // TRADEHISTORYDIALOG_H diff --git a/src/qt/txhistorydialog.cpp b/src/qt/txhistorydialog.cpp new file mode 100644 index 0000000000000..427dbcc5a8f5c --- /dev/null +++ b/src/qt/txhistorydialog.cpp @@ -0,0 +1,593 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "txhistorydialog.h" +#include "ui_txhistorydialog.h" + +#include "omnicore_qtutils.h" + +#include "clientmodel.h" +#include "guiutil.h" +#include "walletmodel.h" + +#include "mastercore.h" +#include "mastercore_rpc.h" +#include "mastercore_sp.h" +#include "mastercore_tx.h" +#include "omnicore_pending.h" + +#include "init.h" +#include "main.h" +#include "primitives/transaction.h" +#include "sync.h" +#include "txdb.h" +#include "uint256.h" +#include "wallet.h" + +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer_template.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; +using namespace json_spirit; +using namespace mastercore; + +TXHistoryDialog::TXHistoryDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::txHistoryDialog), + clientModel(0), + walletModel(0) +{ + ui->setupUi(this); + // setup + ui->txHistoryTable->setColumnCount(7); + ui->txHistoryTable->setHorizontalHeaderItem(2, new QTableWidgetItem(" ")); + ui->txHistoryTable->setHorizontalHeaderItem(3, new QTableWidgetItem("Date")); + ui->txHistoryTable->setHorizontalHeaderItem(4, new QTableWidgetItem("Type")); + ui->txHistoryTable->setHorizontalHeaderItem(5, new QTableWidgetItem("Address")); + ui->txHistoryTable->setHorizontalHeaderItem(6, new QTableWidgetItem("Amount")); + // borrow ColumnResizingFixer again + borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->txHistoryTable,100,100); + // allow user to adjust - go interactive then manually set widths + #if QT_VERSION < 0x050000 + ui->txHistoryTable->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); + ui->txHistoryTable->horizontalHeader()->setResizeMode(3, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setResizeMode(4, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setResizeMode(5, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setResizeMode(6, QHeaderView::Interactive); + #else + ui->txHistoryTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->txHistoryTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Interactive); + ui->txHistoryTable->horizontalHeader()->setSectionResizeMode(6, QHeaderView::Interactive); + #endif + ui->txHistoryTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->txHistoryTable->verticalHeader()->setVisible(false); + ui->txHistoryTable->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->txHistoryTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->txHistoryTable->setSelectionMode(QAbstractItemView::SingleSelection); + ui->txHistoryTable->setTabKeyNavigation(false); + ui->txHistoryTable->setContextMenuPolicy(Qt::CustomContextMenu); + // set alternating row colors via styling instead of manually + ui->txHistoryTable->setAlternatingRowColors(true); + // Actions + QAction *copyAddressAction = new QAction(tr("Copy address"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); + QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); + contextMenu = new QMenu(); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTxIDAction); + contextMenu->addAction(showDetailsAction); + // Connect actions + connect(ui->txHistoryTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); + connect(ui->txHistoryTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(showDetails())); + connect(ui->txHistoryTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(checkSort(int))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); + connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); + // Perform initial population and update of history table + + UpdateHistory(); + // since there is no model we can't do this before we add some data, so now let's do the resizing + // *after* we've populated the initial content for the table + ui->txHistoryTable->setColumnWidth(2, 23); + ui->txHistoryTable->resizeColumnToContents(3); + ui->txHistoryTable->resizeColumnToContents(4); + ui->txHistoryTable->resizeColumnToContents(6); + ui->txHistoryTable->setColumnHidden(0, true); // hidden txid for displaying transaction details + ui->txHistoryTable->setColumnHidden(1, true); // hideen sort key + borrowedColumnResizingFixer->stretchColumnWidth(5); + ui->txHistoryTable->setSortingEnabled(true); + ui->txHistoryTable->horizontalHeader()->setSortIndicator(1, Qt::DescendingOrder); // sort by hidden sort key + +} + +TXHistoryDialog::~TXHistoryDialog() +{ + delete ui; +} + +void TXHistoryDialog::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if (model != NULL) { + connect(model, SIGNAL(refreshOmniState()), this, SLOT(UpdateHistory())); + connect(model, SIGNAL(numBlocksChanged(int)), this, SLOT(UpdateConfirmations())); + } +} + +void TXHistoryDialog::setWalletModel(WalletModel *model) +{ + this->walletModel = model; + if (model != NULL) { + connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(UpdateHistory())); + } +} + +int TXHistoryDialog::PopulateHistoryMap() +{ + CWallet *wallet = pwalletMain; + if (NULL == wallet) return 0; + // try and fix intermittent freeze on startup and while running by only updating if we can get required locks + TRY_LOCK(cs_main,lckMain); + if (!lckMain) return 0; + TRY_LOCK(wallet->cs_wallet, lckWallet); + if (!lckWallet) return 0; + + int64_t nProcessed = 0; // counter for how many transactions we've added to history this time + // ### START PENDING TRANSACTIONS PROCESSING ### + for(PendingMap::iterator it = my_pending.begin(); it != my_pending.end(); ++it) + { + // grab txid + uint256 txid = it->first; + // check historyMap, if this tx exists don't waste resources doing anymore work on it + HistoryMap::iterator hIter = txHistoryMap.find(txid); + if (hIter != txHistoryMap.end()) continue; + // grab pending object & extract details + CMPPending *p_pending = &(it->second); + string senderAddress = p_pending->src; + uint64_t propertyId = p_pending->prop; + int64_t amount = p_pending->amount; + // create a HistoryTXObject and add to map + HistoryTXObject htxo; + htxo.blockHeight = 0; // how are we gonna order pending txs???? + htxo.blockByteOffset = 0; // attempt to use the position of the transaction in the wallet to provide a sortkey for pending + std::map::const_iterator walletIt = wallet->mapWallet.find(txid); + if (walletIt != wallet->mapWallet.end()) { + const CWalletTx* pendingWTx = &(*walletIt).second; + htxo.blockByteOffset = pendingWTx->nOrderPos; + } + htxo.valid = true; // all pending transactions are assumed to be valid while awaiting confirmation since all pending are outbound and we wouldn't let them be sent if invalid + if (p_pending->type == 0) { htxo.txType = "Send"; htxo.fundsMoved = true; } // we don't have a CMPTransaction class here so manually set the type for now + if (p_pending->type == 21) { htxo.txType = "MetaDEx Trade"; htxo.fundsMoved = false; } // send and metadex trades are the only supported outbound txs (thus only possible pending) for now + htxo.address = senderAddress; // always sender, all pending are outbound + if(isPropertyDivisible(propertyId)) {htxo.amount = "-"+FormatDivisibleShortMP(amount)+getTokenLabel(propertyId);} else {htxo.amount="-"+FormatIndivisibleMP(amount)+getTokenLabel(propertyId);} // pending always outbound + txHistoryMap.insert(std::make_pair(txid, htxo)); + nProcessed += 1; // increase our counter so we don't go crazy on a wallet with too many transactions and lock up UI + } + // ### END PENDING TRANSACTIONS PROCESSING ### + + // ### START WALLET TRANSACTIONS PROCESSING ### + // STO has no inbound transaction, so we need to use an insert methodology here - get STO receipts affecting me + string mySTOReceipts = s_stolistdb->getMySTOReceipts(""); + std::vector vecReceipts; + boost::split(vecReceipts, mySTOReceipts, boost::is_any_of(","), boost::token_compress_on); + int64_t lastTXBlock = 999999; // set artificially high initially until first wallet tx is processed + // iterate through wallet entries backwards + std::list acentries; + CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, "*"); + for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx != 0) { + uint256 hash = pwtx->GetHash(); + // check txlistdb, if not there it's not a confirmed Omni transaction so move to next transaction + if (!p_txlistdb->exists(hash)) continue; + // check historyMap, if this tx exists don't waste resources doing anymore work on it + HistoryMap::iterator hIter = txHistoryMap.find(hash); + if (hIter != txHistoryMap.end()) { // the tx is in historyMap, check if it has a blockheight of 0 (means a pending has confirmed) + HistoryTXObject *temphtxo = &(hIter->second); + if (temphtxo->blockHeight == 0) { // pending has confirmed, delete the pending transaction from the map/table and have it parsed/readded properly + txHistoryMap.erase(hIter); + ui->txHistoryTable->setSortingEnabled(false); // disable sorting temporarily while we update the table (leaving enabled gives unexpected results) + QAbstractItemModel* historyAbstractModel = ui->txHistoryTable->model(); // get a model to work with + QSortFilterProxyModel historyProxy; + historyProxy.setSourceModel(historyAbstractModel); + historyProxy.setFilterKeyColumn(0); + historyProxy.setFilterFixedString(QString::fromStdString(hash.GetHex())); + QModelIndex rowIndex = historyProxy.mapToSource(historyProxy.index(0,0)); // map to the row in the actual table + if(rowIndex.isValid()) ui->txHistoryTable->removeRow(rowIndex.row()); // delete the pending tx row, it'll be readded as a proper confirmed transaction + ui->txHistoryTable->setSortingEnabled(true); // re-enable sorting + } else { + continue; // the tx is in historyMap with a blockheight > 0, move on to next transaction + } + } + // tx not in historyMap, retrieve the transaction object + CTransaction wtx; + uint256 blockHash = 0; + if (!GetTransaction(hash, wtx, blockHash, true)) continue; + blockHash = pwtx->hashBlock; + if ((0 == blockHash) || (NULL == GetBlockIndex(blockHash))) continue; + CBlockIndex* pBlockIndex = GetBlockIndex(blockHash); + if (NULL == pBlockIndex) continue; + int blockHeight = pBlockIndex->nHeight; // get the height of the transaction + + // ### START STO INSERTION CHECK ### + for(uint32_t i = 0; i svstr; + boost::split(svstr, vecReceipts[i], boost::is_any_of(":"), boost::token_compress_on); + if(4 == svstr.size()) // make sure expected num items + { + if((atoi(svstr[1]) < lastTXBlock) && (atoi(svstr[1]) > blockHeight)) + { + // STO receipt insert here - add STO receipt to response array + uint256 stoHash; + stoHash.SetHex(svstr[0]); + // check historyMap, if this STO exists don't waste resources doing anymore work on it + HistoryMap::iterator hIterator = txHistoryMap.find(stoHash); + if (hIterator != txHistoryMap.end()) continue; + // STO not in historyMap, get details + uint64_t propertyId = 0; + try { propertyId = boost::lexical_cast(svstr[3]); } // attempt to extract propertyId + catch (const boost::bad_lexical_cast &e) { file_log("DEBUG STO - error in converting values from leveldb\n"); continue; } //(something went wrong) + Array receiveArray; + uint64_t total = 0; + uint64_t stoFee = 0; + s_stolistdb->getRecipients(stoHash, "", &receiveArray, &total, &stoFee); // get matching receipts + // create a HistoryTXObject and add to map + HistoryTXObject htxo; + htxo.blockHeight = atoi(svstr[1]); + htxo.blockByteOffset = 0; + CDiskTxPos position; + if (pblocktree->ReadTxIndex(stoHash, position)) { + htxo.blockByteOffset = position.nTxOffset; + } + htxo.txType = "STO Receive"; + htxo.valid = true; // STO receipts are always valid (STO sends not necessarily so, but this section only handles receipts) + htxo.address = svstr[2]; + htxo.fundsMoved = true; // receiving tokens means tokens moved + if(receiveArray.size()>1) htxo.address = "Multiple addresses"; // override display address if more than one address in the wallet received a cut of this STO + if(isPropertyDivisible(propertyId)) {htxo.amount=FormatDivisibleShortMP(total)+getTokenLabel(propertyId);} else {htxo.amount=FormatIndivisibleMP(total)+getTokenLabel(propertyId);} + txHistoryMap.insert(std::make_pair(stoHash, htxo)); + nProcessed += 1; // increase our counter so we don't go crazy on a wallet with too many transactions and lock up UI + } + } + } + lastTXBlock = blockHeight; + // ### END STO INSERTION CHECK ### + + // continuing with wallet tx, we've already confirmed earlier on that it's in txlistdb + string statusText; + unsigned int propertyId = 0; + uint64_t amount = 0; + string senderAddress; + string refAddress; + bool divisible = false; + bool valid = false; + string MPTxType; + CMPTransaction mp_obj; + int parseRC = parseTransaction(true, wtx, blockHeight, 0, &mp_obj); + string displayAmount; + string displayToken; + string displayValid; + string displayAddress; + string displayType; + if (0 < parseRC) { //positive RC means payment + // ### START HANDLE DEX PURCHASE TRANSACTION ### + string tmpBuyer; + string tmpSeller; + uint64_t total = 0; + uint64_t tmpVout = 0; + uint64_t tmpNValue = 0; + uint64_t tmpPropertyId = 0; + p_txlistdb->getPurchaseDetails(hash,1,&tmpBuyer,&tmpSeller,&tmpVout,&tmpPropertyId,&tmpNValue); + bool bIsBuy = IsMyAddress(tmpBuyer); + int numberOfPurchases=p_txlistdb->getNumberOfPurchases(hash); + if (0getPurchaseDetails(hash,purchaseNumber,&tmpBuyer,&tmpSeller,&tmpVout,&tmpPropertyId,&tmpNValue); + total += tmpNValue; + } + // create a HistoryTXObject and add to map + HistoryTXObject htxo; + htxo.blockHeight = blockHeight; + htxo.blockByteOffset = 0; // needs further investigation + if (!bIsBuy) { htxo.txType = "DEx Sell"; htxo.address = tmpSeller; } else { htxo.txType = "DEx Buy"; htxo.address = tmpBuyer; } + htxo.amount=(!bIsBuy ? "-" : "") + FormatDivisibleShortMP(total)+getTokenLabel(tmpPropertyId); + htxo.fundsMoved=true; + txHistoryMap.insert(std::make_pair(hash, htxo)); + nProcessed += 1; // increase our counter so we don't go crazy on a wallet with too many transactions and lock up UI + } + // ### END HANDLE DEX PURCHASE TRANSACTION ### + } + if (0 == parseRC) { //negative RC means no MP content/badly encoded TX, we shouldn't see this if TX in levelDB but check for sanity + // ### START HANDLE OMNI TRANSACTION ### + if (0<=mp_obj.step1()) { + MPTxType = mp_obj.getTypeString(); + senderAddress = mp_obj.getSender(); + refAddress = mp_obj.getReceiver(); + int tmpblock=0; + uint32_t tmptype=0; + uint64_t amountNew=0; + valid=getValidMPTX(hash, &tmpblock, &tmptype, &amountNew); + if (0 == mp_obj.step2_Value()) { + propertyId = mp_obj.getProperty(); + amount = mp_obj.getAmount(); + // special case for property creation (getProperty cannot get ID as createdID not stored in obj) + if (valid) { // we only generate an ID for valid creates + if ((mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_FIXED) || + (mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_VARIABLE) || + (mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_MANUAL)) { + propertyId = _my_sps->findSPByTX(hash); + if (mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_FIXED) { amount = getTotalTokens(propertyId); } else { amount = 0; } + } + } + divisible = isPropertyDivisible(propertyId); + } + } + bool fundsMoved = true; + displayType = shrinkTxType(mp_obj.getType(), &fundsMoved); // shrink tx type to better fit in column + if (IsMyAddress(senderAddress)) { displayAddress = senderAddress; } else { displayAddress = refAddress; } + if (divisible) { displayAmount = FormatDivisibleShortMP(amount)+getTokenLabel(propertyId); } else { displayAmount = FormatIndivisibleMP(amount)+getTokenLabel(propertyId); } + if ((displayType == "Send") && (!IsMyAddress(senderAddress))) { displayType = "Receive"; } // still a send transaction, but avoid confusion for end users + if (!valid) fundsMoved = false; // funds never move in invalid txs + // override/hide display amount for invalid creates and unknown transactions as we can't display amount/property as no prop exists + if ((mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_FIXED) || + (mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_VARIABLE) || + (mp_obj.getType() == MSC_TYPE_CREATE_PROPERTY_MANUAL) || + (displayType == "Unknown")) { + // alerts are valid and thus display a wacky value attempting to decode amount - whilst no users should ever see this, be clean and N/A the wacky value + if ((!valid) || (displayType == "Unknown")) { displayAmount = "N/A"; } + } else { if ((fundsMoved) && (IsMyAddress(senderAddress))) { displayAmount = "-" + displayAmount; } } + // create a HistoryTXObject and add to map + HistoryTXObject htxo; + htxo.blockHeight = blockHeight; + htxo.blockByteOffset = 0; + CDiskTxPos position; + if (pblocktree->ReadTxIndex(hash, position)) { + htxo.blockByteOffset = position.nTxOffset; + } + htxo.txType = displayType; + htxo.address = displayAddress; + htxo.valid = valid; // bool valid contains result from getValidMPTX + htxo.fundsMoved = fundsMoved; + htxo.amount = displayAmount; + txHistoryMap.insert(std::make_pair(hash, htxo)); + nProcessed += 1; // increase our counter so we don't go crazy on a wallet with too many transactions and lock up UI + // ### END HANDLE OMNI TRANSACTION ### + } + } + // display cap has been removed + } + + // ### END WALLET TRANSACTIONS PROCESSING ### + return nProcessed; +} + +void TXHistoryDialog::UpdateConfirmations() +{ + int chainHeight = chainActive.Height(); // get the chain height + int rowCount = ui->txHistoryTable->rowCount(); + for (int row = 0; row < rowCount; row++) { + int confirmations = 0; + bool valid = false; + uint256 txid; + txid.SetHex(ui->txHistoryTable->item(row,0)->text().toStdString()); + // lookup transaction in the map and grab validity and blockheight details + HistoryMap::iterator hIter = txHistoryMap.find(txid); + if (hIter != txHistoryMap.end()) { + HistoryTXObject *temphtxo = &(hIter->second); + if (temphtxo->blockHeight>0) confirmations = (chainHeight+1) - temphtxo->blockHeight; + valid = temphtxo->valid; + } + int txBlockHeight = ui->txHistoryTable->item(row,1)->text().toInt(); + if (txBlockHeight>0) confirmations = (chainHeight+1) - txBlockHeight; + // setup the appropriate icon + QIcon ic = QIcon(":/icons/transaction_0"); + switch(confirmations) { + case 1: ic = QIcon(":/icons/transaction_1"); break; + case 2: ic = QIcon(":/icons/transaction_2"); break; + case 3: ic = QIcon(":/icons/transaction_3"); break; + case 4: ic = QIcon(":/icons/transaction_4"); break; + case 5: ic = QIcon(":/icons/transaction_5"); break; + } + if (confirmations > 5) ic = QIcon(":/icons/transaction_confirmed"); + if (!valid) ic = QIcon(":/icons/transaction_invalid"); + QTableWidgetItem *iconCell = new QTableWidgetItem; + iconCell->setIcon(ic); + ui->txHistoryTable->setItem(row, 2, iconCell); + } +} + +void TXHistoryDialog::UpdateHistory() +{ + // now moved to a new methodology where historical transactions are stored in a map in memory (effectively a cache) so we can compare our + // history table against the cache. This allows us to avoid reparsing transactions repeatedly and provides a diff to modify the table without + // repopuplating all the rows top to bottom each refresh. + + // first things first, call PopulateHistoryMap to add in any missing (ie new) transactions + int newTXCount = PopulateHistoryMap(); + // were any transactions added? + if(newTXCount > 0) { // there are new transactions (or a pending shifted to confirmed), refresh the table adding any that are in the map but not in the table + ui->txHistoryTable->setSortingEnabled(false); // disable sorting temporarily while we update the table (leaving enabled gives unexpected results) + QAbstractItemModel* historyAbstractModel = ui->txHistoryTable->model(); // get a model to work with + for(HistoryMap::iterator it = txHistoryMap.begin(); it != txHistoryMap.end(); ++it) { + uint256 txid = it->first; // grab txid + // search the history table for this transaction, here we're going to use a filter proxy because it's a little quicker + QSortFilterProxyModel historyProxy; + historyProxy.setSourceModel(historyAbstractModel); + historyProxy.setFilterKeyColumn(0); + historyProxy.setFilterFixedString(QString::fromStdString(txid.GetHex())); + QModelIndex rowIndex = historyProxy.mapToSource(historyProxy.index(0,0)); + if(!rowIndex.isValid()) { // this transaction doesn't exist in the history table, add it + HistoryTXObject htxo = it->second; // grab the tranaaction + int workingRow = ui->txHistoryTable->rowCount(); + ui->txHistoryTable->insertRow(workingRow); // append a new row (sorting will take care of ordering) + QDateTime txTime; + QTableWidgetItem *dateCell = new QTableWidgetItem; + if (htxo.blockHeight>0) { + CBlockIndex* pBlkIdx = chainActive[htxo.blockHeight]; + if (NULL != pBlkIdx) txTime.setTime_t(pBlkIdx->GetBlockTime()); + dateCell->setData(Qt::DisplayRole, txTime); + } else { + dateCell->setData(Qt::DisplayRole, QString::fromStdString("Unconfirmed")); + } + QTableWidgetItem *typeCell = new QTableWidgetItem(QString::fromStdString(htxo.txType)); + QTableWidgetItem *addressCell = new QTableWidgetItem(QString::fromStdString(htxo.address)); + QTableWidgetItem *amountCell = new QTableWidgetItem(QString::fromStdString(htxo.amount)); + QTableWidgetItem *iconCell = new QTableWidgetItem; + QTableWidgetItem *txidCell = new QTableWidgetItem(QString::fromStdString(txid.GetHex())); + std::string sortKey = strprintf("%06d%010d",htxo.blockHeight,htxo.blockByteOffset); + if(htxo.blockHeight == 0) sortKey = strprintf("%06d%010D",999999,htxo.blockByteOffset); // spoof the hidden value to ensure pending txs are sorted top + QTableWidgetItem *sortKeyCell = new QTableWidgetItem(QString::fromStdString(sortKey)); + addressCell->setTextAlignment(Qt::AlignLeft + Qt::AlignVCenter); + addressCell->setForeground(QColor("#707070")); + amountCell->setTextAlignment(Qt::AlignRight + Qt::AlignVCenter); + amountCell->setForeground(QColor("#00AA00")); + if (htxo.amount.length() > 0) { // protect against an empty value + if (htxo.amount.substr(0,1) == "-") amountCell->setForeground(QColor("#EE0000")); // outbound + } + if (!htxo.fundsMoved) amountCell->setForeground(QColor("#404040")); + ui->txHistoryTable->setItem(workingRow, 0, txidCell); + ui->txHistoryTable->setItem(workingRow, 1, sortKeyCell); + ui->txHistoryTable->setItem(workingRow, 2, iconCell); + ui->txHistoryTable->setItem(workingRow, 3, dateCell); + ui->txHistoryTable->setItem(workingRow, 4, typeCell); + ui->txHistoryTable->setItem(workingRow, 5, addressCell); + ui->txHistoryTable->setItem(workingRow, 6, amountCell); + } + } + ui->txHistoryTable->setSortingEnabled(true); // re-enable sorting + } + UpdateConfirmations(); +} + +void TXHistoryDialog::contextualMenu(const QPoint &point) +{ + QModelIndex index = ui->txHistoryTable->indexAt(point); + if(index.isValid()) + { + contextMenu->exec(QCursor::pos()); + } +} + +void TXHistoryDialog::copyAddress() +{ + GUIUtil::setClipboard(ui->txHistoryTable->item(ui->txHistoryTable->currentRow(),5)->text()); +} + +void TXHistoryDialog::copyAmount() +{ + GUIUtil::setClipboard(ui->txHistoryTable->item(ui->txHistoryTable->currentRow(),6)->text()); +} + +void TXHistoryDialog::copyTxID() +{ + GUIUtil::setClipboard(ui->txHistoryTable->item(ui->txHistoryTable->currentRow(),0)->text()); +} + +void TXHistoryDialog::checkSort(int column) +{ + // a header has been clicked on the tx history table, see if it's the status column and override the sort if necessary + // we may wish to be a bit smarter about this longer term, so we can spoof a sort indicator display and perhaps allow both + // directions, but for now will provide the core functionality needed + if (column == 2) { // ignore any other column sorts and allow them to progress normally + ui->txHistoryTable->horizontalHeader()->setSortIndicator(1, Qt::DescendingOrder); + } +} + +void TXHistoryDialog::showDetails() +{ + Object txobj; + uint256 txid; + txid.SetHex(ui->txHistoryTable->item(ui->txHistoryTable->currentRow(),0)->text().toStdString()); + std::string strTXText; + + // first of all check if the TX is a pending tx, if so grab details from pending map + PendingMap::iterator it = my_pending.find(txid); + if (it != my_pending.end()) { + CMPPending *p_pending = &(it->second); + strTXText = "*** THIS TRANSACTION IS UNCONFIRMED ***\n" + p_pending->desc; + } else { + // grab details usual way + int pop = populateRPCTransactionObject(txid, &txobj, ""); + if (0<=pop) { + strTXText = write_string(Value(txobj), true); + // manipulate for STO if needed + size_t pos = strTXText.find("Send To Owners"); + if (pos!=std::string::npos) { + Array receiveArray; + uint64_t tmpAmount = 0; + uint64_t tmpSTOFee = 0; + s_stolistdb->getRecipients(txid, "", &receiveArray, &tmpAmount, &tmpSTOFee); + txobj.push_back(Pair("recipients", receiveArray)); + //rewrite string + strTXText = write_string(Value(txobj), true); + } + } + } + if (!strTXText.empty()) { + PopulateSimpleDialog(strTXText, "Transaction Information", "Transaction Information"); + } +} + +void TXHistoryDialog::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + borrowedColumnResizingFixer->stretchColumnWidth(5); +} + +std::string TXHistoryDialog::shrinkTxType(int txType, bool *fundsMoved) +{ + string displayType = "Unknown"; + switch (txType) { + case MSC_TYPE_SIMPLE_SEND: displayType = "Send"; break; + case MSC_TYPE_RESTRICTED_SEND: displayType = "Rest. Send"; break; + case MSC_TYPE_SEND_TO_OWNERS: displayType = "Send To Owners"; break; + case MSC_TYPE_SAVINGS_MARK: displayType = "Mark Savings"; *fundsMoved = false; break; + case MSC_TYPE_SAVINGS_COMPROMISED: ; displayType = "Lock Savings"; break; + case MSC_TYPE_RATELIMITED_MARK: displayType = "Rate Limit"; break; + case MSC_TYPE_AUTOMATIC_DISPENSARY: displayType = "Auto Dispense"; break; + case MSC_TYPE_TRADE_OFFER: displayType = "DEx Trade"; *fundsMoved = false; break; + case MSC_TYPE_METADEX: displayType = "MetaDEx Trade"; *fundsMoved = false; break; + case MSC_TYPE_ACCEPT_OFFER_BTC: displayType = "DEx Accept"; *fundsMoved = false; break; + case MSC_TYPE_CREATE_PROPERTY_FIXED: displayType = "Create Property"; break; + case MSC_TYPE_CREATE_PROPERTY_VARIABLE: displayType = "Create Property"; break; + case MSC_TYPE_PROMOTE_PROPERTY: displayType = "Promo Property"; break; + case MSC_TYPE_CLOSE_CROWDSALE: displayType = "Close Crowdsale"; break; + case MSC_TYPE_CREATE_PROPERTY_MANUAL: displayType = "Create Property"; break; + case MSC_TYPE_GRANT_PROPERTY_TOKENS: displayType = "Grant Tokens"; break; + case MSC_TYPE_REVOKE_PROPERTY_TOKENS: displayType = "Revoke Tokens"; break; + case MSC_TYPE_CHANGE_ISSUER_ADDRESS: displayType = "Change Issuer"; *fundsMoved = false; break; + } + return displayType; +} diff --git a/src/qt/txhistorydialog.h b/src/qt/txhistorydialog.h new file mode 100644 index 0000000000000..93aae329e8b40 --- /dev/null +++ b/src/qt/txhistorydialog.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef TXHISTORYDIALOG_H +#define TXHISTORYDIALOG_H + +#include "guiutil.h" +#include "uint256.h" + +#include + +#include + +class ClientModel; +class WalletModel; + +QT_BEGIN_NAMESPACE +class QMenu; +class QModelIndex; +class QPoint; +class QResizeEvent; +class QString; +class QWidget; +QT_END_NAMESPACE + +namespace Ui { + class txHistoryDialog; +} + +class HistoryTXObject +{ +public: + HistoryTXObject() + : blockHeight(-1), blockByteOffset(0), valid(false), fundsMoved(true) {}; + int blockHeight; // block transaction was mined in + int blockByteOffset; // byte offset the tx is stored in the block (used for ordering multiple txs same block) + bool valid; // whether the transaction is valid from an Omni perspective + bool fundsMoved; // whether tokens actually moved in this transaction + std::string txType; // human readable string containing type + std::string address; // the address to be displayed (usually sender or recipient) + std::string amount; // string containing formatted amount +}; + +typedef std::map HistoryMap; + +/** Dialog for looking up Master Protocol tokens */ +class TXHistoryDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TXHistoryDialog(QWidget *parent = 0); + ~TXHistoryDialog(); + + void setClientModel(ClientModel *model); + void setWalletModel(WalletModel *model); + + virtual void resizeEvent(QResizeEvent* event); + std::string shrinkTxType(int txType, bool *fundsMoved); + +private: + Ui::txHistoryDialog *ui; + ClientModel *clientModel; + WalletModel *walletModel; + GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; + QMenu *contextMenu; + HistoryMap txHistoryMap; + +private slots: + void contextualMenu(const QPoint &point); + void showDetails(); + void copyAddress(); + void copyAmount(); + void copyTxID(); + void UpdateHistory(); + int PopulateHistoryMap(); + void UpdateConfirmations(); + void checkSort(int column); + +signals: + void doubleClicked(const QModelIndex& idx); + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); +}; + +#endif // TXHISTORYDIALOG_H diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index fead022928562..617fd510e0610 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -112,6 +112,20 @@ void WalletFrame::gotoOverviewPage() i.value()->gotoOverviewPage(); } +void WalletFrame::gotoBalancesPage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoBalancesPage(); +} + +void WalletFrame::gotoExchangePage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoExchangePage(); +} + void WalletFrame::gotoHistoryPage() { QMap::const_iterator i; @@ -119,6 +133,20 @@ void WalletFrame::gotoHistoryPage() i.value()->gotoHistoryPage(); } +void WalletFrame::gotoBitcoinHistoryTab() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoBitcoinHistoryTab(); +} + +void WalletFrame::gotoToolboxPage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoToolboxPage(); +} + void WalletFrame::gotoReceiveCoinsPage() { QMap::const_iterator i; diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index ae8592840d51d..6105451660bbc 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -50,8 +50,16 @@ class WalletFrame : public QFrame public slots: /** Switch to overview (home) page */ void gotoOverviewPage(); + /** Switch to balances page */ + void gotoBalancesPage(); + /** Switch to exchange page */ + void gotoExchangePage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch directory to bitcoin tx history tab */ + void gotoBitcoinHistoryTab(); + /** Switch to utility page */ + void gotoToolboxPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 9bab180108c4d..695b1e488a3da 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -13,11 +13,19 @@ #include "overviewpage.h" #include "receivecoinsdialog.h" #include "sendcoinsdialog.h" +#include "sendmpdialog.h" +#include "lookupspdialog.h" +#include "lookuptxdialog.h" +#include "lookupaddressdialog.h" +#include "metadexcanceldialog.h" +#include "metadexdialog.h" #include "signverifymessagedialog.h" #include "transactiontablemodel.h" #include "transactionview.h" +#include "balancesdialog.h" #include "walletmodel.h" - +#include "tradehistorydialog.h" +#include "txhistorydialog.h" #include "ui_interface.h" #include @@ -28,6 +36,11 @@ #include #include +#include +#include +#include +#include + WalletView::WalletView(QWidget *parent): QStackedWidget(parent), clientModel(0), @@ -35,8 +48,13 @@ WalletView::WalletView(QWidget *parent): { // Create tabs overviewPage = new OverviewPage(); - transactionsPage = new QWidget(this); + balancesPage = new BalancesDialog(); + + // transactions page + // bitcoin transactions in second tab, MP transactions in first + //bitcoin + bitcoinTXTab = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(); QHBoxLayout *hbox_buttons = new QHBoxLayout(); transactionView = new TransactionView(this); @@ -49,15 +67,62 @@ WalletView::WalletView(QWidget *parent): hbox_buttons->addStretch(); hbox_buttons->addWidget(exportButton); vbox->addLayout(hbox_buttons); - transactionsPage->setLayout(vbox); - + bitcoinTXTab->setLayout(vbox); + mpTXTab = new TXHistoryDialog; + transactionsPage = new QWidget(this); + QVBoxLayout *txvbox = new QVBoxLayout(); + txTabHolder = new QTabWidget(); + txTabHolder->addTab(mpTXTab,tr("Omni Layer")); + txTabHolder->addTab(bitcoinTXTab,tr("Bitcoin")); + txvbox->addWidget(txTabHolder); + transactionsPage->setLayout(txvbox); + // receive page receiveCoinsPage = new ReceiveCoinsDialog(); - sendCoinsPage = new SendCoinsDialog(); - + // sending page + sendCoinsPage = new QWidget(this); + QVBoxLayout *svbox = new QVBoxLayout(); + sendCoinsTab = new SendCoinsDialog(); + sendMPTab = new SendMPDialog(); + QTabWidget *tabHolder = new QTabWidget(); + tabHolder->addTab(sendMPTab,tr("Omni Layer")); + tabHolder->addTab(sendCoinsTab,tr("Bitcoin")); + svbox->addWidget(tabHolder); + sendCoinsPage->setLayout(svbox); + // exchange page + exchangePage = new QWidget(this); + QVBoxLayout *exvbox = new QVBoxLayout(); + metaDExTab = new MetaDExDialog(); + cancelTab = new MetaDExCancelDialog(); + QTabWidget *exTabHolder = new QTabWidget(); + tradeHistoryTab = new TradeHistoryDialog; + //exTabHolder->addTab(new QWidget(),tr("Trade Bitcoin/Mastercoin")); not yet implemented + exTabHolder->addTab(metaDExTab,tr("Trade Mastercoin/Smart Properties")); + exTabHolder->addTab(tradeHistoryTab,tr("Trade History")); + exTabHolder->addTab(cancelTab,tr("Cancel Orders")); + exvbox->addWidget(exTabHolder); + exchangePage->setLayout(exvbox); + + // toolbox page + toolboxPage = new QWidget(this); + QVBoxLayout *tvbox = new QVBoxLayout(); + addressLookupTab = new LookupAddressDialog(); + spLookupTab = new LookupSPDialog(); + txLookupTab = new LookupTXDialog(); + QTabWidget *tTabHolder = new QTabWidget(); + tTabHolder->addTab(addressLookupTab,tr("Lookup Address")); + tTabHolder->addTab(spLookupTab,tr("Lookup Property")); + tTabHolder->addTab(txLookupTab,tr("Lookup Transaction")); + tvbox->addWidget(tTabHolder); + toolboxPage->setLayout(tvbox); + + // add pages addWidget(overviewPage); + addWidget(balancesPage); addWidget(transactionsPage); addWidget(receiveCoinsPage); addWidget(sendCoinsPage); + addWidget(exchangePage); + addWidget(toolboxPage); // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); @@ -82,8 +147,8 @@ void WalletView::setBitcoinGUI(BitcoinGUI *gui) { if (gui) { - // Clicking on a transaction on the overview page simply sends you to transaction history page - connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), gui, SLOT(gotoHistoryPage())); + // Clicking on a transaction on the overview page simply sends you to the Bitcoin history tab + connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), gui, SLOT(gotoBitcoinHistoryTab())); // Receive and report messages connect(this, SIGNAL(message(QString,QString,unsigned int)), gui, SLOT(message(QString,QString,unsigned int))); @@ -101,7 +166,10 @@ void WalletView::setClientModel(ClientModel *clientModel) this->clientModel = clientModel; overviewPage->setClientModel(clientModel); - sendCoinsPage->setClientModel(clientModel); + balancesPage->setClientModel(clientModel); + sendMPTab->setClientModel(clientModel); + mpTXTab->setClientModel(clientModel); + cancelTab->setClientModel(clientModel); } void WalletView::setWalletModel(WalletModel *walletModel) @@ -112,7 +180,13 @@ void WalletView::setWalletModel(WalletModel *walletModel) transactionView->setModel(walletModel); overviewPage->setWalletModel(walletModel); receiveCoinsPage->setModel(walletModel); - sendCoinsPage->setModel(walletModel); + sendCoinsTab->setModel(walletModel); + sendMPTab->setWalletModel(walletModel); + balancesPage->setWalletModel(walletModel); + metaDExTab->setModel(walletModel); + mpTXTab->setWalletModel(walletModel); + tradeHistoryTab->setModel(walletModel); + cancelTab->setWalletModel(walletModel); if (walletModel) { @@ -158,22 +232,43 @@ void WalletView::gotoOverviewPage() setCurrentWidget(overviewPage); } +void WalletView::gotoBalancesPage() +{ + setCurrentWidget(balancesPage); +} + void WalletView::gotoHistoryPage() { setCurrentWidget(transactionsPage); } +void WalletView::gotoBitcoinHistoryTab() +{ + setCurrentWidget(transactionsPage); + txTabHolder->setCurrentIndex(1); +} + void WalletView::gotoReceiveCoinsPage() { setCurrentWidget(receiveCoinsPage); } +void WalletView::gotoExchangePage() +{ + setCurrentWidget(exchangePage); +} + +void WalletView::gotoToolboxPage() +{ + setCurrentWidget(toolboxPage); +} + void WalletView::gotoSendCoinsPage(QString addr) { setCurrentWidget(sendCoinsPage); if (!addr.isEmpty()) - sendCoinsPage->setAddress(addr); + sendCoinsTab->setAddress(addr); } void WalletView::gotoSignMessageTab(QString addr) @@ -202,7 +297,7 @@ void WalletView::gotoVerifyMessageTab(QString addr) bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient) { - return sendCoinsPage->handlePaymentRequest(recipient); + return sendCoinsTab->handlePaymentRequest(recipient); } void WalletView::showOutOfSyncWarning(bool fShow) diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 95890ccd67e59..0447f37cc40b2 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -14,13 +14,23 @@ class ClientModel; class OverviewPage; class ReceiveCoinsDialog; class SendCoinsDialog; +class SendMPDialog; +class TradeHistoryDialog; +class LookupSPDialog; +class LookupTXDialog; +class LookupAddressDialog; +class MetaDExDialog; +class MetaDExCancelDialog; class SendCoinsRecipient; class TransactionView; +class TXHistoryDialog; +class BalancesDialog; class WalletModel; QT_BEGIN_NAMESPACE class QModelIndex; class QProgressDialog; +class QTabWidget; QT_END_NAMESPACE /* @@ -57,24 +67,46 @@ class WalletView : public QStackedWidget WalletModel *walletModel; OverviewPage *overviewPage; + BalancesDialog *balancesPage; QWidget *transactionsPage; - ReceiveCoinsDialog *receiveCoinsPage; - SendCoinsDialog *sendCoinsPage; + QWidget *exchangePage; + QWidget *smartPropertyPage; + QWidget *toolboxPage; + ReceiveCoinsDialog *receiveCoinsPage; +// SendCoinsDialog *sendCoinsPage; + QWidget *sendCoinsPage; + SendCoinsDialog *sendCoinsTab; + SendMPDialog *sendMPTab; + LookupSPDialog *spLookupTab; + LookupTXDialog *txLookupTab; + LookupAddressDialog *addressLookupTab; + TradeHistoryDialog *tradeHistoryTab; + MetaDExDialog *metaDExTab; + MetaDExCancelDialog *cancelTab; TransactionView *transactionView; - + TXHistoryDialog *mpTXTab; + QWidget *bitcoinTXTab; QProgressDialog *progressDialog; + QTabWidget *txTabHolder; public slots: /** Switch to overview (home) page */ void gotoOverviewPage(); + /** Switch to balances page */ + void gotoBalancesPage(); + /** Switch to exchange page */ + void gotoExchangePage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ void gotoSendCoinsPage(QString addr = ""); - + /** Switch specifically to bitcoin tx history tab */ + void gotoBitcoinHistoryTab(); + /** Switch to utility page */ + void gotoToolboxPage(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = ""); /** Show Sign/Verify Message dialog and switch to verify message tab */