Browse Source

Merge pull request #1315 from yann300/trackFileStatus

Mix - Track files status.
cl-refactor
Arkadiy Paronyan 10 years ago
parent
commit
21009510a6
  1. 19
      mix/FileIo.cpp
  2. 12
      mix/FileIo.h
  3. 110
      mix/qml/CodeEditorView.qml
  4. 27
      mix/qml/FilesSection.qml
  5. 3
      mix/qml/MainContent.qml
  6. 14
      mix/qml/ProjectList.qml
  7. 52
      mix/qml/ProjectModel.qml
  8. 6
      mix/qml/StatusPane.qml
  9. 17
      mix/qml/TransactionLog.qml
  10. 14
      mix/qml/WebCodeEditor.qml
  11. 17
      mix/qml/WebPreview.qml
  12. 12
      mix/qml/html/codeeditor.js
  13. 57
      mix/qml/js/ProjectModel.js
  14. 35
      mix/qml/main.qml

19
mix/FileIo.cpp

@ -20,6 +20,7 @@
* Ethereum IDE client.
*/
#include <QFileSystemWatcher>
#include <QDebug>
#include <QDesktopServices>
#include <QMimeDatabase>
@ -40,6 +41,10 @@ using namespace dev;
using namespace dev::crypto;
using namespace dev::mix;
FileIo::FileIo(): m_watcher(new QFileSystemWatcher(this))
{
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &FileIo::fileChanged);
}
void FileIo::openFileBrowser(QString const& _dir)
{
@ -87,7 +92,9 @@ QString FileIo::readFile(QString const& _url)
void FileIo::writeFile(QString const& _url, QString const& _data)
{
QFile file(pathFromUrl(_url));
QString path = pathFromUrl(_url);
m_watcher->removePath(path);
QFile file(path);
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream stream(&file);
@ -95,6 +102,7 @@ void FileIo::writeFile(QString const& _url, QString const& _data)
}
else
error(tr("Error writing file %1").arg(_url));
m_watcher->addPath(path);
}
void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl)
@ -191,3 +199,12 @@ QStringList FileIo::makePackage(QString const& _deploymentFolder)
return ret;
}
void FileIo::watchFileChanged(QString const& _path)
{
m_watcher->addPath(pathFromUrl(_path));
}
void FileIo::stopWatching(QString const& _path)
{
m_watcher->removePath(pathFromUrl(_path));
}

12
mix/FileIo.h

@ -25,6 +25,8 @@
#include <libdevcore/CommonData.h>
#include <QObject>
class QFileSystemWatcher;
namespace dev
{
namespace mix
@ -39,8 +41,11 @@ class FileIo: public QObject
signals:
/// Signalled in case of IO error
void error(QString const& _errorText);
/// Signnalled when a file is changed.
void fileChanged(QString const& _filePath);
public:
FileIo();
/// 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.
@ -55,12 +60,17 @@ public:
Q_INVOKABLE bool fileExists(QString const& _url);
/// Compress a folder, @returns sha3 of the compressed file.
Q_INVOKABLE QStringList makePackage(QString const& _deploymentFolder);
/// Open a file browser
/// Open a file browser.
Q_INVOKABLE void openFileBrowser(QString const& _dir);
/// Listen for files change in @arg _path.
Q_INVOKABLE void watchFileChanged(QString const& _path);
/// Stop Listenning for files change in @arg _path.
Q_INVOKABLE void stopWatching(QString const& _path);
private:
QString getHomePath() const;
QString pathFromUrl(QString const& _url);
QFileSystemWatcher* m_watcher;
};
}

110
mix/qml/CodeEditorView.qml

