Browse Source

Merge pull request #865 from arkpar/mix_ux

mix: ethereum.js http transport; UX improvements
cl-refactor
Gav Wood 10 years ago
parent
commit
ebc2f62476
  1. 13
      mix/AppContext.cpp
  2. 9
      mix/AppContext.h
  3. 7
      mix/CMakeLists.txt
  4. 41
      mix/ClientModel.cpp
  5. 14
      mix/ClientModel.h
  6. 1
      mix/CodeModel.cpp
  7. 170
      mix/HttpServer.cpp
  8. 123
      mix/HttpServer.h
  9. 4
      mix/MixApplication.cpp
  10. 124
      mix/qml/CodeEditor.qml
  11. 15
      mix/qml/CodeEditorView.qml
  12. 23
      mix/qml/MainContent.qml
  13. 17
      mix/qml/NewProjectDialog.qml
  14. 25
      mix/qml/ProjectList.qml
  15. 4
      mix/qml/ProjectModel.qml
  16. 2
      mix/qml/StateDialog.qml
  17. 102
      mix/qml/WebCodeEditor.qml
  18. 30
      mix/qml/WebPreview.qml
  19. 8
      mix/qml/html/WebContainer.html
  20. 2
      mix/qml/html/cm/solidity.js
  21. 41
      mix/qml/js/ProjectModel.js
  22. 38
      mix/qml/main.qml

13
mix/AppContext.cpp

@ -33,7 +33,7 @@
#include "Exceptions.h" #include "Exceptions.h"
#include "AppContext.h" #include "AppContext.h"
#include "QEther.h" #include "QEther.h"
#include <libwebthree/WebThree.h> #include "HttpServer.h"
using namespace dev; using namespace dev;
using namespace dev::eth; using namespace dev::eth;
@ -44,19 +44,9 @@ const QString c_projectFileName = "project.mix";
AppContext::AppContext(QQmlApplicationEngine* _engine) AppContext::AppContext(QQmlApplicationEngine* _engine)
{ {
m_applicationEngine = _engine; m_applicationEngine = _engine;
//m_webThree = std::unique_ptr<dev::WebThreeDirect>(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.reset(new CodeModel(this)); m_codeModel.reset(new CodeModel(this));
m_clientModel.reset(new ClientModel(this)); m_clientModel.reset(new ClientModel(this));
m_fileIo.reset(new FileIo()); m_fileIo.reset(new FileIo());
/*
m_applicationEngine->rootContext()->setContextProperty("appContext", this);
qmlRegisterType<FileIo>("org.ethereum.qml", 1, 0, "FileIo");
qmlRegisterSingletonType(QUrl("qrc:/qml/ProjectModel.qml"), "org.ethereum.qml.ProjectModel", 1, 0, "ProjectModel");
qmlRegisterType<QEther>("org.ethereum.qml.QEther", 1, 0, "QEther");
qmlRegisterType<QBigInt>("org.ethereum.qml.QBigInt", 1, 0, "QBigInt");
m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get());
m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get());
*/
} }
AppContext::~AppContext() AppContext::~AppContext()
@ -82,6 +72,7 @@ void AppContext::load()
} }
m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel); m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel);
qmlRegisterType<CodeEditorExtensionManager>("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); qmlRegisterType<CodeEditorExtensionManager>("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager");
qmlRegisterType<HttpServer>("HttpServer", 1, 0, "HttpServer");
m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); m_applicationEngine->load(QUrl("qrc:/qml/main.qml"));
appLoaded(); appLoaded();
} }

9
mix/AppContext.h

@ -32,14 +32,6 @@
#include <QObject> #include <QObject>
class QQmlApplicationEngine; class QQmlApplicationEngine;
namespace dev
{
class WebThreeDirect;
namespace solidity
{
class CompilerStack;
}
}
namespace dev namespace dev
{ {
@ -77,7 +69,6 @@ signals:
private: private:
QQmlApplicationEngine* m_applicationEngine; //owned by app QQmlApplicationEngine* m_applicationEngine; //owned by app
std::unique_ptr<dev::WebThreeDirect> m_webThree;
std::unique_ptr<CodeModel> m_codeModel; std::unique_ptr<CodeModel> m_codeModel;
std::unique_ptr<ClientModel> m_clientModel; std::unique_ptr<ClientModel> m_clientModel;
std::unique_ptr<FileIo> m_fileIo; std::unique_ptr<FileIo> m_fileIo;

7
mix/CMakeLists.txt

@ -12,7 +12,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST) aux_source_directory(. SRC_LIST)
include_directories(..) include_directories(..)
find_package (Qt5WebEngine) find_package (Qt5WebEngine QUIET)
qt5_add_resources(UI_RESOURCES qml.qrc) qt5_add_resources(UI_RESOURCES qml.qrc)
file(GLOB HEADERS "*.h") file(GLOB HEADERS "*.h")
@ -34,8 +34,11 @@ eth_add_executable(${EXECUTABLE}
target_link_libraries(${EXECUTABLE} Qt5::Core) target_link_libraries(${EXECUTABLE} Qt5::Core)
target_link_libraries(${EXECUTABLE} Qt5::Gui) target_link_libraries(${EXECUTABLE} Qt5::Gui)
target_link_libraries(${EXECUTABLE} Qt5::Widgets)
target_link_libraries(${EXECUTABLE} Qt5::Network)
target_link_libraries(${EXECUTABLE} Qt5::Quick)
target_link_libraries(${EXECUTABLE} Qt5::Qml)
target_link_libraries(${EXECUTABLE} webthree) target_link_libraries(${EXECUTABLE} webthree)
target_link_libraries(${EXECUTABLE} qwebthree)
target_link_libraries(${EXECUTABLE} ethereum) target_link_libraries(${EXECUTABLE} ethereum)
target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} evm)
target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} ethcore)

