From 715e0a4745127beb035cfa1c39cb233244f665d3 Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 9 Jan 2015 14:34:12 +0100 Subject: [PATCH] continue project model implementation --- mix/CodeEditorExtensionManager.cpp | 32 +------- mix/CodeEditorExtensionManager.h | 5 -- mix/FileIo.cpp | 29 ++++--- mix/FileIo.h | 20 +++-- mix/MixApplication.cpp | 1 + mix/qml.qrc | 3 + mix/qml/CodeEditor.qml | 81 +++++++++++++++++++ mix/qml/CodeEditorModel.qml | 13 +++ mix/qml/CodeEditorView.qml | 80 +++++++++++++++++++ mix/qml/MainContent.qml | 72 ++--------------- mix/qml/NewProjectDialog.qml | 12 +-- mix/qml/ProjectList.qml | 28 +++++-- mix/qml/ProjectModel.qml | 123 ++++++++++++++++++++--------- mix/qml/StateList.qml | 2 + mix/qml/main.qml | 53 +++++++++++++ 15 files changed, 384 insertions(+), 170 deletions(-) create mode 100644 mix/qml/CodeEditor.qml create mode 100644 mix/qml/CodeEditorModel.qml create mode 100644 mix/qml/CodeEditorView.qml diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp index 48c928a1f..14795c223 100644 --- a/mix/CodeEditorExtensionManager.cpp +++ b/mix/CodeEditorExtensionManager.cpp @@ -51,15 +51,6 @@ void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) if (!_editor) return; - QVariant doc = _editor->property("textDocument"); - if (doc.canConvert()) - { - QQuickTextDocument* qqdoc = doc.value(); - if (qqdoc) - { - m_doc = qqdoc->textDocument(); - } - } } void CodeEditorExtensionManager::initExtensions() @@ -67,7 +58,6 @@ void CodeEditorExtensionManager::initExtensions() 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); @@ -97,35 +87,21 @@ void CodeEditorExtensionManager::initExtension(std::shared_ptr _ext) m_features.append(_ext); } -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()); +// 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); +// m_appContext->codeModel()->updateFormatting(m_doc); } void CodeEditorExtensionManager::setRightTabView(QQuickItem* _tabView) { m_rightTabView = _tabView; + initExtensions(); //TODO: this is not the right place for it } void CodeEditorExtensionManager::setTabView(QQuickItem* _tabView) diff --git a/mix/CodeEditorExtensionManager.h b/mix/CodeEditorExtensionManager.h index 46ee6569f..e910b62d3 100644 --- a/mix/CodeEditorExtensionManager.h +++ b/mix/CodeEditorExtensionManager.h @@ -43,7 +43,6 @@ class CodeEditorExtensionManager: public QObject { Q_OBJECT - Q_PROPERTY(QQuickItem* editor MEMBER m_editor WRITE setEditor) Q_PROPERTY(QQuickItem* tabView MEMBER m_tabView WRITE setTabView) Q_PROPERTY(QQuickItem* rightTabView MEMBER m_rightTabView WRITE setRightTabView) @@ -54,8 +53,6 @@ public: void initExtensions(); /// Initialize extension. void initExtension(std::shared_ptr); - /// Set current text editor. - void setEditor(QQuickItem*); /// Set current tab view void setTabView(QQuickItem*); /// Set current right tab view. @@ -66,11 +63,9 @@ private slots: 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/FileIo.cpp b/mix/FileIo.cpp index 2d37422a9..4ecbc9c08 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -25,20 +25,23 @@ #include #include #include +#include #include "FileIo.h" using namespace dev::mix; -void FileIo::makeDir(QString const& _path) +void FileIo::makeDir(QString const& _url) { - QDir dirPath(_path); + QUrl url(_url); + QDir dirPath(url.path()); if (!dirPath.exists()) dirPath.mkpath(dirPath.path()); } -QString FileIo::readFile(QString const& _path) +QString FileIo::readFile(QString const& _url) { - QFile file(_path); + QUrl url(_url); + QFile file(url.path()); if(file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); @@ -46,23 +49,27 @@ QString FileIo::readFile(QString const& _path) return data; } else - throw std::runtime_error(tr("Error reading file %1").arg(_path).toStdString()); + error(tr("Error reading file %1").arg(_url)); + return QString(); } -void FileIo::writeFile(QString const& _path, QString const& _data) +void FileIo::writeFile(QString const& _url, QString const& _data) { - QFile file(_path); + QUrl url(_url); + QFile file(url.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()); + error(tr("Error writing file %1").arg(_url)); } -void FileIo::copyFile(QString const& _sourcePath, QString const& _destPath) +void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl) { - if (!QFile::copy(_sourcePath, _destPath)) - throw std::runtime_error(tr("Error copying file %1 to %2").arg(_sourcePath).arg(_destPath).toStdString()); + QUrl sourceUrl(_sourceUrl); + QUrl destUrl(_destUrl); + if (!QFile::copy(sourceUrl.path(), destUrl.path())) + error(tr("Error copying file %1 to %2").arg(_sourceUrl).arg(_destUrl)); } diff --git a/mix/FileIo.h b/mix/FileIo.h index ed1326037..83352476b 100644 --- a/mix/FileIo.h +++ b/mix/FileIo.h @@ -34,15 +34,19 @@ class FileIo : public QObject { Q_OBJECT +signals: + /// Signalled in case of IO error + void error(QString const& _errorText); + 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); + /// Create a directory if it does not exist. Signals on failure. + Q_INVOKABLE void makeDir(QString const& _url); + /// Read file contents to a string. Signals on failure. + Q_INVOKABLE QString readFile(QString const& _url); + /// Write contents to a file. Signals on failure. + Q_INVOKABLE void writeFile(QString const& _url, QString const& _data); + /// Copy a file from _sourcePath to _destPath. Signals on failure. + Q_INVOKABLE void copyFile(QString const& _sourceUrl, QString const& _destUrl); }; } diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 5cf71aa7d..b5226b90c 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -35,6 +35,7 @@ MixApplication::MixApplication(int _argc, char* _argv[]): 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_engine->load(QUrl("qrc:/qml/ProjectModel.qml")); m_appContext->loadProject(); } diff --git a/mix/qml.qrc b/mix/qml.qrc index a6215b243..53a2d1ae7 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -15,5 +15,8 @@ qml/js/Debugger.js qml/NewProjectDialog.qml qml/ProjectModel.qml + qml/CodeEditorModel.qml + qml/CodeEditor.qml + qml/CodeEditorView.qml diff --git a/mix/qml/CodeEditor.qml b/mix/qml/CodeEditor.qml new file mode 100644 index 000000000..77e60ee66 --- /dev/null +++ b/mix/qml/CodeEditor.qml @@ -0,0 +1,81 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.1 + +Component { + Item { + signal editorTextChanged + + function setText(text) { + codeEditor.text = 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 + } + } + } + } + + 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 + + tabChangesFocus: false + Keys.onPressed: { + if (event.key === Qt.Key_Tab) { + codeEditor.insert(codeEditor.cursorPosition, "\t"); + event.accepted = true; + } + } + onTextChanged: { + editorTextChanged(); + } + + } + } +} diff --git a/mix/qml/CodeEditorModel.qml b/mix/qml/CodeEditorModel.qml new file mode 100644 index 000000000..c696272ce --- /dev/null +++ b/mix/qml/CodeEditorModel.qml @@ -0,0 +1,13 @@ +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 + +Item { + id: codeEditorModel + + property var codeDocuments: [] +} diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml new file mode 100644 index 000000000..9edc81ed9 --- /dev/null +++ b/mix/qml/CodeEditorView.qml @@ -0,0 +1,80 @@ +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 { + + property string currentDocumentId: "" + + function getDocumentText(documentId) { + for (i = 0; i < editorListModel.count; i++) { + if (editorListModel.get(i).documentId === documentId) { + return editors.itemAt(i).getDocumentText(); + } + } + return ""; + } + + function openDocument(document) { + loadDocument(document); + currentDocumentId = document.documentId; + } + + function loadDocument(document) { + for (var i = 0; i < editorListModel.count; i++) + if (editorListModel.get(i).documentId === document.documentId) + return; //already open + + editorListModel.append(document); + } + + function doLoadDocument(editor, document) { + var data = fileIo.readFile(document.path); + if (document.isContract) + editor.onEditorTextChanged.connect(function() { + codeModel.registerCodeChange(editor.getText()); + }); + editor.setText(data); + } + + Connections { + target: ProjectModel + onDocumentOpen: { + openDocument(document); + } + } + + CodeEditor { + id: codeEditor + } + + Repeater { + id: editors + model: editorListModel + delegate: Loader { + active: false; + asynchronous: true + anchors.fill: parent + sourceComponent: codeEditor + visible: (currentDocumentId === editorListModel.get(index).documentId) + onVisibleChanged: { + loadIfNotLoaded() + } + Component.onCompleted: { + loadIfNotLoaded() + } + onLoaded: { doLoadDocument(item, editorListModel.get(index)) } + + function loadIfNotLoaded () { + if(visible && !active) { + active = true; + } + } + } + } + ListModel { + id: editorListModel + } +} diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 6cd2f5baa..5376be49d 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -22,80 +22,21 @@ Rectangle { anchors.fill: parent ProjectList { - anchors.left: parent.left width: parent.width * 0.2 height: parent.height - Layout.minimumWidth: 20 + Layout.minimumWidth: 200 } SplitView { //anchors.fill: parent width: parent.width * 0.6 orientation: Qt.Vertical - Rectangle { - anchors.top: parent.top - id: contentView - width: parent.width - height: parent.height * 0.7 - - 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" - } + CodeEditorView { + height: parent.height * 0.7 + anchors.top: parent.top + width: parent.width + } - 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 id: contextualView @@ -126,7 +67,6 @@ Rectangle { CodeEditorExtensionManager { tabView: contextualTabs rightTabView: rightPaneTabs - editor: codeEditor } } } diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index b0853ba26..7bba58418 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -14,7 +14,7 @@ Window { visible: false property alias projectTitle : titleField.text - property alias projectPath : pathField.text + readonly property string projectPath : "file://" + pathField.text signal accepted function open() { @@ -83,10 +83,10 @@ Window { 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; - } + 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 index 60f1de898..77810931c 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -6,8 +6,16 @@ import org.ethereum.qml.ProjectModel 1.0 Item { ListView { + id: projectList model: ProjectModel.listModel + anchors.fill: parent delegate: renderDelegate + highlight: Rectangle { + color: "lightsteelblue"; + } + highlightFollowsCurrentItem: true + focus: true + clip: true } Component { @@ -21,19 +29,23 @@ Item { Text { Layout.fillWidth: true Layout.fillHeight: true - text: title + text: name font.pointSize: 12 verticalAlignment: Text.AlignBottom } } + MouseArea { + id: mouseArea + z: 1 + hoverEnabled: false + anchors.fill: parent + + onClicked:{ + projectList.currentIndex = index; + ProjectModel.documentOpen(ProjectModel.listModel.get(index)); + } + } } } - 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 index 26896ea5f..f8217353d 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -12,8 +12,9 @@ Item { signal projectClosed signal projectLoaded + signal documentOpen(var document) - property bool isEmpty: projectFile === "" + property bool isEmpty: (projectFile === "") readonly property string projectFileName: ".mix" property bool haveUnsavedChanges: false @@ -30,24 +31,29 @@ Item { newProjectDialog.open(); } + function browseProject() { + openProjectFileDialog.open(); + } + function closeProject() { - console.log("closing project"); - if (haveUnsavedChanges) - saveMessageDialog.open(); - else - doCloseProject(); + if (!isEmpty) { + console.log("closing project"); + if (haveUnsavedChanges) + saveMessageDialog.open(); + else + doCloseProject(); + } } function saveProject() { if (!isEmpty) { var json = JSON.stringify(projectData); - fileIo.writeFile(projectFile, json) + fileIo.writeFile(projectFile, json); } } function loadProject(path) { - if (!isEmpty) - closeProject(); + closeProject(); console.log("loading project at " + path); var json = fileIo.readFile(path); projectData = JSON.parse(json); @@ -55,14 +61,35 @@ Item { if (!projectData.files) projectData.files = []; - for(var i = 0; i < projectData.files; i++) { + for(var i = 0; i < projectData.files.length; i++) { var p = projectData.files[i]; - projectListModel.append({ - path: p, - name: p.substring(p.lastIndexOf("/") + 1, p.length) - }); + addFile(p); } - onProjectLoaded(); + projectSettings.lastProjectPath = projectFile; + projectLoaded(); + } + + function addExistingFile() { + addExistingFileDialog().open(); + } + + function addProjectFiles(files) { + for(var i = 0; i < files.length; i++) + addFile(files[i]); + } + + function addFile(file) { + var p = file; + var fileData = { + contract: false, + path: p, + name: p.substring(p.lastIndexOf("/") + 1, p.length), + documentId: p, + isText: true, + isContract: p.substring(p.length - 4, p.length) === ".sol", + }; + + projectListModel.append(fileData); } function doCloseProject() { @@ -73,25 +100,39 @@ Item { } function doCreateProject(title, path) { - if (!isEmpty) - closeProject(); + 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, ""); + + var indexFile = dirPath + "/index.html"; + var contractsFile = dirPath + "/contracts.sol"; + var projectData = { + files: [ indexFile, contractsFile ] + }; + + fileIo.writeFile(indexFile, ""); + fileIo.writeFile(contractsFile, "contract MyContract {}"); + var json = JSON.stringify(projectData); + fileIo.writeFile(projectFile, json); loadProject(projectFile); } + Component.onCompleted: { + if (projectSettings.lastProjectPath) + loadProject(projectSettings.lastProjectPath) + } + NewProjectDialog { id: newProjectDialog visible: false onAccepted: { var title = newProjectDialog.projectTitle; var path = newProjectDialog.projectPath; - projectModel.doCreateProject(title, path); + doCreateProject(title, path); } } @@ -114,27 +155,33 @@ Item { 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; } + + FileDialog { + id: openProjectFileDialog + visible: false + title: qsTr("Open a project") + selectFolder: true + onAccepted: { + var path = openProjectFileDialog.fileUrl.toString(); + path += "/" + projectFileName; + loadProject(path); + } + } + + FileDialog { + id: addExistingFileDialog + visible: false + title: qsTr("Add a file") + selectFolder: false + onAccepted: { + var paths = openProjectFileDialog.fileUrls; + addProjectFiles(paths); + } + } + + } diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index ceed6513b..67d81747a 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -21,6 +21,8 @@ Rectangle { stateListModel.clear(); } onProjectLoaded: { + if (!target.projectData.states) + target.projectData.states = []; var items = target.projectData.states; for(var i = 0; i < items.length; i++) { stateListModel.append(items[i]); diff --git a/mix/qml/main.qml b/mix/qml/main.qml index bba7c9088..290649048 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -5,6 +5,7 @@ import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import CodeEditorExtensionManager 1.0 +import org.ethereum.qml.ProjectModel 1.0 ApplicationWindow { id: mainApplication @@ -18,6 +19,12 @@ ApplicationWindow { menuBar: MenuBar { Menu { title: qsTr("File") + MenuItem { action: createProjectAction } + MenuItem { action: openProjectAction } + MenuItem { action: addExistingFileAction } + MenuItem { action: addNewJsFileAction } + MenuItem { action: addNewHtmlFileAction } + MenuItem { action: addNewContractAction } MenuItem { text: qsTr("Exit") onTriggered: Qt.quit(); @@ -62,5 +69,51 @@ ApplicationWindow { onTriggered: debugModel.resetState(); } + Action { + id: createProjectAction + text: qsTr("&New project") + shortcut: "Ctrl+N" + enabled: true; + onTriggered: ProjectModel.createProject(); + } + + Action { + id: openProjectAction + text: qsTr("&Open project") + shortcut: "Ctrl+O" + enabled: true; + onTriggered: ProjectModel.browseProject(); + } + + Action { + id: addNewJsFileAction + text: qsTr("New JavaScript file") + shortcut: "Ctrl+Alt+J" + enabled: !ProjectModel.isEmpty + onTriggered: ProjectModel.addJsFile(); + } + Action { + id: addNewHtmlFileAction + text: qsTr("New HTML file") + shortcut: "Ctrl+Alt+H" + enabled: !ProjectModel.isEmpty + onTriggered: ProjectModel.addHtmlFile(); + } + + Action { + id: addNewContractAction + text: qsTr("New contract") + shortcut: "Ctrl+Alt+C" + enabled: !ProjectModel.isEmpty + onTriggered: ProjectModel.addContract(); + } + + Action { + id: addExistingFileAction + text: qsTr("Add existing file") + shortcut: "Ctrl+Alt+A" + enabled: !ProjectModel.isEmpty + onTriggered: ProjectModel.addExistingFile(); + } }