@ -2,12 +2,14 @@ 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: codeEditorView
property string currentDocumentId: ""
signal documentEdit(string documentId)
signal breakpointsChanged(string documentId)
signal isCleanChanged(var isClean, string documentId)
function getDocumentText(documentId) {
for (var i = 0; i < editorListModel.count; i++) {
@ -51,12 +53,17 @@ Item {
breakpointsChanged(document.documentId);
});
editor.setText(data, document.syntaxMode);
editor.onIsCleanChanged.connect(function() {
isCleanChanged(editor.isClean, document.documentId);
});
}
function getEditor(documentId) {
for (var i = 0; i < editorListModel.count; i++)
{
if (editorListModel.get(i).documentId === documentId)
return editors.itemAt(i).item;
}
return null;
}
@ -91,6 +98,12 @@ Item {
editor.toggleBreakpoint();
}
function resetEditStatus(docId) {
var editor = getEditor(docId);
if (editor)
editor.changeGeneration();
}
Component.onCompleted: projectModel.codeEditor = codeEditorView;
Connections {
@ -98,17 +111,65 @@ Item {
onDocumentOpened: {
openDocument(document);
}
onProjectSaving: {
for (var i = 0; i < editorListModel.count; i++)
fileIo.writeFile(editorListModel.get(i).path, editors.itemAt(i).item.getText());
{
var doc = editorListModel.get(i);
fileIo.writeFile(doc.path, editors.itemAt(i).item.getText());
}
}
onProjectSaved: {
if (projectModel.appIsClosing)
return;
for (var i = 0; i < editorListModel.count; i++)
{
var doc = editorListModel.get(i);
resetEditStatus(doc.documentId);
}
}
onProjectClosed: {
for (var i = 0; i < editorListModel.count; i++) {
for (var i = 0; i < editorListModel.count; i++)
editors.itemAt(i).visible = false;
}
editorListModel.clear();
currentDocumentId = "";
}
onDocumentSaved: {
resetEditStatus(documentId);
}
onContractSaved: {
resetEditStatus(documentId);
}
onDocumentSaving: {
for (var i = 0; i < editorListModel.count; i++)
{
var doc = editorListModel.get(i);
if (doc.path === document.path)
{
fileIo.writeFile(document.path, editors.itemAt(i).item.getText());
break;
}
}
}
}
MessageDialog
{
id: messageDialog
title: qsTr("File Changed")
text: qsTr("This file has been changed outside of the editor. Do you want to reload it?")
standardButtons: StandardButton.Yes | StandardButton.No
property variant item
property variant doc
onYes: {
doLoadDocument(item, doc);
resetEditStatus(doc.documentId);
}
}
Repeater {
@ -121,10 +182,20 @@ Item {
anchors.fill: parent
source: "CodeEditor.qml"
visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId)
property bool changed: false
onVisibleChanged: {
loadIfNotLoaded()
if (visible && item)
{
loader.item.setFocus();
if (changed)
{
changed = false;
messageDialog.item = loader.item;
messageDialog.doc = editorListModel.get(index);
messageDialog.open();
}
}
}
Component.onCompleted: {
loadIfNotLoaded()
@ -133,8 +204,39 @@ Item {
doLoadDocument(loader.item, editorListModel.get(index))
}
Connections
{
target: projectModel
onDocumentChanged: {
if (!item)
return;
var current = editorListModel.get(index);
if (documentId === current.documentId)
{
if (currentDocumentId === current.documentId)
{
messageDialog.item = loader.item;
messageDialog.doc = editorListModel.get(index);
messageDialog.open();
}
else
changed = true
}
}
onDocumentUpdated: {
var document = projectModel.getDocument(documentId);
for (var i = 0; i < editorListModel.count; i++)
if (editorListModel.get(i).documentId === documentId)
{
editorListModel.set(i, document);
break;
}
}
}
function loadIfNotLoaded () {
if(visible && !active) {
if (visible && !active) {
active = true;
}
}

27
mix/qml/FilesSection.qml

@ -141,6 +141,13 @@ Rectangle
color: isSelected ? ProjectFilesStyle.documentsList.highlightColor : "transparent"
property bool isSelected
property bool renameMode
Row {
spacing: 3
anchors.verticalCenter: parent.verticalCenter
anchors.fill: parent
anchors.left: parent.left
anchors.leftMargin: ProjectFilesStyle.general.leftMargin + 2
Text {
id: nameText
height: parent.height
@ -149,11 +156,8 @@ Rectangle
text: name;
font.family: fileNameFont.name
font.pointSize: ProjectFilesStyle.documentsList.fontSize
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.leftMargin: ProjectFilesStyle.general.leftMargin + 2
width: parent.width
Connections
{
target: selManager
@ -168,6 +172,21 @@ Rectangle
if (rootItem.isSelected && section.state === "hidden")
section.state = "";
}
onIsCleanChanged: {
if (groupName === sectionName && doc === documentId)
editStatusLabel.visible = !isClean;
}
}
}
DefaultLabel {
id: editStatusLabel
visible: false
color: rootItem.isSelected ? ProjectFilesStyle.documentsList.selectedColor : ProjectFilesStyle.documentsList.color
verticalAlignment: Text.AlignVCenter
text: "*"
width: 10
height: parent.height
}
}

3
mix/qml/MainContent.qml

@ -173,6 +173,9 @@ Rectangle {
width: 350
Layout.minimumWidth: 250
Layout.fillHeight: true
Connections {
target: projectModel.codeEditor
}
}
Rectangle {

14
mix/qml/ProjectList.qml

@ -67,8 +67,6 @@ Item {
color: ProjectFilesStyle.documentsList.background
}
Rectangle
{
Layout.fillWidth: true
@ -83,6 +81,7 @@ Item {
Repeater {
model: [qsTr("Contracts"), qsTr("Javascript"), qsTr("Web Pages"), qsTr("Styles"), qsTr("Images"), qsTr("Misc")];
signal selected(string doc, string groupName)
signal isCleanChanged(string doc, string groupName, var isClean)
property int incr: -1;
id: sectionRepeater
FilesSection
@ -145,6 +144,17 @@ Item {
}
}
onIsCleanChanged: {
for (var si = 0; si < sectionModel.count; si++) {
var document = sectionModel.get(si);
if (documentId === document.documentId && document.groupName === modelData)
{
selManager.isCleanChanged(documentId, modelData, isClean);
break;
}
}
}
onDocumentOpened: {
if (document.groupName === modelData)
sectionRepeater.selected(document.documentId, modelData);

52
mix/qml/ProjectModel.qml

@ -7,12 +7,13 @@ import Qt.labs.settings 1.0
import "js/ProjectModel.js" as ProjectModelCode
Item {
id: projectModel
signal projectClosed
signal projectLoading(var projectData)
signal projectLoaded()
signal documentSaving(var document)
signal documentChanged(var documentId)
signal documentOpened(var document)
signal documentRemoved(var documentId)
signal documentUpdated(var documentId) //renamed
@ -21,15 +22,17 @@ Item {
signal projectSaved()
signal newProject(var projectData)
signal documentSaved(var documentId)
signal contractSaved(var documentId)
signal deploymentStarted()
signal deploymentStepChanged(string message)
signal deploymentComplete()
signal deploymentError(string error)
signal isCleanChanged(var isClean, string documentId)
property bool isEmpty: (projectPath === "")
readonly property string projectFileName: ".mix"
property bool haveUnsavedChanges: false
property bool appIsClosing: false
property string projectPath: ""
property string projectTitle: ""
property string currentDocumentId: ""
@ -38,11 +41,13 @@ Item {
property var listModel: projectListModel
property var stateListModel: projectStateListModel.model
property CodeEditorView codeEditor: null
property var unsavedFiles: []
//interface
function saveAll() { ProjectModelCode.saveAll(); }
function saveCurrentDocument() { ProjectModelCode.saveCurrentDocument(); }
function createProject() { ProjectModelCode.createProject(); }
function closeProject() { ProjectModelCode.closeProject(); }
function closeProject(callBack) { ProjectModelCode.closeProject(callBack); }
function saveProject() { ProjectModelCode.saveProject(); }
function loadProject(path) { ProjectModelCode.loadProject(path); }
function newHtmlFile() { ProjectModelCode.newHtmlFile(); }
@ -69,6 +74,20 @@ Item {
}
}
Connections {
target: codeEditor
onIsCleanChanged: {
for (var i in unsavedFiles)
{
if (unsavedFiles[i] === documentId && isClean)
unsavedFiles.splice(i, 1);
}
if (!isClean)
unsavedFiles.push(documentId);
isCleanChanged(isClean, documentId);
}
}
NewProjectDialog {
id: newProjectDialog
visible: false
@ -79,18 +98,36 @@ Item {
}
}
Connections
{
target: fileIo
property bool saving: false
onFileChanged:
{
fileIo.watchFileChanged(_filePath);
var documentId = ProjectModelCode.getDocumentByPath(_filePath);
documentChanged(documentId);
}
}
MessageDialog {
id: saveMessageDialog
title: qsTr("Project")
text: qsTr("Do you want to save changes?")
standardButtons: StandardButton.Ok | StandardButton.Cancel
text: qsTr("Some files require to be saved. Do you want to save changes?");
standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Cancel
icon: StandardIcon.Question
onAccepted: {
property var callBack;
onYes: {
projectModel.saveAll();
ProjectModelCode.doCloseProject();
if (callBack)
callBack();
}
onRejected: {
onRejected: {}
onNo: {
ProjectModelCode.doCloseProject();
if (callBack)
callBack();
}
}
@ -135,6 +172,7 @@ Item {
target: projectModel
onProjectClosed: {
projectSettings.lastProjectPath = "";
projectPath = "";
}
}

6
mix/qml/StatusPane.qml

@ -77,11 +77,15 @@ Rectangle {
function format(_message)
{
var formatted = _message.match(/(?:<dev::eth::)(.+)(?:>)/);
if (formatted === null)
formatted = _message.match(/(?:<dev::)(.+)(?:>)/);
if (formatted.length > 1)
formatted = formatted[1] + ": ";
formatted = formatted[1];
else
return _message;
var exceptionInfos = _message.match(/(?:tag_)(.+)/g);
if (exceptionInfos !== null && exceptionInfos.length > 0)
formatted += ": "
for (var k in exceptionInfos)
formatted += " " + exceptionInfos[k].replace("*]", "").replace("tag_", "").replace("=", "");
return formatted;

17
mix/qml/TransactionLog.qml

@ -30,12 +30,23 @@ Item {
anchors.fill: parent
RowLayout {
Connections
{
id: compilationStatus
target: codeModel
property bool compilationComplete: false
onCompilationComplete: compilationComplete = true
onCompilationError: compilationComplete = false
}
Connections
{
target: projectModel
onProjectSaved:
{
if (codeModel.hasContract && !clientModel.running)
if (projectModel.appIsClosing)
return;
if (compilationStatus.compilationComplete && codeModel.hasContract && !clientModel.running)
projectModel.stateListModel.debugDefaultState();
}
onProjectClosed:
@ -44,6 +55,10 @@ Item {
transactionModel.clear();
callModel.clear();
}
onContractSaved: {
if (compilationStatus.compilationComplete && codeModel.hasContract && !clientModel.running)
projectModel.stateListModel.debugDefaultState();
}
}
ComboBox {

14
mix/qml/WebCodeEditor.qml

@ -6,8 +6,9 @@ import QtWebEngine 1.0
import QtWebEngine.experimental 1.0
Item {
signal editorTextChanged;
signal breakpointsChanged;
signal editorTextChanged
signal breakpointsChanged
property bool isClean: true
property string currentText: ""
property string currentMode: ""
property bool initialized: false
@ -50,6 +51,10 @@ Item {
editorBrowser.runJavaScript("toggleBreakpoint()");
}
function changeGeneration() {
editorBrowser.runJavaScript("changeGeneration()", function(result) {});
}
Connections {
target: appContext
onClipboardChanged: syncClipboard()
@ -75,6 +80,7 @@ Item {
runJavaScript("getTextChanged()", function(result) { });
pollTimer.running = true;
syncClipboard();
parent.changeGeneration();
}
}
@ -103,7 +109,9 @@ Item {
});
}
});
editorBrowser.runJavaScript("isClean()", function(result) {
isClean = result;
});
}
}
}

17
mix/qml/WebPreview.qml

@ -87,8 +87,7 @@ Item {
Connections {
target: projectModel
//onProjectSaved : reloadOnSave();
//onDocumentSaved: reloadOnSave();
onDocumentAdded: {
var document = projectModel.getDocument(documentId)
if (document.isHtml)
@ -99,7 +98,13 @@ Item {
}
onDocumentUpdated: {
updateDocument(documentId, function(i) { pageListModel.set(i, projectModel.getDocument(documentId)) } )
var document = projectModel.getDocument(documentId);
for (var i = 0; i < pageListModel.count; i++)
if (pageListModel.get(i).documentId === documentId)
{
pageListModel.set(i, document);
break;
}
}
onProjectLoading: {
@ -116,6 +121,12 @@ Item {
}
}
onDocumentSaved:
{
if (!projectModel.getDocument(documentId).isContract)
reloadOnSave();
}
onProjectClosed: {
pageListModel.clear();
}

12
mix/qml/html/codeeditor.js

@ -18,7 +18,6 @@ editor.breakpointsChangeRegistered = false;
editor.on("change", function(eMirror, object) {
editor.changeRegistered = true;
});
var mac = /Mac/.test(navigator.platform);
@ -110,3 +109,14 @@ highlightExecution = function(start, end) {
executionMark.clear();
executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" });
}
var changeId;
changeGeneration = function()
{
changeId = editor.changeGeneration(true);
}
isClean = function()
{
return editor.isClean(changeId);
}

57
mix/qml/js/ProjectModel.js

@ -25,6 +25,16 @@ Qt.include("TransactionHelper.js")
var htmlTemplate = "<html>\n<head>\n<script>\n</script>\n</head>\n<body>\n<script>\n</script>\n</body>\n</html>";
var contractTemplate = "contract Contract {\n}\n";
function saveCurrentDocument()
{
var doc = projectListModel.get(getDocumentIndex(currentDocumentId));
documentSaving(doc);
if (doc.isContract)
contractSaved(currentDocumentId);
else
documentSaved(currentDocumentId);
}
function saveAll() {
saveProject();
}
@ -33,16 +43,35 @@ function createProject() {
newProjectDialog.open();
}
function closeProject() {
function closeProject(callBack) {
if (!isEmpty) {
if (haveUnsavedChanges)
if (unsavedFiles.length > 0)
{
saveMessageDialog.callBack = callBack;
saveMessageDialog.open();
}
else
{
doCloseProject();
if (callBack)
callBack();
}
}
}
function saveProject() {
if (!isEmpty) {
var projectData = saveProjectFile();
if (projectData !== null)
{
projectSaving(projectData);
projectSaved();
}
}
}
function saveProjectFile()
{
if (!isEmpty) {
var projectData = {
files: [],
@ -55,13 +84,14 @@ function saveProject() {
deploymentDir: projectModel.deploymentDir
};
for (var i = 0; i < projectListModel.count; i++)
projectData.files.push(projectListModel.get(i).fileName)
projectSaving(projectData);
projectData.files.push(projectListModel.get(i).fileName);
var json = JSON.stringify(projectData, null, "\t");
var projectFile = projectPath + projectFileName;
fileIo.writeFile(projectFile, json);
projectSaved();
return projectData;
}
return null;
}
function loadProject(path) {
@ -131,6 +161,8 @@ function addFile(fileName) {
};
projectListModel.append(docData);
saveProjectFile();
fileIo.watchFileChanged(p);
return docData.documentId;
}
@ -143,6 +175,17 @@ function getDocumentIndex(documentId)
return -1;
}
function getDocumentByPath(_path)
{
for (var i = 0; i < projectListModel.count; i++)
{
var doc = projectListModel.get(i);
if (doc.path.indexOf(_path) !== -1)
return doc.documentId;
}
return null;
}
function openDocument(documentId) {
if (documentId !== currentDocumentId) {
documentOpened(projectListModel.get(getDocumentIndex(documentId)));
@ -226,12 +269,16 @@ function renameDocument(documentId, newName) {
var i = getDocumentIndex(documentId);
var document = projectListModel.get(i);
if (!document.isContract) {
fileIo.stopWatching(document.path);
var sourcePath = document.path;
var destPath = projectPath + newName;
fileIo.moveFile(sourcePath, destPath);
document.path = destPath;
document.name = newName;
document.fileName = newName;
projectListModel.set(i, document);
fileIo.watchFileChanged(destPath);
saveProjectFile();
documentUpdated(documentId);
}
}

35
mix/qml/main.qml

@ -17,6 +17,25 @@ ApplicationWindow {
minimumHeight: 300
title: qsTr("Mix")
Connections
{
target: mainApplication
onClosing:
{
mainApplication.close();
close.accepted = false;
}
}
function close()
{
projectModel.appIsClosing = true;
if (projectModel.projectPath !== "")
projectModel.closeProject(function() { Qt.quit(); })
else
Qt.quit();
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
@ -24,6 +43,7 @@ ApplicationWindow {
MenuItem { action: openProjectAction }
MenuSeparator {}
MenuItem { action: saveAllFilesAction }
MenuItem { action: saveCurrentDocument }
MenuSeparator {}
MenuItem { action: addExistingFileAction }
MenuItem { action: addNewJsFileAction }
@ -92,7 +112,10 @@ ApplicationWindow {
id: exitAppAction
text: qsTr("Exit")
shortcut: "Ctrl+Q"
onTriggered: Qt.quit();
onTriggered:
{
mainApplication.close();
}
}
Action {
@ -279,11 +302,19 @@ ApplicationWindow {
Action {
id: saveAllFilesAction
text: qsTr("Save All")
shortcut: "Ctrl+S"
shortcut: "Ctrl+Shift+A"
enabled: !projectModel.isEmpty
onTriggered: projectModel.saveAll();
}
Action {
id: saveCurrentDocument
text: qsTr("Save Current Document")
shortcut: "Ctrl+S"
enabled: !projectModel.isEmpty
onTriggered: projectModel.saveCurrentDocument();
}
Action {
id: closeProjectAction
text: qsTr("Close Project")

Loading…
Cancel
Save