41
mix/ClientModel.cpp

@ -22,9 +22,9 @@
#include <QDebug> #include <QDebug>
#include <QQmlContext> #include <QQmlContext>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <jsonrpccpp/server.h>
#include <libdevcore/CommonJS.h> #include <libdevcore/CommonJS.h>
#include <libethereum/Transaction.h> #include <libethereum/Transaction.h>
#include <libqwebthree/QWebThree.h>
#include "AppContext.h" #include "AppContext.h"
#include "DebuggingStateWrapper.h" #include "DebuggingStateWrapper.h"
#include "QContractDefinition.h" #include "QContractDefinition.h"
@ -38,10 +38,30 @@
using namespace dev; using namespace dev;
using namespace dev::eth; using namespace dev::eth;
using namespace dev::mix;
namespace dev
{
namespace mix
{
class RpcConnector: public jsonrpc::AbstractServerConnector
{
public:
virtual bool StartListening() override { return true; }
virtual bool StopListening() override { return true; }
virtual bool SendResponse(std::string const& _response, void*) override
{
m_response = QString::fromStdString(_response);
return true;
}
QString response() const { return m_response; }
private:
QString m_response;
};
ClientModel::ClientModel(AppContext* _context): ClientModel::ClientModel(AppContext* _context):
m_context(_context), m_running(false), m_qWebThree(nullptr) m_context(_context), m_running(false), m_rpcConnector(new RpcConnector())
{ {
qRegisterMetaType<QBigInt*>("QBigInt*"); qRegisterMetaType<QBigInt*>("QBigInt*");
qRegisterMetaType<QEther*>("QEther*"); qRegisterMetaType<QEther*>("QEther*");
@ -55,11 +75,7 @@ ClientModel::ClientModel(AppContext* _context):
connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection); connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient()); m_client.reset(new MixClient());
m_qWebThree = new QWebThree(this); m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
m_qWebThreeConnector.reset(new QWebThreeConnector());
m_qWebThreeConnector->setQWeb(m_qWebThree);
m_web3Server.reset(new Web3Server(*m_qWebThreeConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
connect(m_qWebThree, &QWebThree::response, this, &ClientModel::apiResponse);
_context->appEngine()->rootContext()->setContextProperty("clientModel", this); _context->appEngine()->rootContext()->setContextProperty("clientModel", this);
} }
@ -68,10 +84,10 @@ ClientModel::~ClientModel()
{ {
} }
void ClientModel::apiRequest(QString const& _message) QString ClientModel::apiCall(QString const& _message)
{ {
(void)_message; m_rpcConnector->OnRequest(_message.toStdString(), nullptr);
// m_qWebThree->postMessage(_message); return m_rpcConnector->response();
} }
QString ClientModel::contractAddress() const QString ClientModel::contractAddress() const
@ -238,3 +254,6 @@ ExecutionResult ClientModel::callContract(Address const& _contract, bytes const&
return r; return r;
} }
}
}

14
mix/ClientModel.h

