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 "AppContext.h"
#include "QEther.h"
#include <libwebthree/WebThree.h>
#include "HttpServer.h"
using namespace dev;
using namespace dev::eth;
@ -44,19 +44,9 @@ const QString c_projectFileName = "project.mix";
AppContext::AppContext(QQmlApplicationEngine* _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_clientModel.reset(new ClientModel(this));
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()
@ -82,6 +72,7 @@ void AppContext::load()
}
m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel);
qmlRegisterType<CodeEditorExtensionManager>("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager");
qmlRegisterType<HttpServer>("HttpServer", 1, 0, "HttpServer");
m_applicationEngine->load(QUrl("qrc:/qml/main.qml"));
appLoaded();
}

9
mix/AppContext.h

@ -32,14 +32,6 @@
#include <QObject>
class QQmlApplicationEngine;
namespace dev
{
class WebThreeDirect;
namespace solidity
{
class CompilerStack;
}
}
namespace dev
{
@ -77,7 +69,6 @@ signals:
private:
QQmlApplicationEngine* m_applicationEngine; //owned by app
std::unique_ptr<dev::WebThreeDirect> m_webThree;
std::unique_ptr<CodeModel> m_codeModel;
std::unique_ptr<ClientModel> m_clientModel;
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)
include_directories(..)
find_package (Qt5WebEngine)
find_package (Qt5WebEngine QUIET)
qt5_add_resources(UI_RESOURCES qml.qrc)
file(GLOB HEADERS "*.h")
@ -34,8 +34,11 @@ eth_add_executable(${EXECUTABLE}
target_link_libraries(${EXECUTABLE} Qt5::Core)
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} qwebthree)
target_link_libraries(${EXECUTABLE} ethereum)
target_link_libraries(${EXECUTABLE} evm)
target_link_libraries(${EXECUTABLE} ethcore)

41
mix/ClientModel.cpp

@ -22,9 +22,9 @@
#include <QDebug>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include <jsonrpccpp/server.h>
#include <libdevcore/CommonJS.h>
#include <libethereum/Transaction.h>
#include <libqwebthree/QWebThree.h>
#include "AppContext.h"
#include "DebuggingStateWrapper.h"
#include "QContractDefinition.h"
@ -38,10 +38,30 @@
using namespace dev;
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):
m_context(_context), m_running(false), m_qWebThree(nullptr)
m_context(_context), m_running(false), m_rpcConnector(new RpcConnector())
{
qRegisterMetaType<QBigInt*>("QBigInt*");
qRegisterMetaType<QEther*>("QEther*");
@ -55,11 +75,7 @@ ClientModel::ClientModel(AppContext* _context):
connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient());
m_qWebThree = new QWebThree(this);
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);
m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
_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_qWebThree->postMessage(_message);
m_rpcConnector->OnRequest(_message.toStdString(), nullptr);
return m_rpcConnector->response();
}
QString ClientModel::contractAddress() const
@ -238,3 +254,6 @@ ExecutionResult ClientModel::callContract(Address const& _contract, bytes const&
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(dev::mix::ExecutionResult)
class QWebThree;
class QWebThreeConnector;
namespace dev
{
namespace mix
@ -42,6 +39,7 @@ namespace mix
class AppContext;
class Web3Server;
class RpcConnector;
/// Backend transaction config class
struct TransactionSettings
@ -76,11 +74,12 @@ public:
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged)
/// @returns address of the last executed contract
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged)
public slots:
/// ethereum.js RPC request entry point
/// @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.
void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction
@ -123,8 +122,7 @@ private:
AppContext* m_context;
std::atomic<bool> m_running;
std::unique_ptr<MixClient> m_client;
QWebThree* m_qWebThree;
std::unique_ptr<QWebThreeConnector> m_qWebThreeConnector;
std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server;
};

1
mix/CodeModel.cpp

@ -46,6 +46,7 @@ CompilationResult::CompilationResult():
m_successful(false),
m_codeHash(qHash(QString())),
m_contract(new QContractDefinition()),
m_contractInterface("[]"),
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[]):
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
QtWebEngine::initialize();
#endif

124
mix/qml/CodeEditor.qml

