diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index dbcd0f493..b73a2cd0d 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1475,7 +1475,7 @@ void Main::populateDebugger(dev::bytesConstRef _r) m_history.append(WorldState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), lastHash, lastDataHash, vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); }; m_currentExecution->go(onOp); - m_currentExecution->finalize(onOp); + m_currentExecution->finalize(); initDebugger(); updateDebugger(); } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 5c196792d..ed5d275eb 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -78,6 +78,8 @@ public: dev::eth::Client* ethereum() const { return m_webThree->ethereum(); } std::shared_ptr whisper() const { return m_webThree->whisper(); } + std::string lookupNatSpec(dev::h256 const& _contractCode) const { (void)_contractCode; return ""; } // TODO: actually implement with leveldb & a UI. + QList owned() const { return m_myIdentities + m_myKeys; } public slots: diff --git a/alethzero/OurWebThreeStubServer.cpp b/alethzero/OurWebThreeStubServer.cpp index 0c6f42b5a..0d840e8d4 100644 --- a/alethzero/OurWebThreeStubServer.cpp +++ b/alethzero/OurWebThreeStubServer.cpp @@ -20,12 +20,16 @@ */ #include "OurWebThreeStubServer.h" + +#include +#include +#include "MainWin.h" using namespace std; using namespace dev; using namespace dev::eth; OurWebThreeStubServer::OurWebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, dev::WebThreeDirect& _web3, std::vector const& _accounts): - WebThreeStubServer(_conn, _web3, _accounts) + WebThreeStubServer(_conn, _web3, _accounts), m_web3(&_web3) {} std::string OurWebThreeStubServer::shh_newIdentity() @@ -34,3 +38,37 @@ std::string OurWebThreeStubServer::shh_newIdentity() emit onNewId(QString::fromStdString(toJS(kp.sec()))); return toJS(kp.pub()); } + +bool OurWebThreeStubServer::authenticate(dev::TransactionSkeleton const& _t) const +{ + return true; + + // To get the balance of the sender + cnote << "Sender has ETH: " << m_web3->ethereum()->postState().balance(_t.from); + + Main* main; // don't know this yet, should be a member and set at construction time by Main, who will construct us. + + h256 contractCodeHash = m_web3->ethereum()->postState().codeHash(_t.to); + + if (contractCodeHash == EmptySHA3) + { + // recipient has no code - nothing special about this transaction. + // TODO: show basic message for value transfer. + return true; // or whatever. + } + + std::string natspecJson = main->lookupNatSpec(contractCodeHash); + + if (natspecJson.empty()) + { + // TODO: HUGE warning - we don't know what this will do! + return false; // or whatever. + } + + // otherwise it's a transaction to contract for which we have the natspec: + // determine the actual message (embellish with real data) and ask user. + +// QMessageBox::question(); + + return true; +} diff --git a/alethzero/OurWebThreeStubServer.h b/alethzero/OurWebThreeStubServer.h index fb026d07e..9ff973371 100644 --- a/alethzero/OurWebThreeStubServer.h +++ b/alethzero/OurWebThreeStubServer.h @@ -32,7 +32,11 @@ public: OurWebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, dev::WebThreeDirect& _web3, std::vector const& _accounts); virtual std::string shh_newIdentity() override; + virtual bool authenticate(dev::TransactionSkeleton const& _t) const; signals: void onNewId(QString _s); + +private: + dev::WebThreeDirect* m_web3; }; diff --git a/eth/main.cpp b/eth/main.cpp index d55766cfd..c6435235e 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -672,7 +672,7 @@ int main(int argc, char** argv) f << ext->myAddress << " " << hex << toHex(dev::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; }; e.go(oof); - e.finalize(oof); + e.finalize(); } catch(Exception const& _e) { diff --git a/libdevcrypto/CryptoPP.cpp b/libdevcrypto/CryptoPP.cpp index 766ca485d..d73e3fa43 100644 --- a/libdevcrypto/CryptoPP.cpp +++ b/libdevcrypto/CryptoPP.cpp @@ -108,7 +108,7 @@ Signature Secp256k1::sign(Secret const& _key, h256 const& _hash) Integer kInv = k.InverseMod(m_q); Integer z(_hash.asBytes().data(), 32); - Integer s = (kInv * (Integer(_key.asBytes().data(), 32)*r + z)) % m_q; + Integer s = (kInv * (Integer(_key.asBytes().data(), 32) * r + z)) % m_q; if (r == 0 || s == 0) BOOST_THROW_EXCEPTION(InvalidState()); @@ -144,7 +144,7 @@ Public Secp256k1::recover(Signature _signature, bytesConstRef _message) Integer s(_signature.data()+32, 32); // cryptopp encodes sign of y as 0x02/0x03 instead of 0/1 or 27/28 byte encodedpoint[33]; - encodedpoint[0] = _signature[64]|2; + encodedpoint[0] = _signature[64] | 2; memcpy(&encodedpoint[1], _signature.data(), 32); ECP::Element x; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index b204546d4..0901766bf 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -317,14 +317,6 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) disable("Blacklisted client version."); else if (host()->isBanned(session()->id())) disable("Peer banned for previous bad behaviour."); - else - { - // Grab transactions off them. - RLPStream s; - prep(s, GetTransactionsPacket); - sealAndSend(s); - transition(Asking::Nothing); - } break; } case GetTransactionsPacket: break; // DEPRECATED. diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 965d6f0af..f79bb4a3b 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -220,7 +220,7 @@ bool Executive::go(OnOpFunc const& _onOp) return true; } -void Executive::finalize(OnOpFunc const&) +void Executive::finalize() { // SSTORE refunds... // must be done before the miner gets the fees. diff --git a/libethereum/Executive.h b/libethereum/Executive.h index 5a97e5f87..d743e3746 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -64,7 +64,7 @@ public: bool setup(bytesConstRef _transaction); /// Finalise a transaction previously set up with setup(). /// @warning Only valid after setup(), and possibly go(). - void finalize(OnOpFunc const& _onOp = OnOpFunc()); + void finalize(); /// @returns the transaction from setup(). /// @warning Only valid after setup(). Transaction const& t() const { return m_t; } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index dfd65c713..c6a6ce125 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -958,6 +958,13 @@ bytes const& State::code(Address _contract) const return m_cache[_contract].code(); } +h256 State::codeHash(Address _contract) const +{ + if (!addressHasCode(_contract)) + return EmptySHA3; + return m_cache[_contract].codeHash(); +} + bool State::isTrieGood(bool _enforceRefs, bool _requireNoLeftOvers) const { for (int e = 0; e < (_enforceRefs ? 2 : 1); ++e) diff --git a/libethereum/State.h b/libethereum/State.h index 0473893c4..921c82bb9 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -201,6 +201,10 @@ public: /// @returns bytes() if no account exists at that address. bytes const& code(Address _contract) const; + /// Get the code hash of an account. + /// @returns EmptySHA3 if no account exists at that address or if there is no code associated with the address. + h256 codeHash(Address _contract) const; + /// Note that the given address is sending a transaction and thus increment the associated ticker. void noteSending(Address _id); diff --git a/libweb3jsonrpc/WebThreeStubServer.cpp b/libweb3jsonrpc/WebThreeStubServer.cpp index 6cba16f84..874c14331 100644 --- a/libweb3jsonrpc/WebThreeStubServer.cpp +++ b/libweb3jsonrpc/WebThreeStubServer.cpp @@ -644,16 +644,24 @@ std::string WebThreeStubServer::eth_transact(Json::Value const& _json) t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); - cwarn << "Silently signing transaction from address" << t.from.abridged() << ": User validation hook goes here."; - if (t.to) - // TODO: from qethereum, insert validification hook here. - client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); - else - ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); - client()->flushTransactions(); + if (authenticate(t)) + { + if (t.to) + // TODO: from qethereum, insert validification hook here. + client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); + else + ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); + client()->flushTransactions(); + } return ret; } +bool WebThreeStubServer::authenticate(TransactionSkeleton const& _t) const +{ + cwarn << "Silently signing transaction from address" << _t.from.abridged() << ": User validation hook goes here."; + return true; +} + Json::Value WebThreeStubServer::eth_transactionByHash(std::string const& _hash, int const& _i) { return toJson(client()->transaction(jsToFixed<32>(_hash), _i)); diff --git a/libweb3jsonrpc/WebThreeStubServer.h b/libweb3jsonrpc/WebThreeStubServer.h index 6d54c59ef..0f81fce9d 100644 --- a/libweb3jsonrpc/WebThreeStubServer.h +++ b/libweb3jsonrpc/WebThreeStubServer.h @@ -42,6 +42,7 @@ namespace dev { class WebThreeDirect; class KeyPair; +class TransactionSkeleton; namespace eth { class Interface; @@ -119,12 +120,15 @@ public: void setIdentities(std::vector const& _ids); std::map const& ids() const { return m_ids; } +protected: + virtual bool authenticate(dev::TransactionSkeleton const& _t) const; + private: dev::eth::Interface* client() const; std::shared_ptr face() const; dev::WebThreeDirect& m_web3; std::map m_accounts; - + ldb::ReadOptions m_readOptions; ldb::WriteOptions m_writeOptions; ldb::DB* m_db; diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 6c48e9be1..ff2d10d01 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -25,55 +25,53 @@ #include #include #include +#include #include -#include +#include +#include +#include #include -#include -#include "KeyEventManager.h" +#include #include "AppContext.h" +#include "CodeModel.h" + using namespace dev; using namespace dev::eth; -using namespace dev::solidity; using namespace dev::mix; -AppContext* AppContext::Instance = nullptr; +const QString c_projectFileName = "project.mix"; AppContext::AppContext(QQmlApplicationEngine* _engine) { - m_applicationEngine = std::unique_ptr(_engine); - m_keyEventManager = std::unique_ptr(new KeyEventManager()); - m_webThree = std::unique_ptr(new WebThreeDirect(std::string("Mix/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/Mix", false, {"eth", "shh"})); - m_compiler = std::unique_ptr(new CompilerStack()); //TODO : to move in a codel model structure. -} - -QQmlApplicationEngine* AppContext::appEngine() -{ - return m_applicationEngine.get(); + m_applicationEngine = _engine; + //m_webThree = std::unique_ptr(new WebThreeDirect(std::string("Mix/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/Mix", false, {"eth", "shh"})); + m_codeModel = std::unique_ptr(new CodeModel(this)); + m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get()); + m_applicationEngine->rootContext()->setContextProperty("appContext", this); } -void AppContext::initKeyEventManager(QObject* _res) +AppContext::~AppContext() { - QObject* mainContent = _res->findChild("mainContent", Qt::FindChildrenRecursively); - if (mainContent) - QObject::connect(mainContent, SIGNAL(keyPressed(QVariant)), m_keyEventManager.get(), SLOT(keyPressed(QVariant))); - else - qDebug() << "Unable to find QObject of mainContent.qml. KeyEvent will not be handled!"; } -KeyEventManager* AppContext::getKeyEventManager() +void AppContext::loadProject() { - return m_keyEventManager.get(); + QString path = QStandardPaths::locate(QStandardPaths::DataLocation, c_projectFileName); + if (!path.isEmpty()) + { + QFile file(path); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream stream(&file); + QString json = stream.readAll(); + emit projectLoaded(json); + } + } } -CompilerStack* AppContext::compiler() -{ - return m_compiler.get(); -} - -void AppContext::setApplicationContext(QQmlApplicationEngine* _engine) +QQmlApplicationEngine* AppContext::appEngine() { - if (Instance == nullptr) - Instance = new AppContext(_engine); + return m_applicationEngine; } void AppContext::displayMessageDialog(QString _title, QString _message) @@ -88,3 +86,19 @@ void AppContext::displayMessageDialog(QString _title, QString _message) dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); QMetaObject::invokeMethod(dialogWin, "open"); } + +void AppContext::saveProject(QString const& _json) +{ + QDir dirPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + QString path = QDir(dirPath).filePath(c_projectFileName); + if (!path.isEmpty()) + { + dirPath.mkpath(dirPath.path()); + QFile file(path); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream stream(&file); + stream << _json; + } + } +} diff --git a/mix/AppContext.h b/mix/AppContext.h index 4419633ed..7435c7777 100644 --- a/mix/AppContext.h +++ b/mix/AppContext.h @@ -28,11 +28,10 @@ #pragma once #include -#include -#include -#include -#include "KeyEventManager.h" +#include +#include +class QQmlApplicationEngine; namespace dev { class WebThreeDirect; @@ -47,42 +46,39 @@ namespace dev namespace mix { +class CodeModel; /** * @brief Provides access to application scope variable. */ + class AppContext: public QObject { Q_OBJECT public: AppContext(QQmlApplicationEngine* _engine); - /// Get the current QQmlApplicationEngine instance. - static AppContext* getInstance() { return Instance; } - /// Renew QQMLApplicationEngine with a new instance. - static void setApplicationContext(QQmlApplicationEngine* _engine); + virtual ~AppContext(); /// Get the current QQMLApplicationEngine instance. QQmlApplicationEngine* appEngine(); - /// Initialize KeyEventManager (used to handle key pressed event). - void initKeyEventManager(QObject* _obj); - /// Get the current KeyEventManager instance. - KeyEventManager* getKeyEventManager(); - /// Get the current Compiler instance (used to parse and compile contract code). - dev::solidity::CompilerStack* compiler(); + /// Get code model + CodeModel* codeModel() { return m_codeModel.get(); } /// Display an alert message. void displayMessageDialog(QString _title, QString _message); + /// Load project settings + void loadProject(); +signals: + void projectLoaded(QString const& _json); private: - static AppContext* Instance; - std::unique_ptr m_applicationEngine; + QQmlApplicationEngine* m_applicationEngine; //owned by app std::unique_ptr m_webThree; - std::unique_ptr m_keyEventManager; - std::unique_ptr m_compiler; + std::unique_ptr m_codeModel; public slots: /// Delete the current instance when application quit. - void quitApplication() { delete Instance; } - /// Initialize components after the loading of the main QML view. - void resourceLoaded(QObject* _obj, QUrl _url) { Q_UNUSED(_url); initKeyEventManager(_obj); } + void quitApplication() {} + /// Write json to a settings file + void saveProject(QString const& _json); }; } diff --git a/mix/AssemblyDebuggerControl.cpp b/mix/AssemblyDebuggerControl.cpp index a3270fed7..136353dc6 100644 --- a/mix/AssemblyDebuggerControl.cpp +++ b/mix/AssemblyDebuggerControl.cpp @@ -19,24 +19,39 @@ #include #include -#include #include +#include #include #include #include #include "AssemblyDebuggerModel.h" #include "AssemblyDebuggerControl.h" -#include "KeyEventManager.h" #include "AppContext.h" #include "DebuggingStateWrapper.h" -#include "TransactionListModel.h" #include "QContractDefinition.h" #include "QVariableDeclaration.h" #include "ContractCallDataEncoder.h" +#include "CodeModel.h" + using namespace dev::eth; using namespace dev::mix; -AssemblyDebuggerControl::AssemblyDebuggerControl(QTextDocument* _doc): Extension(ExtensionDisplayBehavior::ModalDialog) +/// @todo Move this to QML +dev::u256 fromQString(QString const& _s) +{ + return dev::jsToU256(_s.toStdString()); +} + +/// @todo Move this to QML +QString toQString(dev::u256 _value) +{ + std::ostringstream s; + s << _value; + return QString::fromStdString(s.str()); +} + +AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context): + Extension(_context, ExtensionDisplayBehavior::ModalDialog), m_running(false) { qRegisterMetaType("QVariableDefinition*"); qRegisterMetaType("QVariableDefinitionList*"); @@ -44,14 +59,11 @@ AssemblyDebuggerControl::AssemblyDebuggerControl(QTextDocument* _doc): Extension qRegisterMetaType>("QList"); qRegisterMetaType("QVariableDeclaration*"); qRegisterMetaType("AssemblyDebuggerData"); - qRegisterMetaType("DebuggingStatusResult"); - - connect(this, SIGNAL(dataAvailable(bool, DebuggingStatusResult, QList, QList, AssemblyDebuggerData)), - this, SLOT(updateGUI(bool, DebuggingStatusResult, QList, QList, AssemblyDebuggerData)), Qt::QueuedConnection); + connect(this, &AssemblyDebuggerControl::dataAvailable, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection); m_modelDebugger = std::unique_ptr(new AssemblyDebuggerModel); - m_compilation = std::unique_ptr(new ConstantCompilationModel); - m_doc = _doc; + + _context->appEngine()->rootContext()->setContextProperty("debugModel", this); } QString AssemblyDebuggerControl::contentUrl() const @@ -61,111 +73,139 @@ QString AssemblyDebuggerControl::contentUrl() const QString AssemblyDebuggerControl::title() const { - return QApplication::tr("debugger"); + return QApplication::tr("Debugger"); } void AssemblyDebuggerControl::start() const { - //start to listen on F5 - m_ctx->getKeyEventManager()->registerEvent(this, SLOT(keyPressed(int))); } -void AssemblyDebuggerControl::keyPressed(int _key) +void AssemblyDebuggerControl::debugDeployment() { - if (_key == Qt::Key_F5) - { - QtConcurrent::run([this]() - { - deployContract(m_doc->toPlainText()); - }); - } - else if (_key == Qt::Key_F6) + executeSequence(std::vector(), 0); +} + +void AssemblyDebuggerControl::debugState(QVariantMap _state) +{ + u256 balance = fromQString(_state.value("balance").toString()); + QVariantList transactions = _state.value("transactions").toList(); + + std::vector transactionSequence; + + for (auto const& t: transactions) { - m_modelDebugger->resetState(); - AppContext::getInstance()->displayMessageDialog(QApplication::tr("State status"), QApplication::tr("State reseted ... need to redeploy contract")); + QVariantMap transaction = t.toMap(); + + QString functionId = transaction.value("functionId").toString(); + u256 value = fromQString(transaction.value("value").toString()); + u256 gas = fromQString(transaction.value("gas").toString()); + u256 gasPrice = fromQString(transaction.value("gasPrice").toString()); + QVariantMap params = transaction.value("parameters").toMap(); + TransactionSettings transactionSettings(functionId, value, gas, gasPrice); + + for (auto p = params.cbegin(); p != params.cend(); ++p) + transactionSettings.parameterValues.insert(std::make_pair(p.key(), fromQString(p.value().toString()))); + + transactionSequence.push_back(transactionSettings); } + executeSequence(transactionSequence, balance); } -void AssemblyDebuggerControl::callContract(TransactionSettings _tr, dev::Address _contract) +void AssemblyDebuggerControl::executeSequence(std::vector const& _sequence, u256 _balance) { - CompilerResult compilerRes = m_compilation->compile(m_doc->toPlainText()); - if (!compilerRes.success) - AppContext::getInstance()->displayMessageDialog("debugger","compilation failed"); - else + if (m_running) + throw (std::logic_error("debugging already running")); + auto compilerRes = m_ctx->codeModel()->code(); + std::shared_ptr contractDef = compilerRes->sharedContract(); + m_running = true; + + emit runStarted(); + emit stateChanged(); + + //run sequence + QtConcurrent::run([=]() { - ContractCallDataEncoder c; - std::shared_ptr contractDef = QContractDefinition::Contract(m_doc->toPlainText()); - QFunctionDefinition* f = nullptr; - for (int k = 0; k < contractDef->functions().size(); k++) + try { - if (contractDef->functions().at(k)->name() == _tr.functionId) + bytes contractCode = compilerRes->bytes(); + std::vector transactonData; + QFunctionDefinition* f; + ContractCallDataEncoder c; + //encode data for all transactions + for (auto const& t: _sequence) { - f = (QFunctionDefinition*)contractDef->functions().at(k); - break; + f = nullptr; + for (int tf = 0; tf < contractDef->functionsList().size(); tf++) + { + if (contractDef->functionsList().at(tf)->name() == t.functionId) + { + f = contractDef->functionsList().at(tf); + break; + } + } + if (!f) + throw std::runtime_error("function " + t.functionId.toStdString() + " not found"); + + c.encode(f->index()); + for (int p = 0; p < f->parametersList().size(); p++) + { + QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); + u256 value = 0; + auto v = t.parameterValues.find(var->name()); + if (v != t.parameterValues.cend()) + value = v->second; + c.encode(var, value); + } + transactonData.emplace_back(c.encodedData()); } - } - if (!f) - AppContext::getInstance()->displayMessageDialog(QApplication::tr("debugger"), QApplication::tr("function not found. Please redeploy this contract.")); - else - { - c.encode(f->index()); - for (int k = 0; k < f->parameters().size(); k++) + + //run contract creation first + m_modelDebugger->resetState(_balance); + DebuggingContent debuggingContent = m_modelDebugger->deployContract(contractCode); + Address address = debuggingContent.contractAddress; + for (unsigned i = 0; i < _sequence.size(); ++i) + debuggingContent = m_modelDebugger->callContract(address, transactonData.at(i), _sequence.at(i)); + + if (f) + debuggingContent.returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); + + //we need to wrap states in a QObject before sending to QML. + QList wStates; + for (int i = 0; i < debuggingContent.machineStates.size(); i++) { - QVariableDeclaration* var = (QVariableDeclaration*)f->parameters().at(k); - c.encode(var, _tr.parameterValues[var->name()]); + QPointer s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes())); + s->setState(debuggingContent.machineStates.at(i)); + wStates.append(s); } - DebuggingContent debuggingContent = m_modelDebugger->callContract(_contract, c.encodedData(), _tr); - debuggingContent.returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); - finalizeExecution(debuggingContent); + //collect states for last transaction + AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode); + emit dataAvailable(debuggingContent.returnParameters, wStates, code); + emit runComplete(); + } + catch(boost::exception const&) + { + emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); } - } -} - -void AssemblyDebuggerControl::deployContract(QString _source) -{ - CompilerResult compilerRes = m_compilation->compile(_source); - if (!compilerRes.success) - emit dataAvailable(false, DebuggingStatusResult::Compilationfailed); - else - { - m_previousDebugResult = m_modelDebugger->deployContract(compilerRes.bytes); - finalizeExecution(m_previousDebugResult); - } -} -void AssemblyDebuggerControl::finalizeExecution(DebuggingContent _debuggingContent) -{ - //we need to wrap states in a QObject before sending to QML. - QList wStates; - for(int i = 0; i < _debuggingContent.machineStates.size(); i++) - { - QPointer s(new DebuggingStateWrapper(_debuggingContent.executionCode, _debuggingContent.executionData.toBytes())); - s->setState(_debuggingContent.machineStates.at(i)); - wStates.append(s); - } - AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(_debuggingContent.executionCode); - emit dataAvailable(true, DebuggingStatusResult::Ok, _debuggingContent.returnParameters, wStates, code); + catch(std::exception const& e) + { + emit runFailed(e.what()); + } + m_running = false; + emit stateChanged(); + }); } -void AssemblyDebuggerControl::updateGUI(bool _success, DebuggingStatusResult const& _reason, QList const& _returnParam, QList const& _wStates, AssemblyDebuggerData const& _code) +void AssemblyDebuggerControl::showDebugger(QList const& _returnParam, QList const& _wStates, AssemblyDebuggerData const& _code) { - Q_UNUSED(_reason); - if (_success) - { - m_appEngine->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); - m_appEngine->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); - m_appEngine->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); - m_appEngine->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); - this->addContentOn(this); - } - else - m_ctx->displayMessageDialog(QApplication::tr("debugger"), QApplication::tr("compilation failed")); + m_appEngine->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); + m_appEngine->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); + m_appEngine->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); + m_appEngine->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); + this->addContentOn(this); } -void AssemblyDebuggerControl::runTransaction(TransactionSettings const& _tr) +void AssemblyDebuggerControl::showDebugError(QString const& _error) { - QtConcurrent::run([this, _tr]() - { - callContract(_tr, m_previousDebugResult.contractAddress); - }); + m_ctx->displayMessageDialog(QApplication::tr("Debugger"), _error); } diff --git a/mix/AssemblyDebuggerControl.h b/mix/AssemblyDebuggerControl.h index 702839250..b4dff38f5 100644 --- a/mix/AssemblyDebuggerControl.h +++ b/mix/AssemblyDebuggerControl.h @@ -19,25 +19,18 @@ #pragma once +#include #include -#include #include "Extension.h" -#include "ConstantCompilationModel.h" -#include "TransactionListModel.h" #include "AssemblyDebuggerModel.h" -#include "AppContext.h" using AssemblyDebuggerData = std::tuple, dev::mix::QQMLMap*>; -enum DebuggingStatusResult -{ - Ok, - Compilationfailed -}; Q_DECLARE_METATYPE(AssemblyDebuggerData) -Q_DECLARE_METATYPE(DebuggingStatusResult) Q_DECLARE_METATYPE(dev::mix::DebuggingContent) +class AppContext; + namespace dev { namespace mix @@ -51,33 +44,46 @@ class AssemblyDebuggerControl: public Extension Q_OBJECT public: - AssemblyDebuggerControl(QTextDocument* _doc); + AssemblyDebuggerControl(AppContext* _context); ~AssemblyDebuggerControl() {} void start() const override; QString title() const override; QString contentUrl() const override; + Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) + private: - void deployContract(QString _source); - void callContract(TransactionSettings _tr, Address _contract); - void finalizeExecution(DebuggingContent _content); + void executeSequence(std::vector const& _sequence, u256 _balance); std::unique_ptr m_modelDebugger; - std::unique_ptr m_compilation; - DebuggingContent m_previousDebugResult; //TODO: to be replaced in a more consistent struct. Used for now to keep the contract address in case of future transaction call. - QTextDocument* m_doc; + std::atomic m_running; public slots: - /// Handle key pressed. F5 deploy contract - F6 reset state. - void keyPressed(int); + /// Run the contract constructor and show debugger window. + void debugDeployment(); + /// Setup state, run transaction sequence, show debugger for the last transaction + /// @param _state JS object with state configuration + void debugState(QVariantMap _state); + +private slots: /// Update UI with machine states result. Display a modal dialog. - void updateGUI(bool _success, DebuggingStatusResult const& _reason, QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); - /// Run the given transaction. - void runTransaction(TransactionSettings const& _tr); + void showDebugger(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + /// Update UI with transaction run error. + void showDebugError(QString const& _error); signals: + /// Transaction execution started + void runStarted(); + /// Transaction execution completed successfully + void runComplete(); + /// Transaction execution completed with error + /// @param _message Error message + void runFailed(QString const& _message); + /// Execution state changed + void stateChanged(); + /// Emited when machine states are available. - void dataAvailable(bool _success, DebuggingStatusResult const& _reason, QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + void dataAvailable(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); }; } diff --git a/mix/AssemblyDebuggerModel.cpp b/mix/AssemblyDebuggerModel.cpp index 354d0bd1e..d09c2cd18 100644 --- a/mix/AssemblyDebuggerModel.cpp +++ b/mix/AssemblyDebuggerModel.cpp @@ -23,30 +23,30 @@ #include #include #include -#include "AppContext.h" -#include "TransactionListModel.h" #include "AssemblyDebuggerModel.h" -#include "ConstantCompilationModel.h" +#include "AppContext.h" #include "DebuggingStateWrapper.h" + using namespace std; using namespace dev; using namespace dev::eth; -using namespace dev::mix; + +namespace dev +{ +namespace mix +{ AssemblyDebuggerModel::AssemblyDebuggerModel(): - m_userAccount(KeyPair::create()), - m_baseState(Address(), m_overlayDB, BaseState::Empty) + m_userAccount(KeyPair::create()) { - m_baseState.addBalance(m_userAccount.address(), 10000000 * ether); - m_executiveState = m_baseState; - m_currentExecution = std::unique_ptr(new Executive(m_executiveState, LastHashes(), 0)); + resetState(10000000 * ether); } DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const& _rawTransaction) { QList machineStates; - m_currentExecution.reset(new Executive(m_executiveState, LastHashes(), 0)); - m_currentExecution->setup(_rawTransaction); + eth::Executive execution(m_executiveState, LastHashes(), 0); + execution.setup(_rawTransaction); std::vector levels; bytes code; bytesConstRef data; @@ -72,12 +72,12 @@ DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const& vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); }; - m_currentExecution->go(onOp); - m_currentExecution->finalize(onOp); + execution.go(onOp); + execution.finalize(); m_executiveState.completeMine(); DebuggingContent d; - d.returnValue = m_currentExecution->out().toVector(); + d.returnValue = execution.out().toVector(); d.machineStates = machineStates; d.executionCode = code; d.executionData = data; @@ -91,7 +91,7 @@ DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code) u256 gasPrice = 10000000000000; u256 gas = 1000000; u256 amount = 100; - Transaction _tr(amount, gasPrice, min(gas, m_baseState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); + Transaction _tr(amount, gasPrice, min(gas, m_executiveState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); bytes b = _tr.rlp(); dev::bytesConstRef bytesRef = &b; DebuggingContent d = executeTransaction(bytesRef); @@ -102,7 +102,7 @@ DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code) DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) { - Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_baseState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); + Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_executiveState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); bytes b = tr.rlp(); dev::bytesConstRef bytesRef = &b; DebuggingContent d = executeTransaction(bytesRef); @@ -110,8 +110,11 @@ DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, b return d; } -void AssemblyDebuggerModel::resetState() +void AssemblyDebuggerModel::resetState(u256 _balance) { - // Reset the state back to our clean premine. - m_executiveState = m_baseState; + m_executiveState = eth::State(Address(), m_overlayDB, BaseState::Empty); + m_executiveState.addBalance(m_userAccount.address(), _balance); +} + +} } diff --git a/mix/AssemblyDebuggerModel.h b/mix/AssemblyDebuggerModel.h index 1b1254464..c12e711c6 100644 --- a/mix/AssemblyDebuggerModel.h +++ b/mix/AssemblyDebuggerModel.h @@ -26,13 +26,31 @@ #include #include #include "DebuggingStateWrapper.h" -#include "TransactionListModel.h" namespace dev { namespace mix { +/// Backend transaction config class +struct TransactionSettings +{ + TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): + functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} + + /// Contract function name + QString functionId; + /// Transaction value + u256 value; + /// Gas + u256 gas; + /// Gas price + u256 gasPrice; + /// Mapping from contract function parameter name to value + std::map parameterValues; +}; + + /** * @brief Long-life object for managing all executions. */ @@ -44,15 +62,13 @@ public: DebuggingContent callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); /// Deploy the contract described by _code. DebuggingContent deployContract(bytes const& _code); - /// Reset state to the base state. - void resetState(); + /// Reset state to the empty state with given balance. + void resetState(u256 _balance); private: KeyPair m_userAccount; OverlayDB m_overlayDB; - eth::State m_baseState; eth::State m_executiveState; - std::unique_ptr m_currentExecution; DebuggingContent executeTransaction(dev::bytesConstRef const& _rawTransaction); }; diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp index 6c94a805e..48c928a1f 100644 --- a/mix/CodeEditorExtensionManager.cpp +++ b/mix/CodeEditorExtensionManager.cpp @@ -25,14 +25,22 @@ #include #include #include -#include #include "ConstantCompilationControl.h" #include "AssemblyDebuggerControl.h" -#include "TransactionListView.h" +#include "StateListView.h" #include "AppContext.h" +#include "MixApplication.h" +#include "CodeModel.h" +#include "CodeHighlighter.h" #include "CodeEditorExtensionManager.h" + using namespace dev::mix; +CodeEditorExtensionManager::CodeEditorExtensionManager(): + m_appContext(static_cast(QApplication::instance())->context()) +{ +} + CodeEditorExtensionManager::~CodeEditorExtensionManager() { m_features.clear(); @@ -42,40 +50,30 @@ void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) { if (!_editor) return; - try + + QVariant doc = _editor->property("textDocument"); + if (doc.canConvert()) { - QVariant doc = _editor->property("textDocument"); - if (doc.canConvert()) + QQuickTextDocument* qqdoc = doc.value(); + if (qqdoc) { - QQuickTextDocument* qqdoc = doc.value(); - if (qqdoc) - { - m_doc = qqdoc->textDocument(); - auto args = QApplication::arguments(); - if (args.length() > 1) - { - QString path = args[1]; - QFile file(path); - if (file.exists() && file.open(QFile::ReadOnly)) - m_doc->setPlainText(file.readAll()); - } - } + m_doc = qqdoc->textDocument(); } } - catch (...) - { - qDebug() << "unable to load editor: "; - } } void CodeEditorExtensionManager::initExtensions() { - initExtension(std::make_shared(m_doc)); - std::shared_ptr debug = std::make_shared(m_doc); - std::shared_ptr tr = std::make_shared(m_doc); - QObject::connect(tr->model(), &TransactionListModel::transactionStarted, debug.get(), &AssemblyDebuggerControl::runTransaction); + std::shared_ptr output = std::make_shared(m_appContext); + std::shared_ptr debug = std::make_shared(m_appContext); + std::shared_ptr stateList = std::make_shared(m_appContext); + QObject::connect(m_doc, &QTextDocument::contentsChange, this, &CodeEditorExtensionManager::onCodeChange); + QObject::connect(debug.get(), &AssemblyDebuggerControl::runFailed, output.get(), &ConstantCompilationControl::displayError); + QObject::connect(m_appContext->codeModel(), &CodeModel::compilationComplete, this, &CodeEditorExtensionManager::applyCodeHighlight); + + initExtension(output); initExtension(debug); - initExtension(tr); + initExtension(stateList); } void CodeEditorExtensionManager::initExtension(std::shared_ptr _ext) @@ -103,6 +101,26 @@ void CodeEditorExtensionManager::setEditor(QQuickItem* _editor) { this->loadEditor(_editor); this->initExtensions(); + + auto args = QApplication::arguments(); + if (args.length() > 1) + { + QString path = args[1]; + QFile file(path); + if (file.exists() && file.open(QFile::ReadOnly)) + m_doc->setPlainText(file.readAll()); + } +} + +void CodeEditorExtensionManager::onCodeChange() +{ + m_appContext->codeModel()->updateFormatting(m_doc); //update old formatting + m_appContext->codeModel()->registerCodeChange(m_doc->toPlainText()); +} + +void CodeEditorExtensionManager::applyCodeHighlight() +{ + m_appContext->codeModel()->updateFormatting(m_doc); } void CodeEditorExtensionManager::setRightTabView(QQuickItem* _tabView) diff --git a/mix/CodeEditorExtensionManager.h b/mix/CodeEditorExtensionManager.h index 94a5f4b2e..46ee6569f 100644 --- a/mix/CodeEditorExtensionManager.h +++ b/mix/CodeEditorExtensionManager.h @@ -22,6 +22,7 @@ #pragma once +#include #include #include #include @@ -32,6 +33,9 @@ namespace dev namespace mix { + +class AppContext; + /** * @brief Init and provides connection between extensions. */ @@ -44,7 +48,7 @@ class CodeEditorExtensionManager: public QObject Q_PROPERTY(QQuickItem* rightTabView MEMBER m_rightTabView WRITE setRightTabView) public: - CodeEditorExtensionManager() {} + CodeEditorExtensionManager(); ~CodeEditorExtensionManager(); /// Initialize all extensions. void initExtensions(); @@ -57,12 +61,17 @@ public: /// Set current right tab view. void setRightTabView(QQuickItem*); +private slots: + void onCodeChange(); + void applyCodeHighlight(); + private: QQuickItem* m_editor; QVector> m_features; QQuickItem* m_tabView; QQuickItem* m_rightTabView; QTextDocument* m_doc; + AppContext* m_appContext; void loadEditor(QQuickItem* _editor); }; diff --git a/mix/CodeHighlighter.cpp b/mix/CodeHighlighter.cpp new file mode 100644 index 000000000..5c126d8e2 --- /dev/null +++ b/mix/CodeHighlighter.cpp @@ -0,0 +1,170 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CodeHighlighter.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CodeHighlighter.h" + +using namespace dev::mix; + +CodeHighlighterSettings::CodeHighlighterSettings() +{ + backgroundColor = QColor(0x00, 0x2b, 0x36); + foregroundColor = QColor(0xee, 0xe8, 0xd5); + formats[Keyword].setForeground(QColor(0x93, 0xa1, 0xa1)); + formats[Comment].setForeground(QColor(0x85, 0x99, 0x00)); + formats[StringLiteral].setForeground(QColor(0xdc, 0x32, 0x2f)); + formats[NumLiteral].setForeground(foregroundColor); + formats[Import].setForeground(QColor(0x6c, 0x71, 0xc4)); + formats[CompilationError].setUnderlineColor(Qt::red); + formats[CompilationError].setUnderlineStyle(QTextCharFormat::SingleUnderline); +} + +namespace +{ + using namespace dev::solidity; + class HighlightVisitor: public ASTConstVisitor + { + public: + HighlightVisitor(CodeHighlighter::Formats* _formats) { m_formats = _formats; } + private: + CodeHighlighter::Formats* m_formats; + + virtual bool visit(ImportDirective const& _node) + { + m_formats->push_back(CodeHighlighter::FormatRange(CodeHighlighterSettings::Import, _node.getLocation())); + return true; + } + }; +} + +CodeHighlighter::FormatRange::FormatRange(CodeHighlighterSettings::Token _t, solidity::Location const& _location): + token(_t), start(_location.start), length(_location.end - _location.start) +{} + +void CodeHighlighter::processSource(std::string const& _source) +{ + processComments(_source); + solidity::CharStream stream(_source); + solidity::Scanner scanner(stream); + solidity::Token::Value token = scanner.getCurrentToken(); + while (token != Token::EOS) + { + if ((token >= Token::BREAK && token < Token::TYPES_END) || + token == Token::IN || token == Token::DELETE || token == Token::NULL_LITERAL || token == Token::TRUE_LITERAL || token == Token::FALSE_LITERAL) + m_formats.push_back(FormatRange(CodeHighlighterSettings::Keyword, scanner.getCurrentLocation())); + else if (token == Token::STRING_LITERAL) + m_formats.push_back(FormatRange(CodeHighlighterSettings::StringLiteral, scanner.getCurrentLocation())); + else if (token == Token::COMMENT_LITERAL) + m_formats.push_back(FormatRange(CodeHighlighterSettings::Comment, scanner.getCurrentLocation())); + else if (token == Token::NUMBER) + m_formats.push_back(FormatRange(CodeHighlighterSettings::NumLiteral, scanner.getCurrentLocation())); + + token = scanner.next(); + } + std::sort(m_formats.begin(), m_formats.end()); +} + +void CodeHighlighter::processAST(solidity::ASTNode const& _ast) +{ + HighlightVisitor visitor(&m_formats); + _ast.accept(visitor); + + std::sort(m_formats.begin(), m_formats.end()); +} + +void CodeHighlighter::processError(dev::Exception const& _exception) +{ + Location const* location = boost::get_error_info(_exception); + m_formats.push_back(FormatRange(CodeHighlighterSettings::CompilationError, *location)); +} + +void CodeHighlighter::processComments(std::string const& _source) +{ + unsigned i = 0; + unsigned size = _source.size(); + if (size == 0) + return; + while (i < size - 1) + { + if (_source[i] == '/' && _source[i + 1] == '/') + { + //add single line comment + int start = i; + i += 2; + while (_source[i] != '\n' && i < size) + ++i; + m_formats.push_back(FormatRange(CodeHighlighterSettings::Comment, start, i - start)); + } + else if (_source[i] == '/' && _source[i + 1] == '*') + { + //add multiline comment + int start = i; + i += 2; + while ((_source[i] != '/' || _source[i - 1] != '*') && i < size) + ++i; + m_formats.push_back(FormatRange(CodeHighlighterSettings::Comment, start, i - start + 1)); + } + ++i; + } +} + +void CodeHighlighter::updateFormatting(QTextDocument* _document, CodeHighlighterSettings const& _settings) +{ + QTextBlock block = _document->firstBlock(); + QList ranges; + + Formats::const_iterator format = m_formats.begin(); + while (true) + { + while ((format == m_formats.end() || (block.position() + block.length() <= format->start)) && block.isValid()) + { + auto layout = block.layout(); + layout->clearAdditionalFormats(); + layout->setAdditionalFormats(ranges); + _document->markContentsDirty(block.position(), block.length()); + block = block.next(); + ranges.clear(); + } + if (!block.isValid()) + break; + + int intersectionStart = std::max(format->start, block.position()); + int intersectionLength = std::min(format->start + format->length, block.position() + block.length()) - intersectionStart; + if (intersectionLength > 0) + { + QTextLayout::FormatRange range; + range.format = _settings.formats[format->token]; + range.start = format->start - block.position(); + range.length = format->length; + ranges.append(range); + } + ++format; + } +} diff --git a/mix/CodeHighlighter.h b/mix/CodeHighlighter.h new file mode 100644 index 000000000..5083e6c82 --- /dev/null +++ b/mix/CodeHighlighter.h @@ -0,0 +1,109 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CodeHighlighter.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include +#include +#include + +class QTextDocument; + +namespace dev +{ + +struct Exception; + +namespace solidity +{ + class ASTNode; + struct Location; +} + +namespace mix +{ + +/// Code highligting settings +class CodeHighlighterSettings +{ +public: + enum Token + { + Import, + Keyword, + Comment, + StringLiteral, + NumLiteral, + CompilationError, + Size, //this must be kept last + }; + + CodeHighlighterSettings(); + ///Format for each token + QTextCharFormat formats[Size]; + ///Background color + QColor backgroundColor; + ///Foreground color + QColor foregroundColor; +}; + +/// Code highlighting engine class +class CodeHighlighter +{ +public: + /// Formatting range + struct FormatRange + { + FormatRange(CodeHighlighterSettings::Token _t, int _start, int _length): token(_t), start(_start), length(_length) {} + FormatRange(CodeHighlighterSettings::Token _t, solidity::Location const& _location); + bool operator<(FormatRange const& _other) const { return start < _other.start || (start == _other.start && length < _other.length); } + + CodeHighlighterSettings::Token token; + int start; + int length; + }; + typedef std::vector Formats; // Sorted by start position + +public: + /// Collect highligting information by lexing the source + void processSource(std::string const& _source); + /// Collect additional highligting information from AST + void processAST(solidity::ASTNode const& _ast); + /// Collect highlighting information from compilation exception + void processError(dev::Exception const& _exception); + + /// Apply formatting for a text document + /// @todo Remove this once editor is reworked + void updateFormatting(QTextDocument* _document, CodeHighlighterSettings const& _settings); + +private: + /// Collect highligting information by paring for comments + /// @todo Support this in solidity? + void processComments(std::string const& _source); + +private: + Formats m_formats; +}; + +} + +} diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp new file mode 100644 index 000000000..7d8b0442a --- /dev/null +++ b/mix/CodeModel.cpp @@ -0,0 +1,177 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CodeModel.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2014 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "QContractDefinition.h" +#include "QFunctionDefinition.h" +#include "QVariableDeclaration.h" +#include "CodeHighlighter.h" +#include "CodeModel.h" + +using namespace dev::mix; + +void BackgroundWorker::queueCodeChange(int _jobId, QString const& _content) +{ + m_model->runCompilationJob(_jobId, _content); +} + +CompilationResult::CompilationResult(): + QObject(nullptr), + m_successful(false), + m_codeHash(qHash(QString())), + m_contract(new QContractDefinition()), + m_codeHighlighter(new CodeHighlighter()) +{} + +CompilationResult::CompilationResult(const solidity::CompilerStack& _compiler): + QObject(nullptr), + m_successful(true), + m_codeHash(qHash(QString())) +{ + if (!_compiler.getContractNames().empty()) + { + m_contract.reset(new QContractDefinition(&_compiler.getContractDefinition(std::string()))); + m_bytes = _compiler.getBytecode(); + m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes)); + } + else + m_contract.reset(new QContractDefinition()); +} + +CompilationResult::CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage): + QObject(nullptr), + m_successful(false), + m_codeHash(qHash(QString())), + m_contract(_prev.m_contract), + m_compilerMessage(_compilerMessage), + m_bytes(_prev.m_bytes), + m_assemblyCode(_prev.m_assemblyCode), + m_codeHighlighter(_prev.m_codeHighlighter) +{} + +CodeModel::CodeModel(QObject* _parent): + QObject(_parent), + m_compiling(false), + m_result(new CompilationResult()), + m_codeHighlighterSettings(new CodeHighlighterSettings()), + m_backgroundWorker(this), + m_backgroundJobId(0) +{ + m_backgroundWorker.moveToThread(&m_backgroundThread); + connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection); + connect(this, &CodeModel::compilationCompleteInternal, this, &CodeModel::onCompilationComplete, Qt::QueuedConnection); + qRegisterMetaType("CompilationResult*"); + qRegisterMetaType("QContractDefinition*"); + qRegisterMetaType("QFunctionDefinition*"); + qRegisterMetaType("QVariableDeclaration*"); + qmlRegisterType("org.ethereum.qml", 1, 0, "QFunctionDefinition"); + qmlRegisterType("org.ethereum.qml", 1, 0, "QVariableDeclaration"); + m_backgroundThread.start(); +} + +CodeModel::~CodeModel() +{ + stop(); + disconnect(this); +} + +void CodeModel::stop() +{ + ///@todo: cancel bg job + m_backgroundThread.exit(); + m_backgroundThread.wait(); +} + +void CodeModel::registerCodeChange(QString const& _code) +{ + // launch the background thread + uint hash = qHash(_code); + if (m_result->m_codeHash == hash) + return; + m_backgroundJobId++; + m_compiling = true; + emit stateChanged(); + emit scheduleCompilationJob(m_backgroundJobId, _code); +} + +void CodeModel::runCompilationJob(int _jobId, QString const& _code) +{ + if (_jobId != m_backgroundJobId) + return; //obsolete job + + solidity::CompilerStack cs; + std::unique_ptr result; + + std::string source = _code.toStdString(); + // run syntax highlighting first + // @todo combine this with compilation step + auto codeHighlighter = std::make_shared(); + codeHighlighter->processSource(source); + + // run compilation + try + { + cs.setSource(source); + cs.compile(false); + codeHighlighter->processAST(cs.getAST()); + result.reset(new CompilationResult(cs)); + qDebug() << QString(QApplication::tr("compilation succeeded")); + } + catch (dev::Exception const& _exception) + { + std::ostringstream error; + solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs); + result.reset(new CompilationResult(*m_result, QString::fromStdString(error.str()))); + codeHighlighter->processError(_exception); + qDebug() << QString(QApplication::tr("compilation failed:") + " " + result->compilerMessage()); + } + result->m_codeHighlighter = codeHighlighter; + result->m_codeHash = qHash(_code); + + emit compilationCompleteInternal(result.release()); +} + +void CodeModel::onCompilationComplete(CompilationResult*_newResult) +{ + m_compiling = false; + m_result.reset(_newResult); + emit compilationComplete(); + emit stateChanged(); + if (m_result->successfull()) + emit codeChanged(); +} + +bool CodeModel::hasContract() const +{ + return m_result->contract()->functionsList().size() > 0; +} + +void CodeModel::updateFormatting(QTextDocument* _document) +{ + m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings); +} diff --git a/mix/CodeModel.h b/mix/CodeModel.h new file mode 100644 index 000000000..c66703b22 --- /dev/null +++ b/mix/CodeModel.h @@ -0,0 +1,163 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CodeModel.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2014 + * Ethereum IDE client. + */ + +#pragma once + +#include +#include +#include +#include +#include + +class QTextDocument; + +namespace dev +{ + +namespace solidity +{ + class CompilerStack; +} + +namespace mix +{ + +class CodeModel; +class CodeHighlighter; +class CodeHighlighterSettings; +class QContractDefinition; + +//utility class to perform tasks in background thread +class BackgroundWorker: public QObject +{ + Q_OBJECT + +public: + BackgroundWorker(CodeModel* _model): QObject(), m_model(_model) {} + +public slots: + void queueCodeChange(int _jobId, QString const& _content); +private: + CodeModel* m_model; +}; + +///Compilation result model. Contains all the compiled contract data required by UI +class CompilationResult: public QObject +{ + Q_OBJECT + Q_PROPERTY(QContractDefinition* contract READ contract) + +public: + /// Empty compilation result constructor + CompilationResult(); + /// Successfull compilation result constructor + CompilationResult(solidity::CompilerStack const& _compiler); + /// Failed compilation result constructor + CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage); + + /// @returns contract definition for QML property + QContractDefinition* contract() { return m_contract.get(); } + /// @returns contract definition + std::shared_ptr sharedContract() { return m_contract; } + /// Indicates if the compilation was successfull + bool successfull() const { return m_successful; } + /// @returns compiler error message in case of unsuccessfull compilation + QString compilerMessage() const { return m_compilerMessage; } + /// @returns contract bytecode + dev::bytes const& bytes() const { return m_bytes; } + /// @returns contract bytecode in human-readable form + QString assemblyCode() const { return m_assemblyCode; } + /// Get code highlighter + std::shared_ptr codeHighlighter() { return m_codeHighlighter; } + +private: + bool m_successful; + uint m_codeHash; + std::shared_ptr m_contract; + QString m_compilerMessage; ///< @todo: use some structure here + dev::bytes m_bytes; + QString m_assemblyCode; + std::shared_ptr m_codeHighlighter; + + friend class CodeModel; +}; + +/// Background code compiler +class CodeModel: public QObject +{ + Q_OBJECT + +public: + CodeModel(QObject* _parent); + ~CodeModel(); + + /// @returns latest compilation result + CompilationResult* code() { return m_result.get(); } + /// @returns latest compilation resul + CompilationResult const* code() const { return m_result.get(); } + + Q_PROPERTY(CompilationResult* code READ code NOTIFY codeChanged) + Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged) + Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged) + + /// @returns compilation status + bool isCompiling() const { return m_compiling; } + /// @returns true if contract has at least one function + bool hasContract() const; + /// Apply text document formatting. @todo Move this to editor module + void updateFormatting(QTextDocument* _document); + +signals: + /// Emited on compilation state change + void stateChanged(); + /// Emitted on compilation complete + void compilationComplete(); + /// Internal signal used to transfer compilation job to background thread + void scheduleCompilationJob(int _jobId, QString const& _content); + /// Emitted if there are any changes in the code model + void codeChanged(); + /// Emitted on compilation complete. Internal + void compilationCompleteInternal(CompilationResult* _newResult); + +private slots: + void onCompilationComplete(CompilationResult* _newResult); + +public slots: + /// Update code model on source code change + void registerCodeChange(QString const& _code); + +private: + void runCompilationJob(int _jobId, QString const& _content); + void stop(); + + std::atomic m_compiling; + std::unique_ptr m_result; + std::unique_ptr m_codeHighlighterSettings; + QThread m_backgroundThread; + BackgroundWorker m_backgroundWorker; + int m_backgroundJobId = 0; //protects from starting obsolete compilation job + friend class BackgroundWorker; +}; + +} + +} diff --git a/mix/ConstantCompilationControl.cpp b/mix/ConstantCompilationControl.cpp index 3082ba677..e2d69bf62 100644 --- a/mix/ConstantCompilationControl.cpp +++ b/mix/ConstantCompilationControl.cpp @@ -28,14 +28,16 @@ #include #include #include "ConstantCompilationControl.h" -#include "ConstantCompilationModel.h" #include "QContractDefinition.h" +#include "AppContext.h" +#include "CodeModel.h" + using namespace dev::mix; -ConstantCompilationControl::ConstantCompilationControl(QTextDocument* _doc): Extension(ExtensionDisplayBehavior::Tab) +ConstantCompilationControl::ConstantCompilationControl(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::Tab) { - m_editor = _doc; - m_compilationModel = std::unique_ptr(new ConstantCompilationModel()); + connect(_context->codeModel(), &CodeModel::compilationComplete, this, &ConstantCompilationControl::update); + connect(_context->codeModel(), &CodeModel::compilationComplete, this, &ConstantCompilationControl::update); } QString ConstantCompilationControl::contentUrl() const @@ -50,18 +52,25 @@ QString ConstantCompilationControl::title() const void ConstantCompilationControl::start() const { - connect(m_editor, SIGNAL(contentsChange(int,int,int)), this, SLOT(compile())); } -void ConstantCompilationControl::compile() +void ConstantCompilationControl::update() { - QString codeContent = m_editor->toPlainText().replace("\n", ""); - if (codeContent.isEmpty()) - resetOutPut(); + auto result = m_ctx->codeModel()->code(); + + QObject* status = m_view->findChild("status", Qt::FindChildrenRecursively); + QObject* content = m_view->findChild("content", Qt::FindChildrenRecursively); + if (result->successfull()) + { + status->setProperty("text", "succeeded"); + status->setProperty("color", "green"); + content->setProperty("text", result->assemblyCode()); + } else { - CompilerResult res = m_compilationModel->compile(m_editor->toPlainText().replace("\t", " ")); - writeOutPut(res); + status->setProperty("text", "failure"); + status->setProperty("color", "red"); + content->setProperty("text", result->compilerMessage()); } } @@ -73,20 +82,12 @@ void ConstantCompilationControl::resetOutPut() content->setProperty("text", ""); } -void ConstantCompilationControl::writeOutPut(CompilerResult const& _res) + +void ConstantCompilationControl::displayError(QString const& _error) { QObject* status = m_view->findChild("status", Qt::FindChildrenRecursively); QObject* content = m_view->findChild("content", Qt::FindChildrenRecursively); - if (_res.success) - { - status->setProperty("text", "succeeded"); - status->setProperty("color", "green"); - content->setProperty("text", _res.hexCode); - } - else - { - status->setProperty("text", "failure"); - status->setProperty("color", "red"); - content->setProperty("text", _res.comment); - } + status->setProperty("text", "failure"); + status->setProperty("color", "red"); + content->setProperty("text", _error); } diff --git a/mix/ConstantCompilationControl.h b/mix/ConstantCompilationControl.h index b2ffaadd3..227ad6080 100644 --- a/mix/ConstantCompilationControl.h +++ b/mix/ConstantCompilationControl.h @@ -19,8 +19,6 @@ #pragma once -#include -#include "ConstantCompilationModel.h" #include "Extension.h" namespace dev @@ -36,21 +34,18 @@ class ConstantCompilationControl: public Extension Q_OBJECT public: - ConstantCompilationControl(QTextDocument* _doc); + ConstantCompilationControl(AppContext* _appContext); ~ConstantCompilationControl() {} void start() const override; QString title() const override; QString contentUrl() const override; private: - QTextDocument* m_editor; - std::unique_ptr m_compilationModel; - void writeOutPut(CompilerResult const& _res); void resetOutPut(); public slots: - /// Compile text editor content. - void compile(); + void update(); + void displayError(QString const& _error); }; } diff --git a/mix/ConstantCompilationModel.cpp b/mix/ConstantCompilationModel.cpp deleted file mode 100644 index 06141da26..000000000 --- a/mix/ConstantCompilationModel.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file ConstantCompilationModel.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "ConstantCompilationModel.h" -using namespace std; -using namespace dev; -using namespace dev::eth; -using namespace dev::mix; -using namespace dev::solidity; - -CompilerResult ConstantCompilationModel::compile(QString _code) -{ - dev::solidity::CompilerStack compiler; - dev::bytes m_data; - CompilerResult res; - try - { - m_data = compiler.compile(_code.toStdString(), true); - res.success = true; - res.comment = "ok"; - res.hexCode = QString::fromStdString(dev::eth::disassemble(m_data)); - res.bytes = m_data; - } - catch (dev::Exception const& _exception) - { - ostringstream error; - solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", compiler); - res.success = false; - res.comment = QString::fromStdString(error.str()); - res.hexCode = ""; - } - catch (...) - { - res.success = false; - res.comment = QApplication::tr("Uncaught exception."); - res.hexCode = ""; - } - return res; -} diff --git a/mix/ConstantCompilationModel.h b/mix/ConstantCompilationModel.h deleted file mode 100644 index a4c4f3de5..000000000 --- a/mix/ConstantCompilationModel.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file ConstantCompilationModel.h - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include -#include - -namespace dev -{ -namespace mix -{ - -/** - * @brief Provides compiler result information. - */ -struct CompilerResult -{ - QString hexCode; - QString comment; - dev::bytes bytes; - bool success; -}; - -/** - * @brief Compile source code using the solidity library. - */ -class ConstantCompilationModel -{ - -public: - ConstantCompilationModel() {} - ~ConstantCompilationModel() {} - /// Compile code. - CompilerResult compile(QString _code); -}; - -} - -} diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index b83a0c582..757f7243c 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -43,19 +43,19 @@ void ContractCallDataEncoder::encode(int _functionIndex) m_encodedData.insert(m_encodedData.end(), i.begin(), i.end()); } -void ContractCallDataEncoder::encode(QVariableDeclaration* _dec, bool _value) +void ContractCallDataEncoder::encode(QVariableDeclaration const* _dec, bool _value) { return encode(_dec, QString(formatBool(_value))); } -void ContractCallDataEncoder::encode(QVariableDeclaration* _dec, QString _value) +void ContractCallDataEncoder::encode(QVariableDeclaration const* _dec, QString _value) { int padding = this->padding(_dec->type()); bytes data = padded(jsToBytes(_value.toStdString()), padding); m_encodedData.insert(m_encodedData.end(), data.begin(), data.end()); } -void ContractCallDataEncoder::encode(QVariableDeclaration* _dec, u256 _value) +void ContractCallDataEncoder::encode(QVariableDeclaration const* _dec, u256 _value) { int padding = this->padding(_dec->type()); std::ostringstream s; diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index a66d9b4b6..fd67a7b7a 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -38,11 +38,11 @@ class ContractCallDataEncoder public: ContractCallDataEncoder() {} /// Encode variable in order to be sent as parameter. - void encode(QVariableDeclaration* _dec, QString _value); + void encode(QVariableDeclaration const* _dec, QString _value); /// Encode variable in order to be sent as parameter. - void encode(QVariableDeclaration* _dec, u256 _value); + void encode(QVariableDeclaration const* _dec, u256 _value); /// Encode variable in order to be sent as parameter. - void encode(QVariableDeclaration* _dec, bool _value); + void encode(QVariableDeclaration const* _dec, bool _value); /// Encode index of the function to call. void encode(int _functionIndex); /// Decode variable in order to be sent to QML view. diff --git a/mix/Extension.cpp b/mix/Extension.cpp index 53d3f491f..8a36751a0 100644 --- a/mix/Extension.cpp +++ b/mix/Extension.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include "Extension.h" @@ -26,20 +27,20 @@ using namespace dev; using namespace dev::mix; -Extension::Extension() +Extension::Extension(AppContext* _context) { - init(); + init(_context); } -Extension::Extension(ExtensionDisplayBehavior _displayBehavior) +Extension::Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior) { - init(); + init(_context); m_displayBehavior = _displayBehavior; } -void Extension::init() +void Extension::init(AppContext* _context) { - m_ctx = AppContext::getInstance(); + m_ctx = _context; m_appEngine = m_ctx->appEngine(); } @@ -50,7 +51,7 @@ void Extension::addTabOn(QObject* _view) QVariant returnValue; QQmlComponent* component = new QQmlComponent( - AppContext::getInstance()->appEngine(), + m_appEngine, QUrl(contentUrl()), _view); QMetaObject::invokeMethod(_view, "addTab", @@ -66,9 +67,9 @@ void Extension::addContentOn(QObject* _view) Q_UNUSED(_view); if (m_displayBehavior == ExtensionDisplayBehavior::ModalDialog) { - QQmlComponent* component = new QQmlComponent(AppContext::getInstance()->appEngine(), QUrl(contentUrl()), _view); - QObject* dialogWin = AppContext::getInstance()->appEngine()->rootObjects().at(0)->findChild("dialog", Qt::FindChildrenRecursively); - QObject* dialogWinComponent = AppContext::getInstance()->appEngine()->rootObjects().at(0)->findChild("modalDialogContent", Qt::FindChildrenRecursively); + QQmlComponent* component = new QQmlComponent(m_appEngine, QUrl(contentUrl()), _view); + QObject* dialogWin = m_appEngine->rootObjects().at(0)->findChild("dialog", Qt::FindChildrenRecursively); + QObject* dialogWinComponent = m_appEngine->rootObjects().at(0)->findChild("modalDialogContent", Qt::FindChildrenRecursively); dialogWinComponent->setProperty("sourceComponent", QVariant::fromValue(component)); dialogWin->setProperty("title", title()); QMetaObject::invokeMethod(dialogWin, "open"); diff --git a/mix/Extension.h b/mix/Extension.h index 9e3e7f55b..bbd2d206b 100644 --- a/mix/Extension.h +++ b/mix/Extension.h @@ -21,13 +21,16 @@ #include #include -#include "AppContext.h" + +class QQmlApplicationEngine; namespace dev { namespace mix { +class AppContext; + enum ExtensionDisplayBehavior { Tab, @@ -41,8 +44,8 @@ class Extension: public QObject Q_OBJECT public: - Extension(); - Extension(ExtensionDisplayBehavior _displayBehavior); + Extension(AppContext* _context); + Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior); /// Return the QML url of the view to display. virtual QString contentUrl() const { return ""; } /// Return the title of this extension. @@ -65,7 +68,7 @@ protected: QQmlApplicationEngine* m_appEngine; private: - void init(); + void init(AppContext* _context); }; } diff --git a/mix/KeyEventManager.cpp b/mix/KeyEventManager.cpp deleted file mode 100644 index c32caadb8..000000000 --- a/mix/KeyEventManager.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file KeyEventManager.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Used as an event handler for all classes which need keyboard interactions. - * Can be improve by adding the possibility to register to a specific key. - */ - -#include -#include -#include "KeyEventManager.h" - -void KeyEventManager::registerEvent(const QObject* _receiver, const char* _slot) -{ - QObject::connect(this, SIGNAL(onKeyPressed(int)), _receiver, _slot); -} - -void KeyEventManager::unRegisterEvent(QObject* _receiver) -{ - QObject::disconnect(_receiver); -} - -void KeyEventManager::keyPressed(QVariant _event) -{ - emit onKeyPressed(_event.toInt()); -} diff --git a/mix/KeyEventManager.h b/mix/KeyEventManager.h deleted file mode 100644 index 2a743fa8f..000000000 --- a/mix/KeyEventManager.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file KeyEventManager.h - * @author Yann yann@ethdev.com - * @date 2014 - * Used as an event handler for all classes which need keyboard interactions - */ - -#pragma once - -#include -#include - -class KeyEventManager: public QObject -{ - Q_OBJECT - -public: - KeyEventManager() {} - /// Allows _receiver to handle key pressed event. - void registerEvent(const QObject* _receiver, const char* _slot); - /// Unregister _receiver. - void unRegisterEvent(QObject* _receiver); - -signals: - /// Emited when a key is pressed. - void onKeyPressed(int _event); - -public slots: - /// Called when a key is pressed. - void keyPressed(QVariant _event); -}; - diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 0c217dace..5cf71aa7d 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -20,26 +20,24 @@ */ #include +#include +#include "CodeEditorExtensionManager.h" #include "MixApplication.h" +#include "AppContext.h" + +#include + using namespace dev::mix; -MixApplication::MixApplication(int _argc, char* _argv[]): QApplication(_argc, _argv) +MixApplication::MixApplication(int _argc, char* _argv[]): + QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get())) { + qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); + QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff + m_engine->load(QUrl("qrc:/qml/main.qml")); + m_appContext->loadProject(); } -bool MixApplication::notify(QObject* _receiver, QEvent* _event) +MixApplication::~MixApplication() { - try - { - return MixApplication::notify(_receiver, _event); - } - catch (std::exception& _ex) - { - qDebug() << "std::exception was caught " << _ex.what(); - } - catch (...) - { - qDebug() << "uncaught exception "; - } - return false; } diff --git a/mix/MixApplication.h b/mix/MixApplication.h index 62c37bb5c..86c1126fc 100644 --- a/mix/MixApplication.h +++ b/mix/MixApplication.h @@ -23,21 +23,31 @@ #pragma once +#include #include +class QQmlApplicationEngine; + namespace dev { namespace mix { +class AppContext; + class MixApplication: public QApplication { Q_OBJECT public: MixApplication(int _argc, char* _argv[]); - virtual ~MixApplication() {} - virtual bool notify(QObject* _receiver, QEvent* _event); + virtual ~MixApplication(); + AppContext* context() { return m_appContext.get(); } + QQmlApplicationEngine* engine() { return m_engine.get(); } + +private: + std::unique_ptr m_engine; + std::unique_ptr m_appContext; }; } diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index 55454f123..72f96dbcc 100644 --- a/mix/QContractDefinition.cpp +++ b/mix/QContractDefinition.cpp @@ -31,18 +31,13 @@ using namespace dev::solidity; using namespace dev::mix; -std::shared_ptr QContractDefinition::Contract(QString _source) +QContractDefinition::QContractDefinition(dev::solidity::ContractDefinition const* _contract): QBasicNodeDefinition(_contract) { - CompilerStack* comp = AppContext::getInstance()->compiler(); - comp->addSource("contract", _source.toStdString()); - comp->parse(); - ContractDefinition const* def = &comp->getContractDefinition(comp->getContractNames().front()); - return std::make_shared(def); -} - -void QContractDefinition::initQFunctions() -{ - std::vector functions = m_contract->getInterfaceFunctions(); + std::vector functions = _contract->getInterfaceFunctions(); for (unsigned i = 0; i < functions.size(); i++) - m_functions.append(new QFunctionDefinition(functions.at(i), i)); + { + FunctionDefinition const* func = functions.at(i); + m_functions.append(new QFunctionDefinition(func, i)); + } } + diff --git a/mix/QContractDefinition.h b/mix/QContractDefinition.h index e4e767380..e9c618804 100644 --- a/mix/QContractDefinition.h +++ b/mix/QContractDefinition.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include "QFunctionDefinition.h" #include "QBasicNodeDefinition.h" @@ -34,20 +35,18 @@ namespace mix class QContractDefinition: public QBasicNodeDefinition { Q_OBJECT - Q_PROPERTY(QList functions READ functions) + Q_PROPERTY(QQmlListProperty functions READ functions CONSTANT) public: - QContractDefinition(solidity::ContractDefinition const* _contract): QBasicNodeDefinition(_contract), m_contract(_contract) { initQFunctions(); } + QContractDefinition() {} + QContractDefinition(solidity::ContractDefinition const* _contract); /// Get all the functions of the contract. - QList functions() const { return m_functions; } - /// Get the description (functions, parameters, return parameters, ...) of the contract describes by _code. - static std::shared_ptr Contract(QString _code); - + QQmlListProperty functions() const { return QQmlListProperty(const_cast(this), const_cast(this)->m_functions); } + QList const& functionsList() const { return m_functions; } private: - solidity::ContractDefinition const* m_contract; QList m_functions; - void initQFunctions(); }; } } + diff --git a/mix/QFunctionDefinition.cpp b/mix/QFunctionDefinition.cpp index e1884b5aa..7149809af 100644 --- a/mix/QFunctionDefinition.cpp +++ b/mix/QFunctionDefinition.cpp @@ -25,13 +25,13 @@ using namespace dev::solidity; using namespace dev::mix; -void QFunctionDefinition::initQParameters() +QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionDefinition const* _f, int _index): QBasicNodeDefinition(_f), m_index(_index) { - std::vector> parameters = m_functions->getParameterList().getParameters(); + std::vector> parameters = _f->getParameterList().getParameters(); for (unsigned i = 0; i < parameters.size(); i++) m_parameters.append(new QVariableDeclaration(parameters.at(i).get())); - std::vector> returnParameters = m_functions->getReturnParameters(); + std::vector> returnParameters = _f->getReturnParameters(); for (unsigned i = 0; i < returnParameters.size(); i++) m_returnParameters.append(new QVariableDeclaration(returnParameters.at(i).get())); } diff --git a/mix/QFunctionDefinition.h b/mix/QFunctionDefinition.h index 368e2e239..b2d0cd0b7 100644 --- a/mix/QFunctionDefinition.h +++ b/mix/QFunctionDefinition.h @@ -22,8 +22,9 @@ #pragma once #include +#include #include -#include +#include "QVariableDeclaration.h" #include "QBasicNodeDefinition.h" namespace dev @@ -34,13 +35,16 @@ namespace mix class QFunctionDefinition: public QBasicNodeDefinition { Q_OBJECT - Q_PROPERTY(QList parameters READ parameters) + Q_PROPERTY(QQmlListProperty parameters READ parameters) Q_PROPERTY(int index READ index) public: - QFunctionDefinition(solidity::FunctionDefinition const* _f, int _index): QBasicNodeDefinition(_f), m_index(_index), m_functions(_f) { initQParameters(); } + QFunctionDefinition() {} + QFunctionDefinition(solidity::FunctionDefinition const* _f, int _index); /// Get all input parameters of this function. - QList parameters() const { return m_parameters; } + QList const& parametersList() const { return m_parameters; } + /// Get all input parameters of this function as QML property. + QQmlListProperty parameters() const { return QQmlListProperty(const_cast(this), const_cast(this)->m_parameters); } /// Get all return parameters of this function. QList returnParameters() const { return m_returnParameters; } /// Get the index of this function on the contract ABI. @@ -48,7 +52,6 @@ public: private: int m_index; - solidity::FunctionDefinition const* m_functions; QList m_parameters; QList m_returnParameters; void initQParameters(); diff --git a/mix/QVariableDeclaration.h b/mix/QVariableDeclaration.h index 2d2c40970..966ee0ff3 100644 --- a/mix/QVariableDeclaration.h +++ b/mix/QVariableDeclaration.h @@ -35,15 +35,12 @@ class QVariableDeclaration: public QBasicNodeDefinition Q_PROPERTY(QString type READ type CONSTANT) public: - QVariableDeclaration(solidity::VariableDeclaration const* _v): QBasicNodeDefinition(_v), m_variable(_v) {} - /// Get the type of this variable. - QString type() const { return QString::fromStdString(m_variable->getType()->toString()); } - + QVariableDeclaration() {} + QVariableDeclaration(solidity::VariableDeclaration const* _v): QBasicNodeDefinition(_v), m_type(QString::fromStdString(_v->getType()->toString())) {} + QString type() const { return m_type; } private: - solidity::VariableDeclaration const* m_variable; + QString m_type; }; } } - -Q_DECLARE_METATYPE(dev::mix::QVariableDeclaration*) diff --git a/mix/QVariableDefinition.h b/mix/QVariableDefinition.h index 898a02621..f55e51346 100644 --- a/mix/QVariableDefinition.h +++ b/mix/QVariableDefinition.h @@ -32,6 +32,7 @@ namespace mix class QVariableDefinition: public QObject { Q_OBJECT + Q_PROPERTY(QString value READ value CONSTANT) Q_PROPERTY(QVariableDeclaration* declaration READ declaration CONSTANT) diff --git a/mix/TransactionListView.cpp b/mix/StateListView.cpp similarity index 57% rename from mix/TransactionListView.cpp rename to mix/StateListView.cpp index 7e2bfaa25..79364fdfb 100644 --- a/mix/TransactionListView.cpp +++ b/mix/StateListView.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file TransactionListView.cpp +/** @file StateListView.cpp * @author Arkadiy Paronyan arkadiy@ethdev.com * @date 2014 * Ethereum IDE client. @@ -25,32 +25,24 @@ #include #include #include -#include "TransactionListView.h" -#include "TransactionListModel.h" -using namespace dev::mix; +#include "StateListView.h" -TransactionListView::TransactionListView(QTextDocument* _doc): Extension(ExtensionDisplayBehavior::RightTab) -{ - m_editor = _doc; - m_model.reset(new TransactionListModel(this, _doc)); - m_appEngine->rootContext()->setContextProperty("transactionListModel", m_model.get()); -} +using namespace dev::mix; -TransactionListView::~TransactionListView() +StateListView::StateListView(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::RightTab) { - //implementation is in cpp file so that all types deleted are complete } -QString TransactionListView::contentUrl() const +QString StateListView::contentUrl() const { - return QStringLiteral("qrc:/qml/TransactionList.qml"); + return QStringLiteral("qrc:/qml/StateList.qml"); } -QString TransactionListView::title() const +QString StateListView::title() const { - return QApplication::tr("Transactions"); + return QApplication::tr("States"); } -void TransactionListView::start() const +void StateListView::start() const { } diff --git a/mix/TransactionListView.h b/mix/StateListView.h similarity index 69% rename from mix/TransactionListView.h rename to mix/StateListView.h index 2ec775261..18d1ae879 100644 --- a/mix/TransactionListView.h +++ b/mix/StateListView.h @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file TransactionListView.h +/** @file StateListView.h * @author Arkadiy Paronyan arkadiy@ethdev.com * @date 2014 * Ethereum IDE client. @@ -19,6 +19,7 @@ #pragma once +#include #include #include "Extension.h" @@ -27,26 +28,16 @@ namespace dev namespace mix { -class TransactionListModel; - -/// Transactions list control -/// @todo This should be moved into state as a sequence -class TransactionListView: public Extension +/// State list control +class StateListView: public Extension { Q_OBJECT public: - TransactionListView(QTextDocument*); - ~TransactionListView(); + StateListView(AppContext* _context); void start() const override; QString title() const override; QString contentUrl() const override; - /// @returns the underlying model - TransactionListModel* model() const { return m_model.get(); } - -private: - QTextDocument* m_editor; - std::unique_ptr m_model; }; } diff --git a/mix/TransactionListModel.cpp b/mix/TransactionListModel.cpp deleted file mode 100644 index 64964b898..000000000 --- a/mix/TransactionListModel.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file TransactionListModel.cpp - * @author Arkadiy Paronyan arkadiy@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#include -#include -#include -#include -#include -#include "TransactionListModel.h" -#include "QContractDefinition.h" -#include "QFunctionDefinition.h" -#include "QVariableDeclaration.h" - - -namespace dev -{ -namespace mix -{ - -/// @todo Move this to QML -u256 fromQString(QString const& _s) -{ - return dev::jsToU256(_s.toStdString()); -} - -/// @todo Move this to QML -QString toQString(u256 _value) -{ - std::ostringstream s; - s << _value; - return QString::fromStdString(s.str()); -} - -TransactionListItem::TransactionListItem(int _index, TransactionSettings const& _t, QObject* _parent): - QObject(_parent), m_index(_index), m_title(_t.title), m_functionId(_t.functionId), m_value(toQString(_t.value)), - m_gas(toQString(_t.gas)), m_gasPrice(toQString(_t.gasPrice)) -{} - -TransactionListModel::TransactionListModel(QObject* _parent, QTextDocument* _document): - QAbstractListModel(_parent), m_document(_document) -{ - qRegisterMetaType("TransactionListItem*"); -} - -QHash TransactionListModel::roleNames() const -{ - QHash roles; - roles[TitleRole] = "title"; - roles[IdRole] = "transactionIndex"; - return roles; -} - -int TransactionListModel::rowCount(QModelIndex const& _parent) const -{ - Q_UNUSED(_parent); - return m_transactions.size(); -} - -QVariant TransactionListModel::data(QModelIndex const& _index, int _role) const -{ - if (_index.row() < 0 || _index.row() >= (int)m_transactions.size()) - return QVariant(); - auto const& transaction = m_transactions.at(_index.row()); - switch (_role) - { - case TitleRole: - return QVariant(transaction.title); - case IdRole: - return QVariant(_index.row()); - default: - return QVariant(); - } -} - -///@todo: get parameters from code model -QList buildParameters(QTextDocument* _document, TransactionSettings const& _transaction, QString const& _functionId) -{ - QList params; - try - { - std::shared_ptr contract = QContractDefinition::Contract(_document->toPlainText()); - auto functions = contract->functions(); - for (auto f : functions) - { - if (f->name() != _functionId) - continue; - - auto parameters = f->parameters(); - //build a list of parameters for a function. If the function is selected as current, add parameter values as well - for (auto p : parameters) - { - QString paramValue; - if (f->name() == _transaction.functionId) - { - auto paramValueIter = _transaction.parameterValues.find(p->name()); - if (paramValueIter != _transaction.parameterValues.cend()) - paramValue = toQString(paramValueIter->second); - } - - TransactionParameterItem* item = new TransactionParameterItem(p->name(), p->type(), paramValue); - QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); - params.append(item); - } - } - } - catch (boost::exception const&) - { - //TODO: - } - - return params; -} - -///@todo: get fnctions from code model -QList TransactionListModel::getFunctions() -{ - QList functionNames; - try - { - QString code = m_document->toPlainText(); - std::shared_ptr contract(QContractDefinition::Contract(code)); - auto functions = contract->functions(); - for (auto f : functions) - { - functionNames.append(f->name()); - } - } - catch (boost::exception const&) - { - } - return functionNames; -} - -QVariantList TransactionListModel::getParameters(int _index, QString const& _functionId) -{ - TransactionSettings const& transaction = (_index >= 0 && _index < (int)m_transactions.size()) ? m_transactions[_index] : TransactionSettings(); - auto plist = buildParameters(m_document, transaction, _functionId); - QVariantList vl; - for (QObject* p : plist) - vl.append(QVariant::fromValue(p)); - return vl; -} - -TransactionListItem* TransactionListModel::getItem(int _index) -{ - TransactionSettings const& transaction = (_index >= 0 && _index < (int)m_transactions.size()) ? m_transactions[_index] : TransactionSettings(); - TransactionListItem* item = new TransactionListItem(_index, transaction, nullptr); - QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); - return item; -} - -void TransactionListModel::edit(QObject* _data) -{ - //these properties come from TransactionDialog QML object - ///@todo change the model to a qml component - int index = _data->property("transactionIndex").toInt(); - QString title = _data->property("transactionTitle").toString(); - QString gas = _data->property("gas").toString(); - QString gasPrice = _data->property("gasPrice").toString(); - QString value = _data->property("transactionValue").toString(); - QString functionId = _data->property("functionId").toString(); - QAbstractListModel* paramsModel = qvariant_cast(_data->property("transactionParams")); - TransactionSettings transaction(title, functionId, fromQString(value), fromQString(gas), fromQString(gasPrice)); - int paramCount = paramsModel->rowCount(QModelIndex()); - for (int p = 0; p < paramCount; ++p) - { - QString paramName = paramsModel->data(paramsModel->index(p, 0), Qt::DisplayRole).toString(); - QString paramValue = paramsModel->data(paramsModel->index(p, 0), Qt::DisplayRole + 2).toString(); - if (!paramValue.isEmpty() && !paramName.isEmpty()) - transaction.parameterValues[paramName] = fromQString(paramValue); - } - - if (index >= 0 && index < (int)m_transactions.size()) - { - beginRemoveRows(QModelIndex(), index, index); - m_transactions.erase(m_transactions.begin() + index); - endRemoveRows(); - } - else - index = rowCount(QModelIndex()); - - beginInsertRows(QModelIndex(), index, index); - m_transactions.push_back(transaction); - emit countChanged(); - endInsertRows(); -} - -int TransactionListModel::getCount() const -{ - return rowCount(QModelIndex()); -} - -void TransactionListModel::runTransaction(int _index) -{ - TransactionSettings tr = m_transactions.at(_index); - emit transactionStarted(tr); -} - - -} -} - diff --git a/mix/TransactionListModel.h b/mix/TransactionListModel.h deleted file mode 100644 index d321fc91c..000000000 --- a/mix/TransactionListModel.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file TransactionListView.h - * @author Arkadiy Paronyan arkadiy@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -class QTextDocument; - -namespace dev -{ -namespace mix -{ - -/// Backend transaction config class -struct TransactionSettings -{ - TransactionSettings(): - value(0), gas(10000), gasPrice(10 * dev::eth::szabo) {} - - TransactionSettings(QString const& _title, QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): - title(_title), functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} - - /// User specified transaction title - QString title; - /// Contract function name - QString functionId; - /// Transaction value - u256 value; - /// Gas - u256 gas; - /// Gas price - u256 gasPrice; - /// Mapping from contract function parameter name to value - std::map parameterValues; -}; - -/// QML transaction parameter class -class TransactionParameterItem: public QObject -{ - Q_OBJECT - Q_PROPERTY(QString name READ name CONSTANT) - Q_PROPERTY(QString type READ type CONSTANT) - Q_PROPERTY(QString value READ value CONSTANT) -public: - TransactionParameterItem(QString const& _name, QString const& _type, QString const& _value): - m_name(_name), m_type(_type), m_value(_value) {} - - /// Parameter name, set by contract definition - QString name() { return m_name; } - /// Parameter type, set by contract definition - QString type() { return m_type; } - /// Parameter value, set by user - QString value() { return m_value; } - -private: - QString m_name; - QString m_type; - QString m_value; -}; - -class TransactionListItem: public QObject -{ - Q_OBJECT - Q_PROPERTY(int index READ index CONSTANT) - Q_PROPERTY(QString title READ title CONSTANT) - Q_PROPERTY(QString functionId READ functionId CONSTANT) - Q_PROPERTY(QString gas READ gas CONSTANT) - Q_PROPERTY(QString gasPrice READ gasPrice CONSTANT) - Q_PROPERTY(QString value READ value CONSTANT) - -public: - TransactionListItem(int _index, TransactionSettings const& _t, QObject* _parent); - - /// User specified transaction title - QString title() { return m_title; } - /// Gas - QString gas() { return m_gas; } - /// Gas cost - QString gasPrice() { return m_gasPrice; } - /// Transaction value - QString value() { return m_value; } - /// Contract function name - QString functionId() { return m_functionId; } - /// Index of this transaction in the transactions list - int index() { return m_index; } - -private: - int m_index; - QString m_title; - QString m_functionId; - QString m_value; - QString m_gas; - QString m_gasPrice; -}; - -/// QML model for a list of transactions -class TransactionListModel: public QAbstractListModel -{ - Q_OBJECT - Q_PROPERTY(int count READ getCount() NOTIFY countChanged()) - - enum Roles - { - TitleRole = Qt::DisplayRole, - IdRole = Qt::UserRole + 1 - }; - -public: - TransactionListModel(QObject* _parent, QTextDocument* _document); - ~TransactionListModel() {} - - QHash roleNames() const override; - int rowCount(QModelIndex const& _parent) const override; - QVariant data(QModelIndex const& _index, int _role) const override; - int getCount() const; - /// Apply changes from transaction dialog. Argument is a dialog model as defined in TransactionDialog.qml - /// @todo Change that to transaction item - Q_INVOKABLE void edit(QObject* _data); - /// @returns transaction item for a give index - Q_INVOKABLE TransactionListItem* getItem(int _index); - /// @returns a list of functions for current contract - Q_INVOKABLE QList getFunctions(); - /// @returns function parameters along with parameter values if set. @see TransactionParameterItem - Q_INVOKABLE QVariantList getParameters(int _id, QString const& _functionId); - /// Launch transaction execution UI handler - Q_INVOKABLE void runTransaction(int _index); - -signals: - /// Transaction count has changed - void countChanged(); - /// Transaction has been launched - void transactionStarted(dev::mix::TransactionSettings); - -private: - std::vector m_transactions; - QTextDocument* m_document; -}; - -} - -} - diff --git a/mix/main.cpp b/mix/main.cpp index 36a742f8f..90099555f 100644 --- a/mix/main.cpp +++ b/mix/main.cpp @@ -20,22 +20,11 @@ * Ethereum IDE client. */ -#include -#include -#include -#include "CodeEditorExtensionManager.h" -#include "AppContext.h" #include "MixApplication.h" using namespace dev::mix; -int main(int _argc, char *_argv[]) +int main(int _argc, char* _argv[]) { - QApplication app(_argc, _argv); - qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); - QQmlApplicationEngine* engine = new QQmlApplicationEngine(); - AppContext::setApplicationContext(engine); - QObject::connect(&app, SIGNAL(lastWindowClosed()), AppContext::getInstance(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff - QObject::connect(engine, SIGNAL(objectCreated(QObject*, QUrl)), AppContext::getInstance(), SLOT(resourceLoaded(QObject*, QUrl))); - engine->load(QUrl("qrc:/qml/main.qml")); + MixApplication app(_argc, _argv); return app.exec(); } diff --git a/mix/qml.qrc b/mix/qml.qrc index 2384afa69..7e731b6f4 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -8,8 +8,9 @@ qml/js/Debugger.js qml/BasicMessage.qml qml/TransactionDialog.qml - qml/TransactionList.qml qml/ModalDialog.qml qml/AlertMessageDialog.qml + qml/StateDialog.qml + qml/StateList.qml diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 794e5746d..7f9a27d58 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -29,21 +29,64 @@ Rectangle { id: contentView width: parent.width height: parent.height * 0.7 - TextArea { - id: codeEditor - height: parent.height - font.family: "Monospace" - font.pointSize: 12 - width: parent.width - anchors.centerIn: parent - tabChangesFocus: false - Keys.onPressed: { - if (event.key === Qt.Key_Tab) { - codeEditor.insert(codeEditor.cursorPosition, "\t"); - event.accepted = true; - } - } - } + + Item { + anchors.fill: parent + Rectangle { + id: lineColumn + property int rowHeight: codeEditor.font.pixelSize + 3 + color: "#202020" + width: 50 + height: parent.height + Column { + y: -codeEditor.flickableItem.contentY + 4 + width: parent.width + Repeater { + model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) + delegate: Text { + id: text + color: codeEditor.textColor + font: codeEditor.font + width: lineColumn.width - 4 + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + height: lineColumn.rowHeight + renderType: Text.NativeRendering + text: index + 1 + } + } + } + } + + TextArea { + id: codeEditor + textColor: "#EEE8D5" + style: TextAreaStyle { + backgroundColor: "#002B36" + } + + anchors.left: lineColumn.right + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + wrapMode: TextEdit.NoWrap + frameVisible: false + + height: parent.height + font.family: "Monospace" + font.pointSize: 12 + width: parent.width + //anchors.centerIn: parent + tabChangesFocus: false + Keys.onPressed: { + if (event.key === Qt.Key_Tab) { + codeEditor.insert(codeEditor.cursorPosition, "\t"); + event.accepted = true; + } + } + } + } + } Rectangle { anchors.bottom: parent.bottom diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml new file mode 100644 index 000000000..eb0b68ac8 --- /dev/null +++ b/mix/qml/StateDialog.qml @@ -0,0 +1,182 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.0 + +Window { + modality: Qt.WindowModal + + width:640 + height:480 + + visible: false + + property alias stateTitle : titleField.text + property alias stateBalance : balanceField.text + property int stateIndex + property var stateTransactions: [] + signal accepted + + function open(index, item) { + stateIndex = index; + stateTitle = item.title; + stateBalance = item.balance; + transactionsModel.clear(); + stateTransactions = []; + var transactions = item.transactions; + for (var t = 0; t < transactions.length; t++) { + transactionsModel.append(item.transactions[t]); + stateTransactions.push(item.transactions[t]); + } + visible = true; + titleField.focus = true; + } + + function close() { + visible = false; + } + + function getItem() { + var item = { + title: stateDialog.stateTitle, + balance: stateDialog.stateBalance, + transactions: [] + } + item.transactions = stateTransactions; + return item; + } + + GridLayout { + id: dialogContent + columns: 2 + anchors.fill: parent + anchors.margins: 10 + rowSpacing: 10 + columnSpacing: 10 + + Label { + text: qsTr("Title") + } + TextField { + id: titleField + focus: true + Layout.fillWidth: true + } + + Label { + text: qsTr("Balance") + } + TextField { + id: balanceField + Layout.fillWidth: true + } + + Label { + text: qsTr("Transactions") + } + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: transactionsModel + delegate: transactionRenderDelegate + } + + Label { + + } + Button { + text: qsTr("Add") + onClicked: transactionsModel.addTransaction() + } + } + + RowLayout { + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + text: qsTr("Ok"); + onClicked: { + close(); + accepted(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); + } + } + + ListModel { + id: transactionsModel + + function editTransaction(index) { + transactionDialog.open(index, transactionsModel.get(index)); + } + + function addTransaction() { + + // Set next id here to work around Qt bug + // https://bugreports.qt-project.org/browse/QTBUG-41327 + // Second call to signal handler would just edit the item that was just created, no harm done + var item = { + value: "0", + functionId: "", + gas: "1000000000000", + gasPrice: "100000" + }; + + transactionDialog.open(transactionsModel.count, item); + } + + function deleteTransaction(index) { + stateTransactions.splice(index, 1); + transactionsModel.remove(index); + } + } + + Component { + id: transactionRenderDelegate + Item { + id: wrapperItem + height: 20 + width: parent.width + RowLayout { + anchors.fill: parent + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: functionId + font.pointSize: 12 + verticalAlignment: Text.AlignBottom + } + ToolButton { + text: qsTr("Edit"); + Layout.fillHeight: true + onClicked: transactionsModel.editTransaction(index) + } + ToolButton { + text: qsTr("Delete"); + Layout.fillHeight: true + onClicked: transactionsModel.deleteTransaction(index) + } + } + } + } + + TransactionDialog { + id: transactionDialog + onAccepted: { + var item = transactionDialog.getItem(); + + if (transactionDialog.transactionIndex < transactionsModel.count) { + transactionsModel.set(transactionDialog.transactionIndex, item); + stateTransactions[index] = item; + } else { + transactionsModel.append(item); + stateTransactions.push(item); + } + } + } + +} diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml new file mode 100644 index 000000000..152a35671 --- /dev/null +++ b/mix/qml/StateList.qml @@ -0,0 +1,132 @@ +import QtQuick 2.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 + +Rectangle { + color: "transparent" + id: stateListContainer + focus: true + anchors.topMargin: 10 + anchors.left: parent.left + height: parent.height + width: parent.width + property var stateList: [] + + Connections { + target: appContext + onProjectLoaded: { + var items = JSON.parse(_json); + for(var i = 0; i < items.length; i++) { + stateListModel.append(items[i]); + stateList.push(items[i]) + } + } + } + + ListView { + anchors.top: parent.top + height: parent.height + width: parent.width + model: stateListModel + delegate: renderDelegate + } + + Button { + anchors.bottom: parent.bottom + action: addStateAction + } + + StateDialog { + id: stateDialog + onAccepted: { + var item = stateDialog.getItem(); + if (stateDialog.stateIndex < stateListModel.count) { + stateList[stateDialog.stateIndex] = item; + stateListModel.set(stateDialog.stateIndex, item); + } else { + stateList.push(item); + stateListModel.append(item); + } + + stateListModel.save(); + } + } + + ListModel { + id: stateListModel + + function addState() { + var item = { + title: "", + balance: "100000000000000000000000000", + transactions: [] + }; + stateDialog.open(stateListModel.count, item); + } + + function editState(index) { + stateDialog.open(index, stateList[index]); + } + + function runState(index) { + var item = stateList[index]; + debugModel.debugState(item); + } + + function deleteState(index) { + stateListModel.remove(index); + stateList.splice(index, 1); + save(); + } + + function save() { + var json = JSON.stringify(stateList); + appContext.saveProject(json); + } + } + + Component { + id: renderDelegate + Item { + id: wrapperItem + height: 20 + width: parent.width + RowLayout { + anchors.fill: parent + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: title + font.pointSize: 12 + verticalAlignment: Text.AlignBottom + } + ToolButton { + text: qsTr("Edit"); + Layout.fillHeight: true + onClicked: stateListModel.editState(index); + } + ToolButton { + text: qsTr("Delete"); + Layout.fillHeight: true + onClicked: stateListModel.deleteState(index); + } + ToolButton { + text: qsTr("Run"); + Layout.fillHeight: true + onClicked: stateListModel.runState(index); + } + } + } + } + + Action { + id: addStateAction + text: "&Add State" + shortcut: "Ctrl+N" + enabled: codeModel.hasContract && !debugModel.running; + onTriggered: stateListModel.addState(); + } +} + diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 004d9be49..b7c556272 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -5,51 +5,43 @@ import QtQuick.Window 2.0 Window { modality: Qt.WindowModal - width:640 height:480 - visible: false - function open() - { - visible = true; - } - function close() - { - visible = false; - } - - property alias focus : titleField.focus - property alias transactionTitle : titleField.text property int transactionIndex property alias transactionParams : paramsModel; property alias gas : gasField.text; property alias gasPrice : gasPriceField.text; property alias transactionValue : valueField.text; property alias functionId : functionComboBox.currentText; - property var model; + property var itemParams; signal accepted; - function reset(index, m) { - model = m; - var item = model.getItem(index); + function open(index, item) { transactionIndex = index; - transactionTitle = item.title; gas = item.gas; gasPrice = item.gasPrice; transactionValue = item.value; var functionId = item.functionId; + itemParams = item.parameters !== undefined ? item.parameters : {}; functionsModel.clear(); var functionIndex = -1; - var functions = model.getFunctions(); + var functions = codeModel.code.contract.functions; for (var f = 0; f < functions.length; f++) { - functionsModel.append({ text: functions[f] }); - if (functions[f] === item.functionId) + functionsModel.append({ text: functions[f].name }); + if (functions[f].name === item.functionId) functionIndex = f; } + + if (functionIndex == -1 && functionsModel.count > 0) + functionIndex = 0; //@todo suggest unused funtion + functionComboBox.currentIndex = functionIndex; + loadParameters(); + visible = true; + valueField.focus = true; } function loadParameters() { @@ -57,13 +49,36 @@ Window { return; paramsModel.clear(); if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { - var parameters = model.getParameters(transactionIndex, functionsModel.get(functionComboBox.currentIndex).text); + var func = codeModel.code.contract.functions[functionComboBox.currentIndex]; + var parameters = func.parameters; for (var p = 0; p < parameters.length; p++) { - paramsModel.append({ name: parameters[p].name, type: parameters[p].type, value: parameters[p].value }); + var pname = parameters[p].name; + paramsModel.append({ name: pname, type: parameters[p].type, value: itemParams[pname] !== undefined ? itemParams[pname] : "" }); } } } + function close() + { + visible = false; + } + + function getItem() + { + var item = { + functionId: transactionDialog.functionId, + gas: transactionDialog.gas, + gasPrice: transactionDialog.gasPrice, + value: transactionDialog.transactionValue, + parameters: {} + } + for (var p = 0; p < transactionDialog.transactionParams.count; p++) { + var parameter = transactionDialog.transactionParams.get(p); + item.parameters[parameter.name] = parameter.value; + } + return item; + } + GridLayout { id: dialogContent columns: 2 @@ -72,19 +87,9 @@ Window { rowSpacing: 10 columnSpacing: 10 - Label { - text: qsTr("Title") - } - TextField { - id: titleField - focus: true - Layout.fillWidth: true - } - Label { text: qsTr("Function") } - ComboBox { id: functionComboBox Layout.fillWidth: true @@ -98,6 +103,7 @@ Window { loadParameters(); } } + Label { text: qsTr("Value") } @@ -195,7 +201,8 @@ Window { Connections { target: loaderEditor.item onTextChanged: { - paramsModel.setProperty(styleData.row, styleData.role, loaderEditor.item.text); + if (styleData.role === "value" && styleData.row < paramsModel.count) + paramsModel.setProperty(styleData.row, styleData.role, loaderEditor.item.text); } } sourceComponent: (styleData.selected) ? editor : null diff --git a/mix/qml/TransactionList.qml b/mix/qml/TransactionList.qml deleted file mode 100644 index 06ed2fa15..000000000 --- a/mix/qml/TransactionList.qml +++ /dev/null @@ -1,89 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Controls 1.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 - - -Rectangle { - color: "transparent" - id: transactionListContainer - focus: true - anchors.topMargin: 10 - anchors.left: parent.left - height: parent.height - width: parent.width - - ListView { - anchors.top: parent.top - height: parent.height - width: parent.width - id: transactionList - model: transactionListModel - delegate: renderDelegate - } - - Button { - anchors.bottom: parent.bottom - text: qsTr("Add") - onClicked: - { - // Set next id here to work around Qt bug - // https://bugreports.qt-project.org/browse/QTBUG-41327 - // Second call to signal handle would just edit the item that was just created, no harm done - transactionDialog.reset(transactionListModel.count, transactionListModel); - transactionDialog.open(); - transactionDialog.focus = true; - } - } - - TransactionDialog { - id: transactionDialog - onAccepted: { - transactionListModel.edit(transactionDialog); - } - } - - Component { - id: renderDelegate - Item { - id: wrapperItem - height: 20 - width: parent.width - RowLayout - { - anchors.fill: parent - Text { - //anchors.fill: parent - Layout.fillWidth: true - Layout.fillHeight: true - text: title - font.pointSize: 12 - verticalAlignment: Text.AlignBottom - } - ToolButton { - text: qsTr("Edit"); - Layout.fillHeight: true - onClicked: { - transactionDialog.reset(index, transactionListModel); - transactionDialog.open(); - transactionDialog.focus = true; - } - } - ToolButton { - text: qsTr("Delete"); - Layout.fillHeight: true - onClicked: { - } - } - ToolButton { - text: qsTr("Run"); - Layout.fillHeight: true - onClicked: { - transactionListModel.runTransaction(index); - } - } - } - } - } -} diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 1e7542831..bba7c9088 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -1,9 +1,9 @@ import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Controls.Styles 1.1 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 -import QtQuick.Window 2.0 +import QtQuick.Window 2.1 import CodeEditorExtensionManager 1.0 ApplicationWindow { @@ -23,6 +23,11 @@ ApplicationWindow { onTriggered: Qt.quit(); } } + Menu { + title: qsTr("Debug") + MenuItem { action: debugRunAction } + MenuItem { action: debugResetStateAction } + } } Component.onCompleted: { setX(Screen.width / 2 - width / 2); @@ -41,4 +46,21 @@ ApplicationWindow { objectName: "alertMessageDialog" id: messageDialog } + + Action { + id: debugRunAction + text: "&Run" + shortcut: "F5" + enabled: codeModel.hasContract && !debugModel.running; + onTriggered: debugModel.debugDeployment(); + } + + Action { + id: debugResetStateAction + text: "Reset &State" + shortcut: "F6" + onTriggered: debugModel.resetState(); + } + + }