@ -32,9 +32,6 @@ using AssemblyDebuggerData = std::tuple<QList<QObject*>, dev::mix::QQMLMap*>;
Q_DECLARE_METATYPE(AssemblyDebuggerData) Q_DECLARE_METATYPE(AssemblyDebuggerData)
Q_DECLARE_METATYPE(dev::mix::ExecutionResult) Q_DECLARE_METATYPE(dev::mix::ExecutionResult)
class QWebThree;
class QWebThreeConnector;
namespace dev namespace dev
{ {
namespace mix namespace mix
@ -42,6 +39,7 @@ namespace mix
class AppContext; class AppContext;
class Web3Server; class Web3Server;
class RpcConnector;
/// Backend transaction config class /// Backend transaction config class
struct TransactionSettings struct TransactionSettings
@ -76,11 +74,12 @@ public:
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged)
/// @returns address of the last executed contract /// @returns address of the last executed contract
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged) Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged)
public slots:
/// ethereum.js RPC request entry point /// ethereum.js RPC request entry point
/// @param _message RPC request in Json format /// @param _message RPC request in Json format
void apiRequest(QString const& _message); /// @returns RPC response in Json format
Q_INVOKABLE QString apiCall(QString const& _message);
public slots:
/// Run the contract constructor and show debugger window. /// Run the contract constructor and show debugger window.
void debugDeployment(); void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction /// Setup state, run transaction sequence, show debugger for the last transaction
@ -123,8 +122,7 @@ private:
AppContext* m_context; AppContext* m_context;
std::atomic<bool> m_running; std::atomic<bool> m_running;
std::unique_ptr<MixClient> m_client; std::unique_ptr<MixClient> m_client;
QWebThree* m_qWebThree; std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<QWebThreeConnector> m_qWebThreeConnector;
std::unique_ptr<Web3Server> m_web3Server; std::unique_ptr<Web3Server> m_web3Server;
}; };

1
mix/CodeModel.cpp

@ -46,6 +46,7 @@ CompilationResult::CompilationResult():
m_successful(false), m_successful(false),
m_codeHash(qHash(QString())), m_codeHash(qHash(QString())),
m_contract(new QContractDefinition()), m_contract(new QContractDefinition()),
m_contractInterface("[]"),
m_codeHighlighter(new CodeHighlighter()) m_codeHighlighter(new CodeHighlighter())
{} {}

170
mix/HttpServer.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 <http://www.gnu.org/licenses/>.
*/
/** @file HttpServer.cpp
* @author Arkadiy Paronyan arkadiy@ethdev.com
* @date 2015
* Ethereum IDE client.
*/
#include <memory>
#include <QTcpSocket>
#include "HttpServer.h"
using namespace dev::mix;
HttpServer::HttpServer()
: m_port(0) , m_listen(false) , m_accept(true) , m_componentCompleted(true)
{
}
HttpServer::~HttpServer()
{
setListen(false);
}
void HttpServer::classBegin()
{
m_componentCompleted = false;
}
void HttpServer::componentComplete()
{
init();
m_componentCompleted = true;
}
QUrl HttpServer::url() const
{
QUrl url;
url.setPort(m_port);
url.setHost("localhost");
url.setScheme("http");
return url;
}
void HttpServer::setPort(int _port)
{
if (_port == m_port)
return;
m_port = _port;
emit portChanged(_port);
emit urlChanged(url());
if (m_componentCompleted && this->isListening())
updateListening();
}
QString HttpServer::errorString() const
{
return this->errorString();
}
void HttpServer::setListen(bool _listen)
{
if (_listen == m_listen)
return;
m_listen = _listen;
emit listenChanged(_listen);
if (m_componentCompleted)
updateListening();
}
void HttpServer::setAccept(bool _accept)
{
if (_accept == m_accept)
return;
m_accept = _accept;
emit acceptChanged(_accept);
}
void HttpServer::init()
{
updateListening();
}
void HttpServer::updateListening()
{
if (this->isListening())
this->close();
if (!m_listen || QTcpServer::listen(QHostAddress::LocalHost, m_port))
return;
}
void HttpServer::incomingConnection(qintptr _socket)
{
if (!m_accept)
return;
QTcpSocket* s = new QTcpSocket(this);
connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
s->setSocketDescriptor(_socket);
}
void HttpServer::readClient()
{
if (!m_accept)
return;
QTcpSocket* socket = (QTcpSocket*)sender();
try
{
if (socket->canReadLine())
{
QString hdr = QString(socket->readLine());
if (hdr.startsWith("POST"))
{
QString l;
do
l = socket->readLine();
while (!(l.isEmpty() || l == "\r" || l == "\r\n"));
QString content = socket->readAll();
QUrl url;
std::unique_ptr<HttpRequest> request(new HttpRequest(this, url, content));
clientConnected(request.get());
QTextStream os(socket);
os.setAutoDetectUnicode(true);
///@todo: allow setting response content-type, charset, etc
os << "HTTP/1.0 200 Ok\r\n"
"Content-Type: text/plain; charset=\"utf-8\"\r\n"
"\r\n";
os << request->m_response;
}
}
}
catch(...)
{
delete socket;
throw;
}
socket->close();
if (socket->state() == QTcpSocket::UnconnectedState)
delete socket;
}
void HttpServer::discardClient()
{
QTcpSocket* socket = (QTcpSocket*)sender();
socket->deleteLater();
}

123
mix/HttpServer.h