@ -4,78 +4,80 @@ import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1
Component {
Item {
signal editorTextChanged
Item {
signal editorTextChanged
function setText(text) {
codeEditor.text = text;
}
function setText(text) {
codeEditor.text = text;
}
function getText() {
return codeEditor.text;
}
function getText() {
return codeEditor.text;
}
anchors.fill: parent
id: contentView
width: parent.width
height: parent.height * 0.7
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
}
function setFocus() {
codeEditor.forceActiveFocus();
}
anchors.fill: parent
id: contentView
width: parent.width
height: parent.height * 0.7
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"
}
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
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
height: parent.height
font.family: "Monospace"
font.pointSize: 12
width: parent.width
tabChangesFocus: false
Keys.onPressed: {
if (event.key === Qt.Key_Tab) {
codeEditor.insert(codeEditor.cursorPosition, "\t");
event.accepted = true;
}
tabChangesFocus: false
Keys.onPressed: {
if (event.key === Qt.Key_Tab) {
codeEditor.insert(codeEditor.cursorPosition, "\t");
event.accepted = true;
}
onTextChanged: {
editorTextChanged();
}
}
onTextChanged: {
editorTextChanged();
}
}
}

15
mix/qml/CodeEditorView.qml

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

23
mix/qml/MainContent.qml

