From 9efb644ce414db9a348026ed4315f2a1b56f50a3 Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 8 Jan 2015 17:02:18 +0100 Subject: [PATCH] started project model --- mix/AppContext.cpp | 11 ++- mix/AppContext.h | 2 + mix/CMakeLists.txt | 3 + mix/ConstantCompilationControl.cpp | 1 + mix/FileIo.cpp | 68 ++++++++++++++ mix/FileIo.h | 49 ++++++++++ mix/qml.qrc | 17 ++-- mix/qml/MainContent.qml | 10 ++- mix/qml/NewProjectDialog.qml | 92 +++++++++++++++++++ mix/qml/ProjectList.qml | 39 ++++++++ mix/qml/ProjectModel.qml | 140 +++++++++++++++++++++++++++++ mix/qml/StateList.qml | 14 +-- 12 files changed, 431 insertions(+), 15 deletions(-) create mode 100644 mix/FileIo.cpp create mode 100644 mix/FileIo.h create mode 100644 mix/qml/NewProjectDialog.qml create mode 100644 mix/qml/ProjectList.qml create mode 100644 mix/qml/ProjectModel.qml diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 8eae2b230..65ca4cb20 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -32,8 +32,10 @@ #include #include #include -#include "AppContext.h" #include "CodeModel.h" +#include "FileIo.h" +#include "AppContext.h" + using namespace dev; using namespace dev::eth; @@ -46,8 +48,13 @@ AppContext::AppContext(QQmlApplicationEngine* _engine) 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_fileIo.reset(new FileIo()); m_applicationEngine->rootContext()->setContextProperty("appContext", this); + qmlRegisterType("org.ethereum.qml", 1, 0, "FileIo"); + qmlRegisterSingletonType(QUrl("qrc:/qml/ProjectModel.qml"), "org.ethereum.qml.ProjectModel", 1, 0, "ProjectModel"); + m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get()); + m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get()); + } AppContext::~AppContext() diff --git a/mix/AppContext.h b/mix/AppContext.h index a7fa8a017..02d3ef032 100644 --- a/mix/AppContext.h +++ b/mix/AppContext.h @@ -47,6 +47,7 @@ namespace mix { class CodeModel; +class FileIo; /** * @brief Provides access to application scope variable. */ @@ -73,6 +74,7 @@ private: QQmlApplicationEngine* m_applicationEngine; //owned by app std::unique_ptr m_webThree; std::unique_ptr m_codeModel; + std::unique_ptr m_fileIo; public slots: /// Delete the current instance when application quit. diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 555f6290f..6cef4470c 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -46,3 +46,6 @@ eth_install_executable(${EXECUTABLE} QMLDIR ${CMAKE_CURRENT_SOURCE_DIR}/qml ) +#add qml files to project tree in Qt creator +file(GLOB_RECURSE QMLFILES "qml/*.*") +add_custom_target(dummy SOURCES ${QMLFILES}) diff --git a/mix/ConstantCompilationControl.cpp b/mix/ConstantCompilationControl.cpp index e2d69bf62..c9f21c11b 100644 --- a/mix/ConstantCompilationControl.cpp +++ b/mix/ConstantCompilationControl.cpp @@ -34,6 +34,7 @@ using namespace dev::mix; + ConstantCompilationControl::ConstantCompilationControl(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::Tab) { connect(_context->codeModel(), &CodeModel::compilationComplete, this, &ConstantCompilationControl::update); diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp new file mode 100644 index 000000000..2d37422a9 --- /dev/null +++ b/mix/FileIo.cpp @@ -0,0 +1,68 @@ +/* + 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 FileIo.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include +#include "FileIo.h" + +using namespace dev::mix; + +void FileIo::makeDir(QString const& _path) +{ + QDir dirPath(_path); + if (!dirPath.exists()) + dirPath.mkpath(dirPath.path()); +} + +QString FileIo::readFile(QString const& _path) +{ + QFile file(_path); + if(file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream stream(&file); + QString data = stream.readAll(); + return data; + } + else + throw std::runtime_error(tr("Error reading file %1").arg(_path).toStdString()); +} + +void FileIo::writeFile(QString const& _path, QString const& _data) +{ + QFile file(_path); + if(file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream stream(&file); + stream << _data; + } + else + throw std::runtime_error(tr("Error writing file %1").arg(_path).toStdString()); +} + +void FileIo::copyFile(QString const& _sourcePath, QString const& _destPath) +{ + if (!QFile::copy(_sourcePath, _destPath)) + throw std::runtime_error(tr("Error copying file %1 to %2").arg(_sourcePath).arg(_destPath).toStdString()); +} diff --git a/mix/FileIo.h b/mix/FileIo.h new file mode 100644 index 000000000..ed1326037 --- /dev/null +++ b/mix/FileIo.h @@ -0,0 +1,49 @@ +/* + 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 FileIo.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include + +namespace dev +{ +namespace mix +{ + +///File services for QML +class FileIo : public QObject +{ + Q_OBJECT + +public: + /// Create a directory if it does not exist. Throws on failure. + Q_INVOKABLE void makeDir(QString const& _path); + /// Read file contents to a string. Throws on failure. + Q_INVOKABLE QString readFile(QString const& _path); + /// Write contents to a file. Throws on failure. + Q_INVOKABLE void writeFile(QString const& _path, QString const& _data); + /// Copy a file from _sourcePath to _destPath. Throws on failure. + Q_INVOKABLE void copyFile(QString const& _sourcePath, QString const& _destPath); +}; + +} +} diff --git a/mix/qml.qrc b/mix/qml.qrc index 7e731b6f4..a6215b243 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -1,16 +1,19 @@ - qml/BasicContent.qml qml/main.qml - qml/MainContent.qml - qml/TabStyle.qml - qml/Debugger.qml - qml/js/Debugger.js + qml/AlertMessageDialog.qml + qml/BasicContent.qml qml/BasicMessage.qml - qml/TransactionDialog.qml + qml/Debugger.qml + qml/MainContent.qml qml/ModalDialog.qml - qml/AlertMessageDialog.qml + qml/ProjectList.qml qml/StateDialog.qml qml/StateList.qml + qml/TabStyle.qml + qml/TransactionDialog.qml + qml/js/Debugger.js + qml/NewProjectDialog.qml + qml/ProjectModel.qml diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 7f9a27d58..6cd2f5baa 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -20,9 +20,17 @@ Rectangle { SplitView { orientation: Qt.Horizontal anchors.fill: parent + + ProjectList { + anchors.left: parent.left + width: parent.width * 0.2 + height: parent.height + Layout.minimumWidth: 20 + } + SplitView { //anchors.fill: parent - width: parent.width * 0.8 + width: parent.width * 0.6 orientation: Qt.Vertical Rectangle { anchors.top: parent.top diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml new file mode 100644 index 000000000..b0853ba26 --- /dev/null +++ b/mix/qml/NewProjectDialog.qml @@ -0,0 +1,92 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.0 +import QtQuick.Dialogs 1.2 + +Window { + + modality: Qt.WindowModal + + width: 640 + height: 280 + + visible: false + + property alias projectTitle : titleField.text + property alias projectPath : pathField.text + signal accepted + + function open() { + visible = true; + titleField.focus = true; + } + + function close() { + visible = false; + } + + 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("Path") + } + RowLayout { + TextField { + id: pathField + Layout.fillWidth: true + } + Button { + text: qsTr("Browse") + onClicked: createProjectFileDialog.open() + } + } + + RowLayout + { + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + enabled: titleField.text != "" && pathField.text != "" + text: qsTr("Ok"); + onClicked: { + close(); + accepted(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); + } + } + } + + FileDialog { + id: createProjectFileDialog + visible: false + title: qsTr("Please choose a path for the project") + selectFolder: true + onAccepted: { + var u = createProjectFileDialog.fileUrl.toString(); + if (u.indexOf("file://") == 0) + u = u.substring(7, u.length) + pathField.text = u; + } + } +} diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml new file mode 100644 index 000000000..60f1de898 --- /dev/null +++ b/mix/qml/ProjectList.qml @@ -0,0 +1,39 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import org.ethereum.qml.ProjectModel 1.0 + +Item { + ListView { + model: ProjectModel.listModel + delegate: renderDelegate + } + + 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 + } + } + } + } + + Action { + id: createProjectAction + text: qsTr("&New project") + shortcut: "Ctrl+N" + enabled: true; + onTriggered: ProjectModel.createProject(); + } +} diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml new file mode 100644 index 000000000..26896ea5f --- /dev/null +++ b/mix/qml/ProjectModel.qml @@ -0,0 +1,140 @@ +pragma Singleton + +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtQuick.Dialogs 1.1 +import Qt.labs.settings 1.0 + +Item { + id: projectModel + + signal projectClosed + signal projectLoaded + + property bool isEmpty: projectFile === "" + readonly property string projectFileName: ".mix" + + property bool haveUnsavedChanges: false + property string projectFile: "" + property var projectData: null + property var listModel: projectListModel + + function saveAll() { + saveProject(); + } + + function createProject() { + closeProject(); + newProjectDialog.open(); + } + + function closeProject() { + console.log("closing project"); + if (haveUnsavedChanges) + saveMessageDialog.open(); + else + doCloseProject(); + } + + function saveProject() { + if (!isEmpty) { + var json = JSON.stringify(projectData); + fileIo.writeFile(projectFile, json) + } + } + + function loadProject(path) { + if (!isEmpty) + closeProject(); + console.log("loading project at " + path); + var json = fileIo.readFile(path); + projectData = JSON.parse(json); + projectFile = path; + if (!projectData.files) + projectData.files = []; + + for(var i = 0; i < projectData.files; i++) { + var p = projectData.files[i]; + projectListModel.append({ + path: p, + name: p.substring(p.lastIndexOf("/") + 1, p.length) + }); + } + onProjectLoaded(); + } + + function doCloseProject() { + projectListModel.clear(); + projectFile = ""; + projectData = null; + projectClosed(); + } + + function doCreateProject(title, path) { + if (!isEmpty) + closeProject(); + console.log("creating project " + title + " at " + path); + if (path[path.length - 1] !== "/") + path += "/"; + var dirPath = path + title; + fileIo.makeDir(dirPath); + var projectFile = dirPath + "/" + projectFileName; + fileIo.writeFile(projectFile, ""); + loadProject(projectFile); + } + + NewProjectDialog { + id: newProjectDialog + visible: false + onAccepted: { + var title = newProjectDialog.projectTitle; + var path = newProjectDialog.projectPath; + projectModel.doCreateProject(title, path); + } + } + + MessageDialog { + id: saveMessageDialog + title: qsTr("Project") + text: qsTr("Do you want to save changes?") + standardButtons: StandardButton.Ok | StandardButton.Cancel + icon: StandardIcon.Question + onAccepted: { + projectModel.saveAll(); + projectModel.doCloseProject(); + } + onRejected: { + projectModel.doCloseProject(); + } + } + + ListModel { + id: projectListModel + } + + 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 + } + } + } + } + + Settings { + id: projectSettings + property string lastProjectPath; + } +} diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index 152a35671..ceed6513b 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -3,6 +3,7 @@ import QtQuick.Controls.Styles 1.2 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 +import org.ethereum.qml.ProjectModel 1.0 Rectangle { color: "transparent" @@ -15,9 +16,12 @@ Rectangle { property var stateList: [] Connections { - target: appContext + target: ProjectModel + onProjectClosed: { + stateListModel.clear(); + } onProjectLoaded: { - var items = JSON.parse(_json); + var items = target.projectData.states; for(var i = 0; i < items.length; i++) { stateListModel.append(items[i]); stateList.push(items[i]) @@ -82,8 +86,8 @@ Rectangle { } function save() { - var json = JSON.stringify(stateList); - appContext.saveProject(json); + console.log(parent.id); + ProjectModel.saveProject(); } } @@ -124,7 +128,7 @@ Rectangle { Action { id: addStateAction text: "&Add State" - shortcut: "Ctrl+N" + shortcut: "Ctrl+T" enabled: codeModel.hasContract && !debugModel.running; onTriggered: stateListModel.addState(); }