@ -0,0 +1,123 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/** @file HttpServer.h
* @author Arkadiy Paronyan arkadiy@ethdev.com
* @date 2015
* Ethereum IDE client.
*/
#pragma once
#include <QObject>
#include <QTcpServer>
#include <QUrl>
#include <QQmlParserStatus>
namespace dev
{
namespace mix
{
/// Simple http server for serving jsonrpc requests
class HttpRequest : public QObject
{
Q_OBJECT
/// Request url
Q_PROPERTY(QUrl url MEMBER m_url CONSTANT)
/// Request body contents
Q_PROPERTY(QString content MEMBER m_content CONSTANT)
private:
HttpRequest(QObject* _parent, QUrl const& _url, QString const& _content):
QObject(_parent), m_url(_url), m_content(_content)
{
}
public:
/// Set response for a request
/// @param _response Response body. If no response is set, server returns status 200 with empty body
Q_INVOKABLE void setResponse(QString const& _response) { m_response = _response; }
private:
QUrl m_url;
QString m_content;
QString m_response;
friend class HttpServer;
};
class HttpServer : public QTcpServer, public QQmlParserStatus
{
Q_OBJECT
Q_DISABLE_COPY(HttpServer)
Q_INTERFACES(QQmlParserStatus)
/// Server url
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
/// Server port
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
/// Listen for connections
Q_PROPERTY(bool listen READ listen WRITE setListen NOTIFY listenChanged)
/// Accept new connections
Q_PROPERTY(bool accept READ accept WRITE setAccept NOTIFY acceptChanged)
/// Error string if any
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
public:
explicit HttpServer();
virtual ~HttpServer();
QUrl url() const;
int port() const { return m_port; }
void setPort(int _port);
bool listen() const { return m_listen; }
void setListen(bool _listen);
bool accept() const { return m_accept; }
void setAccept(bool _accept);
QString errorString() const;
protected:
virtual void classBegin() override;
virtual void componentComplete() override;
virtual void incomingConnection(qintptr _socket) override;
signals:
void clientConnected(HttpRequest* _request);
void errorStringChanged(QString const& _errorString);
void urlChanged(QUrl const& _url);
void portChanged(int _port);
void listenChanged(bool _listen);
void acceptChanged(bool _accept);
private:
void init();
void updateListening();
void newConnection();
void serverError();
private slots:
void readClient();
void discardClient();
private:
int m_port;
bool m_listen;
bool m_accept;
bool m_componentCompleted;
};
}
}

4
mix/MixApplication.cpp

@ -36,6 +36,10 @@ using namespace dev::mix;
MixApplication::MixApplication(int _argc, char* _argv[]): MixApplication::MixApplication(int _argc, char* _argv[]):
QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get())) QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get()))
{ {
setOrganizationName(tr("Ethreum"));
setOrganizationDomain(tr("ethereum.org"));
setApplicationName(tr("Mix"));
setApplicationVersion("0.1");
#ifdef ETH_HAVE_WEBENGINE #ifdef ETH_HAVE_WEBENGINE
QtWebEngine::initialize(); QtWebEngine::initialize();
#endif #endif

124
mix/qml/CodeEditor.qml

@ -4,78 +4,80 @@ import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0 import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
Component { Item {
Item { signal editorTextChanged
signal editorTextChanged
function setText(text) { function setText(text) {
codeEditor.text = text; codeEditor.text = text;
} }
function getText() { function getText() {
return codeEditor.text; return codeEditor.text;
} }
anchors.fill: parent function setFocus() {
id: contentView codeEditor.forceActiveFocus();
width: parent.width }
height: parent.height * 0.7
Rectangle { anchors.fill: parent
id: lineColumn id: contentView
property int rowHeight: codeEditor.font.pixelSize + 3 width: parent.width
color: "#202020" height: parent.height * 0.7
width: 50 Rectangle {
height: parent.height id: lineColumn
Column { property int rowHeight: codeEditor.font.pixelSize + 3
y: -codeEditor.flickableItem.contentY + 4 color: "#202020"
width: parent.width width: 50
Repeater { height: parent.height
model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) Column {
delegate: Text { y: -codeEditor.flickableItem.contentY + 4
id: text width: parent.width
color: codeEditor.textColor Repeater {
font: codeEditor.font model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight))
width: lineColumn.width - 4 delegate: Text {
horizontalAlignment: Text.AlignRight id: text
verticalAlignment: Text.AlignVCenter color: codeEditor.textColor
height: lineColumn.rowHeight font: codeEditor.font
renderType: Text.NativeRendering width: lineColumn.width - 4
text: index + 1 horizontalAlignment: Text.AlignRight
} verticalAlignment: Text.AlignVCenter
height: lineColumn.rowHeight
renderType: Text.NativeRendering
text: index + 1
} }
} }
} }
}
TextArea { TextArea {
id: codeEditor id: codeEditor
textColor: "#EEE8D5" textColor: "#EEE8D5"
style: TextAreaStyle { style: TextAreaStyle {
backgroundColor: "#002B36" backgroundColor: "#002B36"
} }
anchors.left: lineColumn.right anchors.left: lineColumn.right
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
wrapMode: TextEdit.NoWrap wrapMode: TextEdit.NoWrap
frameVisible: false frameVisible: false
height: parent.height height: parent.height
font.family: "Monospace" font.family: "Monospace"
font.pointSize: 12 font.pointSize: 12
width: parent.width width: parent.width
tabChangesFocus: false tabChangesFocus: false
Keys.onPressed: { Keys.onPressed: {
if (event.key === Qt.Key_Tab) { if (event.key === Qt.Key_Tab) {
codeEditor.insert(codeEditor.cursorPosition, "\t"); codeEditor.insert(codeEditor.cursorPosition, "\t");
event.accepted = true; event.accepted = true;
}
} }
onTextChanged: {
editorTextChanged();
}
} }
onTextChanged: {
editorTextChanged();
}
} }
} }