@ -20,6 +20,7 @@ Rectangle {
property alias rightViewVisible : rightView.visible
property alias webViewVisible : webPreview.visible
property bool webViewHorizontal : codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally
onWidthChanged:
{
@ -50,6 +51,10 @@ Rectangle {
webPreview.visible = !webPreview.visible;
}
function toggleWebPreviewOrientation() {
codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical);
}
function rightViewVisible() {
return rightView.visible;
}
@ -59,6 +64,13 @@ Rectangle {
rightView: rightPaneTabs;
}
Settings {
id: mainLayoutSettings
property alias codeWebOrientation: codeWebSplitter.orientation
property alias webWidth: webPreview.width
property alias webHeight: webPreview.height
}
GridLayout
{
anchors.fill: parent
@ -130,6 +142,12 @@ Rectangle {
width: parent.width - projectList.width
height: parent.height
SplitView {
handleDelegate: Rectangle {
width: 4
height: 4
color: "#cccccc"
}
id: codeWebSplitter
anchors.fill: parent
orientation: Qt.Vertical
CodeEditorView {
@ -141,7 +159,10 @@ Rectangle {
WebPreview {
id: webPreview
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;
}
function acceptAndClose() {
close();
accepted();
}
GridLayout {
id: dialogContent
columns: 2
@ -41,6 +46,10 @@ Window {
id: titleField
focus: true
Layout.fillWidth: true
Keys.onReturnPressed: {
if (okButton.enabled)
acceptAndClose();
}
}
Label {
@ -50,6 +59,10 @@ Window {
TextField {
id: pathField
Layout.fillWidth: true
Keys.onReturnPressed: {
if (okButton.enabled)
acceptAndClose();
}
}
Button {
text: qsTr("Browse")
@ -63,11 +76,11 @@ Window {
anchors.right: parent.right;
Button {
id: okButton;
enabled: titleField.text != "" && pathField.text != ""
text: qsTr("OK");
onClicked: {
close();
accepted();
acceptAndClose();
}
}
Button {

25
mix/qml/ProjectList.qml

@ -10,7 +10,7 @@ Item {
Text {
Layout.fillWidth: true
color: "blue"
text: projectModel.projectData ? projectModel.projectData.title : ""
text: projectModel.projectTitle
horizontalAlignment: Text.AlignHCenter
visible: !projectModel.isEmpty;
}
@ -117,12 +117,23 @@ Item {
contextMenu.popup();
}
}
Connections {
target: projectModel
onProjectLoaded: {
projectList.currentIndex = 0;
}
}
}
}
Connections {
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 string projectPath: ""
property string projectTitle: ""
property string currentDocumentId: ""
property var listModel: projectListModel
//interface
@ -40,9 +41,12 @@ Item {
function newJsFile() { ProjectModelCode.newJsFile(); }
//function newContract() { ProjectModelCode.newContract(); }
function openDocument(documentId) { ProjectModelCode.openDocument(documentId); }
function openNextDocument() { ProjectModelCode.openNextDocument(); }
function openPrevDocument() { ProjectModelCode.openPrevDocument(); }
function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); }
function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); }
function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); }
function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); }
Connections {
target: appContext

2
mix/qml/StateDialog.qml

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

102
mix/qml/WebCodeEditor.qml

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

30
mix/qml/WebPreview.qml

@ -4,7 +4,8 @@ import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1
import QtWebEngine 1.0
import Qt.WebSockets 1.0
import QtWebEngine.experimental 1.0
import HttpServer 1.0
Item {
id: webPreview
@ -53,7 +54,7 @@ Item {
onAppLoaded: {
//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");
webView.loadHtml(containerPage, "file:///")
webView.loadHtml(containerPage, "file:///WebContainer.html")
}
}
@ -104,20 +105,16 @@ Item {
id: pageListModel
}
WebSocketServer {
id: socketServer
HttpServer {
id: httpServer
listen: true
name: "mix"
onClientConnected:
{
webSocket.onTextMessageReceived.connect(function(message) {
console.log("rpc_request: " + message);
clientModel.apiRequest(message);
});
clientModel.onApiResponse.connect(function(message) {
console.log("rpc_response: " + message);
webSocket.sendTextMessage(message);
});
accept: true
port: 8893
onClientConnected: {
console.log(_request.content);
var response = clientModel.apiCall(_request.content);
console.log(response);
_request.setResponse(response);
}
}
@ -152,13 +149,14 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
id: webView
experimental.settings.localContentCanAccessRemoteUrls: true
onJavaScriptConsoleMessage: {
console.log(sourceID + ":" + lineNumber + ":" + message);
}
onLoadingChanged: {
if (!loading) {
initialized = true;
webView.runJavaScript("init(\"" + socketServer.url + "\")");
webView.runJavaScript("init(\"" + httpServer.url + "\")");
if (pendingPageUrl)
setPreviewUrl(pendingPageUrl);
}

8
mix/qml/html/WebContainer.html

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

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

@ -12,7 +12,7 @@
CodeMirror.defineMode("solidity", function(config) {
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,
"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,

41
mix/qml/js/ProjectModel.js

@ -104,7 +104,7 @@ function addFile(fileName) {
return docData.documentId;
}
function findDocument(documentId)
function getDocumentIndex(documentId)
{
for (var i = 0; i < projectListModel.count; i++)
if (projectListModel.get(i).documentId === documentId)
@ -114,7 +114,38 @@ function findDocument(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() {
@ -160,7 +191,7 @@ function doAddExistingFiles(files) {
}
function renameDocument(documentId, newName) {
var i = findDocument(documentId);
var i = getDocumentIndex(documentId);
var document = projectListModel.get(i);
if (!document.isContract) {
var sourcePath = document.path;
@ -174,12 +205,12 @@ function renameDocument(documentId, newName) {
}
function getDocument(documentId) {
var i = findDocument(documentId);
var i = getDocumentIndex(documentId);
return projectListModel.get(i);
}
function removeDocument(documentId) {
var i = findDocument(documentId);
var i = getDocumentIndex(documentId);
var document = projectListModel.get(i);
if (!document.isContract) {
projectListModel.remove(i);

38
mix/qml/main.qml

@ -39,8 +39,12 @@ ApplicationWindow {
}
Menu {
title: qsTr("Windows")
MenuItem { action: showHideRightPanel }
MenuItem { action: toggleWebPreview }
MenuItem { action: openNextDocumentAction }
MenuItem { action: openPrevDocumentAction }
MenuSeparator {}
MenuItem { action: showHideRightPanelAction }
MenuItem { action: toggleWebPreviewAction }
MenuItem { action: toggleWebPreviewOrientationAction }
}
}
@ -90,7 +94,7 @@ ApplicationWindow {
}
Action {
id: toggleWebPreview
id: toggleWebPreviewAction
text: "Show Web View"
shortcut: "F2"
checkable: true
@ -99,7 +103,16 @@ ApplicationWindow {
}
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"
shortcut: "F7"
checkable: true
@ -170,4 +183,21 @@ ApplicationWindow {
enabled: !projectModel.isEmpty
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