15
mix/qml/CodeEditorView.qml

@ -56,26 +56,27 @@ Item {
} }
} }
CodeEditor {
id: codeEditor
}
Repeater { Repeater {
id: editors id: editors
model: editorListModel model: editorListModel
delegate: Loader { delegate: Loader {
active: false; id: loader
active: false
asynchronous: true asynchronous: true
anchors.fill: parent anchors.fill: parent
sourceComponent: codeEditor source: "CodeEditor.qml"
visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId) visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId)
onVisibleChanged: { onVisibleChanged: {
loadIfNotLoaded() loadIfNotLoaded()
if (visible && item)
loader.item.setFocus();
} }
Component.onCompleted: { Component.onCompleted: {
loadIfNotLoaded() loadIfNotLoaded()
} }
onLoaded: { doLoadDocument(item, editorListModel.get(index)) } onLoaded: {
doLoadDocument(loader.item, editorListModel.get(index))
}
function loadIfNotLoaded () { function loadIfNotLoaded () {
if(visible && !active) { if(visible && !active) {

23
mix/qml/MainContent.qml

@ -20,6 +20,7 @@ Rectangle {
property alias rightViewVisible : rightView.visible property alias rightViewVisible : rightView.visible
property alias webViewVisible : webPreview.visible property alias webViewVisible : webPreview.visible
property bool webViewHorizontal : codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally
onWidthChanged: onWidthChanged:
{ {
@ -50,6 +51,10 @@ Rectangle {
webPreview.visible = !webPreview.visible; webPreview.visible = !webPreview.visible;
} }
function toggleWebPreviewOrientation() {
codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical);
}
function rightViewVisible() { function rightViewVisible() {
return rightView.visible; return rightView.visible;
} }
@ -59,6 +64,13 @@ Rectangle {
rightView: rightPaneTabs; rightView: rightPaneTabs;
} }
Settings {
id: mainLayoutSettings
property alias codeWebOrientation: codeWebSplitter.orientation
property alias webWidth: webPreview.width
property alias webHeight: webPreview.height
}
GridLayout GridLayout
{ {
anchors.fill: parent anchors.fill: parent
@ -130,6 +142,12 @@ Rectangle {
width: parent.width - projectList.width width: parent.width - projectList.width
height: parent.height height: parent.height
SplitView { SplitView {
handleDelegate: Rectangle {
width: 4
height: 4
color: "#cccccc"
}
id: codeWebSplitter
anchors.fill: parent anchors.fill: parent
orientation: Qt.Vertical orientation: Qt.Vertical
CodeEditorView { CodeEditorView {
@ -141,7 +159,10 @@ Rectangle {
WebPreview { WebPreview {
id: webPreview id: webPreview
height: parent.height * 0.4 height: parent.height * 0.4
Layout.fillWidth: true Layout.fillWidth: codeWebSplitter.orientation === Qt.Vertical
Layout.fillHeight: codeWebSplitter.orientation === Qt.Horizontal
Layout.minimumHeight: 200
Layout.minimumWidth: 200
} }
} }
} }

17
mix/qml/NewProjectDialog.qml

@ -26,6 +26,11 @@ Window {
visible = false; visible = false;
} }
function acceptAndClose() {
close();
accepted();
}
GridLayout { GridLayout {
id: dialogContent id: dialogContent
columns: 2 columns: 2
@ -41,6 +46,10 @@ Window {
id: titleField id: titleField
focus: true focus: true
Layout.fillWidth: true Layout.fillWidth: true
Keys.onReturnPressed: {
if (okButton.enabled)
acceptAndClose();
}
} }
Label { Label {
@ -50,6 +59,10 @@ Window {
TextField { TextField {
id: pathField id: pathField
Layout.fillWidth: true Layout.fillWidth: true
Keys.onReturnPressed: {
if (okButton.enabled)
acceptAndClose();
}
} }
Button { Button {
text: qsTr("Browse") text: qsTr("Browse")
@ -63,11 +76,11 @@ Window {
anchors.right: parent.right; anchors.right: parent.right;
Button { Button {
id: okButton;
enabled: titleField.text != "" && pathField.text != "" enabled: titleField.text != "" && pathField.text != ""
text: qsTr("OK"); text: qsTr("OK");
onClicked: { onClicked: {
close(); acceptAndClose();
accepted();
} }
} }
Button { Button {

25
mix/qml/ProjectList.qml

@ -10,7 +10,7 @@ Item {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
color: "blue" color: "blue"
text: projectModel.projectData ? projectModel.projectData.title : "" text: projectModel.projectTitle
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
visible: !projectModel.isEmpty; visible: !projectModel.isEmpty;
} }
@ -117,12 +117,23 @@ Item {
contextMenu.popup(); contextMenu.popup();
} }
} }
Connections { }
target: projectModel }
onProjectLoaded: { Connections {
projectList.currentIndex = 0; target: projectModel
} onProjectLoaded: {
} projectList.currentIndex = 0;
if (projectList.currentIndex >= 0 && projectList.currentIndex < projectModel.listModel.count)
projectModel.openDocument(projectModel.listModel.get(projectList.currentIndex).documentId);
}
onProjectClosed: {
projectList.currentIndex = -1;
}
onDocumentOpened: {
if (projectList.currentItem.documentId !== document.documentId)
projectList.currentIndex = projectModel.getDocumentIndex(document.documentId);
} }
} }
} }

4
mix/qml/ProjectModel.qml

@ -26,6 +26,7 @@ Item {
property bool haveUnsavedChanges: false property bool haveUnsavedChanges: false
property string projectPath: "" property string projectPath: ""
property string projectTitle: "" property string projectTitle: ""
property string currentDocumentId: ""
property var listModel: projectListModel property var listModel: projectListModel
//interface //interface
@ -40,9 +41,12 @@ Item {
function newJsFile() { ProjectModelCode.newJsFile(); } function newJsFile() { ProjectModelCode.newJsFile(); }
//function newContract() { ProjectModelCode.newContract(); } //function newContract() { ProjectModelCode.newContract(); }
function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } function openDocument(documentId) { ProjectModelCode.openDocument(documentId); }
function openNextDocument() { ProjectModelCode.openNextDocument(); }
function openPrevDocument() { ProjectModelCode.openPrevDocument(); }
function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); } function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); }
function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); } function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); }
function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); } function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); }
function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); }
Connections { Connections {
target: appContext target: appContext

2
mix/qml/StateDialog.qml

@ -184,7 +184,7 @@ Window {
if (transactionDialog.transactionIndex < transactionsModel.count) { if (transactionDialog.transactionIndex < transactionsModel.count) {
transactionsModel.set(transactionDialog.transactionIndex, item); transactionsModel.set(transactionDialog.transactionIndex, item);
stateTransactions[index] = item; stateTransactions[transactionDialog.transactionIndex] = item;
} else { } else {
transactionsModel.append(item); transactionsModel.append(item);
stateTransactions.push(item); stateTransactions.push(item);

102
mix/qml/WebCodeEditor.qml

@ -5,64 +5,66 @@ import QtQuick.Controls.Styles 1.1
import CodeEditorExtensionManager 1.0 import CodeEditorExtensionManager 1.0
import QtWebEngine 1.0 import QtWebEngine 1.0
Component { Item {
Item { signal editorTextChanged
signal editorTextChanged property string currentText: ""
property string currentText: "" property string currentMode: ""
property string currentMode: "" property bool initialized: false
property bool initialized: false
function setText(text, mode) { function setText(text, mode) {
currentText = text; currentText = text;
currentMode = mode; currentMode = mode;
if (initialized) { if (initialized) {
editorBrowser.runJavaScript("setTextBase64(\"" + Qt.btoa(text) + "\")"); editorBrowser.runJavaScript("setTextBase64(\"" + Qt.btoa(text) + "\")");
editorBrowser.runJavaScript("setMode(\"" + mode + "\")"); editorBrowser.runJavaScript("setMode(\"" + mode + "\")");
}
editorBrowser.forceActiveFocus();
} }
setFocus();
}
function getText() { function setFocus() {
return currentText; editorBrowser.forceActiveFocus();
} }
anchors.top: parent.top function getText() {
id: codeEditorView return currentText;
}
anchors.top: parent.top
id: codeEditorView
anchors.fill: parent
WebEngineView {
id: editorBrowser
url: "qrc:///qml/html/codeeditor.html"
anchors.fill: parent anchors.fill: parent
WebEngineView { onJavaScriptConsoleMessage: {
id: editorBrowser console.log("editor: " + sourceID + ":" + lineNumber + ":" + message);
url: "qrc:///qml/html/codeeditor.html" }
anchors.fill: parent
onJavaScriptConsoleMessage: {
console.log("editor: " + sourceID + ":" + lineNumber + ":" + message);
}
onLoadingChanged: onLoadingChanged:
{ {
if (!loading) { if (!loading) {
initialized = true; initialized = true;
setText(currentText, currentMode); setText(currentText, currentMode);
runJavaScript("getTextChanged()", function(result) { }); runJavaScript("getTextChanged()", function(result) { });
pollTimer.running = true; pollTimer.running = true;
}
} }
}
Timer Timer
{ {
id: pollTimer id: pollTimer
interval: 30 interval: 30
running: false running: false
repeat: true repeat: true
onTriggered: { onTriggered: {
editorBrowser.runJavaScript("getTextChanged()", function(result) { editorBrowser.runJavaScript("getTextChanged()", function(result) {
if (result === true) { if (result === true) {
editorBrowser.runJavaScript("getText()" , function(textValue) { editorBrowser.runJavaScript("getText()" , function(textValue) {
currentText = textValue; currentText = textValue;
editorTextChanged(); editorTextChanged();
}); });
} }
}); });
}
} }
} }
} }

30
mix/qml/WebPreview.qml

@ -4,7 +4,8 @@ import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0 import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
import QtWebEngine 1.0 import QtWebEngine 1.0
import Qt.WebSockets 1.0 import QtWebEngine.experimental 1.0
import HttpServer 1.0
Item { Item {
id: webPreview id: webPreview
@ -53,7 +54,7 @@ Item {
onAppLoaded: { onAppLoaded: {
//We need to load the container using file scheme so that web security would allow loading local files in iframe //We need to load the container using file scheme so that web security would allow loading local files in iframe
var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html"); var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html");
webView.loadHtml(containerPage, "file:///") webView.loadHtml(containerPage, "file:///WebContainer.html")
} }
} }
@ -104,20 +105,16 @@ Item {
id: pageListModel id: pageListModel
} }
WebSocketServer { HttpServer {
id: socketServer id: httpServer
listen: true listen: true
name: "mix" accept: true
onClientConnected: port: 8893
{ onClientConnected: {
webSocket.onTextMessageReceived.connect(function(message) { console.log(_request.content);
console.log("rpc_request: " + message); var response = clientModel.apiCall(_request.content);
clientModel.apiRequest(message); console.log(response);
}); _request.setResponse(response);
clientModel.onApiResponse.connect(function(message) {
console.log("rpc_response: " + message);
webSocket.sendTextMessage(message);
});
} }
} }
@ -152,13 +149,14 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
id: webView id: webView
experimental.settings.localContentCanAccessRemoteUrls: true
onJavaScriptConsoleMessage: { onJavaScriptConsoleMessage: {
console.log(sourceID + ":" + lineNumber + ":" + message); console.log(sourceID + ":" + lineNumber + ":" + message);
} }
onLoadingChanged: { onLoadingChanged: {
if (!loading) { if (!loading) {
initialized = true; initialized = true;
webView.runJavaScript("init(\"" + socketServer.url + "\")"); webView.runJavaScript("init(\"" + httpServer.url + "\")");
if (pendingPageUrl) if (pendingPageUrl)
setPreviewUrl(pendingPageUrl); setPreviewUrl(pendingPageUrl);
} }

8
mix/qml/html/WebContainer.html

@ -16,17 +16,17 @@ reloadPage = function() {
preview.contentWindow.location.reload(); preview.contentWindow.location.reload();
}; };
updateContract = function(address, definition) { updateContract = function(address, contractFace) {
if (window.web3) { if (window.web3) {
window.contractAddress = address; window.contractAddress = address;
window.contractDefinition = definition; window.contractInterface = contractFace;
window.contract = window.web3.eth.contract(address, definition); window.contract = window.web3.eth.contract(address, contractFace);
} }
}; };
init = function(url) { init = function(url) {
web3 = require('web3'); web3 = require('web3');
web3.setProvider(new web3.providers.WebSocketProvider(url)); web3.setProvider(new web3.providers.HttpSyncProvider(url));
window.web3 = web3; window.web3 = web3;
}; };

2
mix/qml/html/cm/solidity.js

@ -12,7 +12,7 @@
CodeMirror.defineMode("solidity", function(config) { CodeMirror.defineMode("solidity", function(config) {
var indentUnit = config.indentUnit; var indentUnit = config.indentUnit;
var keywords = { "delete":true, "break":true, "case":true, "const":true, "continue":true, "contract":true, "default":true, var keywords = { "delete":true, "break":true, "case":true, "constant":true, "continue":true, "contract":true, "default":true,
"do":true, "else":true, "is":true, "for":true, "function":true, "if":true, "import":true, "mapping":true, "new":true, "do":true, "else":true, "is":true, "for":true, "function":true, "if":true, "import":true, "mapping":true, "new":true,
"public":true, "private":true, "return":true, "returns":true, "struct":true, "switch":true, "var":true, "while":true, "public":true, "private":true, "return":true, "returns":true, "struct":true, "switch":true, "var":true, "while":true,
"int":true, "uint":true, "hash":true, "bool":true, "string":true, "string0":true, "text":true, "real":true, "int":true, "uint":true, "hash":true, "bool":true, "string":true, "string0":true, "text":true, "real":true,

41
mix/qml/js/ProjectModel.js

@ -104,7 +104,7 @@ function addFile(fileName) {
return docData.documentId; return docData.documentId;
} }
function findDocument(documentId) function getDocumentIndex(documentId)
{ {
for (var i = 0; i < projectListModel.count; i++) for (var i = 0; i < projectListModel.count; i++)
if (projectListModel.get(i).documentId === documentId) if (projectListModel.get(i).documentId === documentId)
@ -114,7 +114,38 @@ function findDocument(documentId)
} }
function openDocument(documentId) { function openDocument(documentId) {
documentOpened(projectListModel.get(findDocument(documentId))); if (documentId !== currentDocumentId) {
documentOpened(projectListModel.get(getDocumentIndex(documentId)));
currentDocumentId = documentId;
}
}
function openNextDocument() {
var docIndex = getDocumentIndex(currentDocumentId);
var nextDocId = "";
while (nextDocId === "") {
docIndex++;
if (docIndex >= projectListModel.count)
docIndex = 0;
var document = projectListModel.get(docIndex);
if (document.isText)
nextDocId = document.documentId;
}
openDocument(nextDocId);
}
function openPrevDocument() {
var docIndex = getDocumentIndex(currentDocumentId);
var prevDocId = "";
while (prevDocId === "") {
docIndex--;
if (docIndex < 0)
docIndex = projectListModel.count - 1;
var document = projectListModel.get(docIndex);
if (document.isText)
prevDocId = document.documentId;
}
openDocument(prevDocId);
} }
function doCloseProject() { function doCloseProject() {
@ -160,7 +191,7 @@ function doAddExistingFiles(files) {
} }
function renameDocument(documentId, newName) { function renameDocument(documentId, newName) {
var i = findDocument(documentId); var i = getDocumentIndex(documentId);
var document = projectListModel.get(i); var document = projectListModel.get(i);
if (!document.isContract) { if (!document.isContract) {
var sourcePath = document.path; var sourcePath = document.path;
@ -174,12 +205,12 @@ function renameDocument(documentId, newName) {
} }
function getDocument(documentId) { function getDocument(documentId) {
var i = findDocument(documentId); var i = getDocumentIndex(documentId);
return projectListModel.get(i); return projectListModel.get(i);
} }
function removeDocument(documentId) { function removeDocument(documentId) {
var i = findDocument(documentId); var i = getDocumentIndex(documentId);
var document = projectListModel.get(i); var document = projectListModel.get(i);
if (!document.isContract) { if (!document.isContract) {
projectListModel.remove(i); projectListModel.remove(i);

38
mix/qml/main.qml

@ -39,8 +39,12 @@ ApplicationWindow {
} }
Menu { Menu {
title: qsTr("Windows") title: qsTr("Windows")
MenuItem { action: showHideRightPanel } MenuItem { action: openNextDocumentAction }
MenuItem { action: toggleWebPreview } MenuItem { action: openPrevDocumentAction }
MenuSeparator {}
MenuItem { action: showHideRightPanelAction }
MenuItem { action: toggleWebPreviewAction }
MenuItem { action: toggleWebPreviewOrientationAction }
} }
} }
@ -90,7 +94,7 @@ ApplicationWindow {
} }
Action { Action {
id: toggleWebPreview id: toggleWebPreviewAction
text: "Show Web View" text: "Show Web View"
shortcut: "F2" shortcut: "F2"
checkable: true checkable: true
@ -99,7 +103,16 @@ ApplicationWindow {
} }
Action { Action {
id: showHideRightPanel id: toggleWebPreviewOrientationAction
text: "Horizontal Web View"
shortcut: ""
checkable: true
checked: mainContent.webViewHorizontal
onTriggered: mainContent.toggleWebPreviewOrientation();
}
Action {
id: showHideRightPanelAction
text: "Show Right View" text: "Show Right View"
shortcut: "F7" shortcut: "F7"
checkable: true checkable: true
@ -170,4 +183,21 @@ ApplicationWindow {
enabled: !projectModel.isEmpty enabled: !projectModel.isEmpty
onTriggered: projectModel.closeProject(); onTriggered: projectModel.closeProject();
} }
Action {
id: openNextDocumentAction
text: qsTr("Next Document")
shortcut: "Ctrl+Tab"
enabled: !projectModel.isEmpty
onTriggered: projectModel.openNextDocument();
}
Action {
id: openPrevDocumentAction
text: qsTr("Previous Document")
shortcut: "Ctrl+Shift+Tab"
enabled: !projectModel.isEmpty
onTriggered: projectModel.openPrevDocument();
}
} }

Loading…
Cancel
Save