diff options
author | Alessandro Portale <alessandro.portale@qt.io> | 2016-09-15 17:55:28 +0200 |
---|---|---|
committer | Alessandro Portale <alessandro.portale@qt.io> | 2016-09-16 12:39:49 +0000 |
commit | 4ea332b0620c773abbd1dcf1689ec6c7253069ca (patch) | |
tree | f9c4e5c806dfc87aaebe4de041eb99357de96fd8 /src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp | |
parent | 1a9919a20811cea6bf2fc77639f668c3a595883a (diff) |
ScxmlEditor: Initial import
Change-Id: I4701b77ebd4e2520f2616c42206ac17be3a12b60
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp')
-rw-r--r-- | src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp b/src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp new file mode 100644 index 0000000000..7e665d6605 --- /dev/null +++ b/src/plugins/scxmleditor/plugin_interface/scxmldocument.cpp @@ -0,0 +1,701 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "scxmldocument.h" +#include "scxmlnamespace.h" +#include "scxmltagutils.h" +#include "undocommands.h" + +#include <QBuffer> +#include <QDebug> +#include <QFile> +#include <QXmlStreamReader> +#include <QXmlStreamWriter> + +#include <app/app_version.h> + +using namespace ScxmlEditor::PluginInterface; + +ScxmlDocument::ScxmlDocument(const QString &fileName, QObject *parent) + : QObject(parent) +{ + initVariables(); + m_fileName = fileName; + load(fileName); +} + +ScxmlDocument::ScxmlDocument(const QByteArray &data, QObject *parent) + : QObject(parent) +{ + initVariables(); + load(QLatin1String(data)); +} + +ScxmlDocument::~ScxmlDocument() +{ + clear(false); +} + +void ScxmlDocument::initVariables() +{ + m_idDelimiter = "::"; + m_undoStack = new QUndoStack(this); + connect(m_undoStack, &QUndoStack::cleanChanged, this, &ScxmlDocument::documentChanged); +} + +void ScxmlDocument::clear(bool createRoot) +{ + m_currentTag = nullptr; + m_nextIdHash.clear(); + + // First clear undostack + m_undoStack->clear(); + + // Second delete all other available tags + // tags will call the removeChild-function -> m_tags will be cleared + for (int i = m_tags.count(); i--;) + delete m_tags[i]; + + m_rootTags.clear(); + clearNamespaces(); + + if (createRoot) { + pushRootTag(createScxmlTag()); + rootTag()->setAttribute("qt:editorversion", QLatin1String(Core::Constants::IDE_VERSION_LONG)); + + auto ns = new ScxmlNamespace("qt", "http://www.qt.io/2015/02/scxml-ext"); + ns->setTagVisibility("editorInfo", false); + addNamespace(ns); + } + + m_useFullNameSpace = false; +} + +QString ScxmlDocument::nameSpaceDelimiter() const +{ + return m_idDelimiter; +} + +bool ScxmlDocument::useFullNameSpace() const +{ + return m_useFullNameSpace; +} + +void ScxmlDocument::setNameSpaceDelimiter(const QString &delimiter) +{ + m_idDelimiter = delimiter; +} + +QString ScxmlDocument::fileName() const +{ + return m_fileName; +} + +void ScxmlDocument::setFileName(const QString &filename) +{ + m_fileName = filename; +} + +ScxmlNamespace *ScxmlDocument::scxmlNamespace(const QString &prefix) +{ + return m_namespaces.value(prefix, 0); +} + +void ScxmlDocument::addNamespace(ScxmlNamespace *ns) +{ + if (!ns) + return; + + delete m_namespaces.take(ns->prefix()); + m_namespaces[ns->prefix()] = ns; + + ScxmlTag *scxmlTag = scxmlRootTag(); + if (scxmlTag) { + QMapIterator<QString, ScxmlNamespace*> i(m_namespaces); + while (i.hasNext()) { + i.next(); + QString prefix = i.value()->prefix(); + if (prefix.isEmpty()) + prefix = "xmlns"; + + if (prefix.startsWith("xmlns")) + scxmlTag->setAttribute(prefix, i.value()->name()); + else + scxmlTag->setAttribute(QString::fromLatin1("xmlns:%1").arg(prefix), i.value()->name()); + } + } +} + +void ScxmlDocument::clearNamespaces() +{ + while (!m_namespaces.isEmpty()) { + delete m_namespaces.take(m_namespaces.firstKey()); + } +} + +bool ScxmlDocument::generateSCXML(QIODevice *io, ScxmlTag *tag) const +{ + QXmlStreamWriter xml(io); + xml.setAutoFormatting(true); + xml.writeStartDocument(); + if (tag) + tag->writeXml(xml); + else + rootTag()->writeXml(xml); + xml.writeEndDocument(); + + return !xml.hasError(); +} + +ScxmlTag *ScxmlDocument::createScxmlTag() +{ + auto tag = new ScxmlTag(Scxml, this); + QMapIterator<QString, ScxmlNamespace*> i(m_namespaces); + while (i.hasNext()) { + i.next(); + QString prefix = i.value()->prefix(); + if (prefix.isEmpty()) + prefix = "xmlns"; + + if (prefix.startsWith("xmlns")) + tag->setAttribute(prefix, i.value()->name()); + else + tag->setAttribute(QString::fromLatin1("xmlns:%1").arg(prefix), i.value()->name()); + } + return tag; +} + +bool ScxmlDocument::hasLayouted() const +{ + return m_hasLayouted; +} +QColor ScxmlDocument::getColor(int depth) const +{ + return m_colors.isEmpty() ? QColor(Qt::gray) : m_colors[depth % m_colors.count()]; +} + +void ScxmlDocument::setLevelColors(const QVector<QColor> &colors) +{ + m_colors = colors; + emit colorThemeChanged(); +} + +bool ScxmlDocument::load(QIODevice *io) +{ + m_currentTag = nullptr; + clearNamespaces(); + + bool ok = true; + clear(false); + + QXmlStreamReader xml(io); + while (!xml.atEnd()) { + QXmlStreamReader::TokenType token = xml.readNext(); + + if (token == QXmlStreamReader::StartDocument) + continue; + + if (token == QXmlStreamReader::StartElement) { + if (xml.name() == "scxml") { + // Get and add namespaces + QXmlStreamNamespaceDeclarations ns = xml.namespaceDeclarations(); + for (int i = 0; i < ns.count(); ++i) + addNamespace(new ScxmlNamespace(ns[i].prefix().toString(), ns[i].namespaceUri().toString())); + + // create root tag + pushRootTag(createScxmlTag()); + + // and read other tags also + rootTag()->readXml(xml); + + // Check editorversion + m_hasLayouted = rootTag()->hasAttribute("qt:editorversion"); + rootTag()->setAttribute("qt:editorversion", QLatin1String(Core::Constants::IDE_VERSION_LONG)); + } + } + + if (token == QXmlStreamReader::Invalid) + break; + } + + if (xml.hasError()) { + m_hasError = true; + initErrorMessage(xml, io); + m_fileName.clear(); + ok = false; + + clear(); + } else { + m_hasError = false; + m_lastError.clear(); + } + + m_undoStack->setClean(); + + return ok; +} + +void ScxmlDocument::initErrorMessage(const QXmlStreamReader &xml, QIODevice *io) +{ + QString errorString; + switch (xml.error()) { + case QXmlStreamReader::Error::UnexpectedElementError: + errorString = tr("UnexpectedElementError"); + break; + case QXmlStreamReader::Error::NotWellFormedError: + errorString = tr("NotWellFormedError"); + break; + case QXmlStreamReader::Error::PrematureEndOfDocumentError: + errorString = tr("PrematureEndOfDocumentError"); + break; + case QXmlStreamReader::Error::CustomError: + errorString = tr("CustomError"); + break; + default: + break; + } + + QString lineString; + io->seek(0); + for (int i = 0; i < xml.lineNumber() - 1; ++i) + io->readLine(); + lineString = QLatin1String(io->readLine()); + + m_lastError = tr("Error in reading XML.\nType: %1 (%2)\nDescription: %3\n\nRow: %4, Column: %5\n%6") + .arg(xml.error()) + .arg(errorString) + .arg(xml.errorString()) + .arg(xml.lineNumber()) + .arg(xml.columnNumber()) + .arg(lineString); +} + +bool ScxmlDocument::pasteData(const QByteArray &data, const QPointF &minPos, const QPointF &pastePos) +{ + if (!m_currentTag) + m_currentTag = rootTag(); + + if (!m_currentTag) { + m_hasError = true; + m_lastError = tr("Current tag not selected"); + return false; + } + + if (data.trimmed().isEmpty()) { + m_hasError = true; + m_lastError = tr("Pasted data is empty."); + return false; + } + + bool ok = true; + m_undoStack->beginMacro(tr("Paste item(s)")); + + QByteArray d(data); + QBuffer buffer(&d); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader xml(&buffer); + foreach (ScxmlNamespace *ns, m_namespaces) { + xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration(ns->prefix(), ns->name())); + } + + m_idMap.clear(); + QVector<ScxmlTag*> addedTags; + + while (!xml.atEnd()) { + QXmlStreamReader::TokenType token = xml.readNext(); + + if (token == QXmlStreamReader::StartDocument) + continue; + + if (token == QXmlStreamReader::StartElement) { + if (xml.name().toString() == "scxml") + continue; + + ScxmlTag *childTag = nullptr; + if ((m_currentTag->tagType() == Initial || m_currentTag->tagType() == History) && xml.name().toString() == "transition") + childTag = new ScxmlTag(InitialTransition, this); + else + childTag = new ScxmlTag(xml.prefix().toString(), xml.name().toString(), this); + + childTag->readXml(xml, true); + addedTags << childTag; + } + + if (token == QXmlStreamReader::Invalid) + break; + } + + if (xml.error()) { + m_hasError = true; + qDeleteAll(addedTags); + addedTags.clear(); + initErrorMessage(xml, &buffer); + ok = false; + } else { + m_hasError = false; + m_lastError.clear(); + + // Fine-tune names and coordinates + for (int i = 0; i < addedTags.count(); ++i) + TagUtils::modifyPosition(addedTags[i], minPos, pastePos); + + // Update targets and initial-attributes + for (int i = 0; i < addedTags.count(); ++i) + addedTags[i]->finalizeTagNames(); + + // Add tags to the document + addTags(m_currentTag, addedTags); + } + + m_undoStack->endMacro(); + + return ok; +} + +void ScxmlDocument::load(const QString &fileName) +{ + if (QFile::exists(fileName)) { + QFile file(fileName); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (load(&file)) { + m_fileName = fileName; + } + } + } + + // If loading doesn't work, create root tag here + if (m_rootTags.isEmpty()) { + pushRootTag(createScxmlTag()); + rootTag()->setAttribute("qt:editorversion", QLatin1String(Core::Constants::IDE_VERSION_LONG)); + } + + auto ns = new ScxmlNamespace("qt", "http://www.qt.io/2015/02/scxml-ext"); + ns->setTagVisibility("editorInfo", false); + addNamespace(ns); +} + +void ScxmlDocument::printSCXML() +{ + qDebug() << content(); +} + +QByteArray ScxmlDocument::content(const QVector<ScxmlTag*> &tags) const +{ + QByteArray result; + if (tags.count() > 0) { + QBuffer buffer(&result); + buffer.open(QIODevice::WriteOnly); + + bool writeScxml = tags.count() > 1 || tags[0]->tagType() != Scxml; + + QXmlStreamWriter xml(&buffer); + xml.setAutoFormatting(true); + xml.writeStartDocument(); + if (writeScxml) + xml.writeStartElement("scxml"); + + foreach (ScxmlTag *tag, tags) { + tag->writeXml(xml); + } + xml.writeEndDocument(); + + if (writeScxml) + xml.writeEndElement(); + } + return result; +} + +QByteArray ScxmlDocument::content(ScxmlTag *tag) const +{ + QByteArray byteArray; + QBuffer buffer(&byteArray); + buffer.open(QIODevice::WriteOnly); + generateSCXML(&buffer, tag); + return byteArray; +} + +bool ScxmlDocument::save() +{ + return save(m_fileName); +} + +bool ScxmlDocument::save(const QString &fileName) +{ + QString name(fileName); + if (!name.endsWith(".scxml", Qt::CaseInsensitive)) + name.append(".scxml"); + + bool ok = true; + + QFile file(name); + if (file.open(QIODevice::WriteOnly)) { + ok = generateSCXML(&file, scxmlRootTag()); + if (ok) { + m_fileName = name; + m_undoStack->setClean(); + } + file.close(); + if (!ok) + m_lastError = tr("Cannot save xml to the file %1.").arg(fileName); + } else { + ok = false; + m_lastError = tr("Cannot open file %1.").arg(fileName); + } + + return ok; +} + +void ScxmlDocument::setContent(ScxmlTag *tag, const QString &content) +{ + if (tag && !m_undoRedoRunning) + m_undoStack->push(new SetContentCommand(this, tag, content)); +} + +void ScxmlDocument::setValue(ScxmlTag *tag, const QString &key, const QString &value) +{ + if (tag && !m_undoRedoRunning) + m_undoStack->push(new SetAttributeCommand(this, tag, key, value)); +} + +void ScxmlDocument::setValue(ScxmlTag *tag, int attributeIndex, const QString &value) +{ + if (tag && attributeIndex >= 0 && attributeIndex < tag->info()->n_attributes) + m_undoStack->push(new SetAttributeCommand(this, tag, QLatin1String(tag->info()->attributes[attributeIndex].name), value)); +} + +void ScxmlDocument::setEditorInfo(ScxmlTag *tag, const QString &key, const QString &value) +{ + if (tag && !m_undoRedoRunning) + m_undoStack->push(new SetEditorInfoCommand(this, tag, key, value)); +} + +void ScxmlDocument::setCurrentTag(ScxmlTag *tag) +{ + if (tag != m_currentTag) { + emit beginTagChange(TagCurrentChanged, tag, QVariant()); + m_currentTag = tag; + emit endTagChange(TagCurrentChanged, tag, QVariant()); + } +} + +ScxmlTag *ScxmlDocument::currentTag() const +{ + return m_currentTag; +} + +void ScxmlDocument::changeParent(ScxmlTag *child, ScxmlTag *newParent, int tagIndex) +{ + if (child && child->parentTag() != newParent && !m_undoRedoRunning) + m_undoStack->push(new ChangeParentCommand(this, child, newParent == nullptr ? rootTag() : newParent, tagIndex)); +} + +void ScxmlDocument::changeOrder(ScxmlTag *child, int newPos) +{ + if (child && !m_undoRedoRunning) { + ScxmlTag *parentTag = child->parentTag(); + if (parentTag) { + m_undoStack->push(new ChangeOrderCommand(this, child, parentTag, newPos)); + } + } +} + +void ScxmlDocument::addTags(ScxmlTag *parent, const QVector<ScxmlTag*> tags) +{ + if (m_undoRedoRunning) + return; + + if (!parent) + parent = rootTag(); + + m_undoStack->push(new AddRemoveTagsBeginCommand(this, parent)); + for (int i = 0; i < tags.count(); ++i) + addTag(parent, tags[i]); + m_undoStack->push(new AddRemoveTagsEndCommand(this, parent)); +} + +void ScxmlDocument::addTag(ScxmlTag *parent, ScxmlTag *child) +{ + if (m_undoRedoRunning) + return; + + if (!parent) + parent = rootTag(); + + if (parent && child) { + m_undoStack->beginMacro(tr("Add Tag")); + addTagRecursive(parent, child); + m_undoStack->endMacro(); + } +} + +void ScxmlDocument::removeTag(ScxmlTag *tag) +{ + if (tag && !m_undoRedoRunning) { + // Create undo/redo -macro, because state can includes lot of child-states + m_undoStack->beginMacro(tr("Remove Tag")); + removeTagRecursive(tag); + m_undoStack->endMacro(); + } +} + +void ScxmlDocument::addTagRecursive(ScxmlTag *parent, ScxmlTag *tag) +{ + if (tag && !m_undoRedoRunning) { + m_undoStack->push(new AddRemoveTagCommand(this, parent, tag, TagAddChild)); + + // First create AddRemoveTagCommands for the all children recursive + for (int i = 0; i < tag->childCount(); ++i) + addTagRecursive(tag, tag->child(i)); + } +} + +void ScxmlDocument::removeTagRecursive(ScxmlTag *tag) +{ + if (tag && !m_undoRedoRunning) { + // First create AddRemoveTagCommands for the all children recursive + int childCount = tag->childCount(); + for (int i = childCount; i--;) + removeTagRecursive(tag->child(i)); + + m_undoStack->push(new AddRemoveTagCommand(this, tag->parentTag(), tag, TagRemoveChild)); + } +} + +void ScxmlDocument::setUseFullNameSpace(bool use) +{ + if (m_useFullNameSpace != use) + m_undoStack->push(new ChangeFullNameSpaceCommand(this, scxmlRootTag(), use)); +} + +QString ScxmlDocument::nextUniqueId(const QString &key) +{ + QString name; + bool bFound = false; + int id = 0; + while (true) { + id = m_nextIdHash.value(key, 0) + 1; + m_nextIdHash[key] = id; + bFound = false; + name = QString::fromLatin1("%1_%2").arg(key).arg(id); + + // Check duplicate + foreach (const ScxmlTag *tag, m_tags) { + if (tag->attribute("id") == name) { + bFound = true; + break; + } + } + if (!bFound) + break; + } + return name; +} + +QString ScxmlDocument::getUniqueCopyId(const ScxmlTag *tag) +{ + const QString key = tag->attribute("id"); + QString name = key; + int counter = 1; + bool bFound = false; + + while (true) { + bFound = false; + // Check duplicate + foreach (const ScxmlTag *t, m_tags) { + if (t->attribute("id") == name && t != tag) { + name = QString::fromLatin1("%1_Copy(%2)").arg(key).arg(counter); + bFound = true; + counter++; + } + } + + if (!bFound) + break; + } + + return name; +} + +bool ScxmlDocument::changed() const +{ + return !m_undoStack->isClean(); +} + +ScxmlTag *ScxmlDocument::scxmlRootTag() const +{ + ScxmlTag *tag = rootTag(); + while (tag && tag->tagType() != Scxml) { + tag = tag->parentTag(); + } + + return tag; +} + +ScxmlTag *ScxmlDocument::rootTag() const +{ + return m_rootTags.isEmpty() ? 0 : m_rootTags.last(); +} + +void ScxmlDocument::pushRootTag(ScxmlTag *tag) +{ + m_rootTags << tag; +} + +ScxmlTag *ScxmlDocument::popRootTag() +{ + return m_rootTags.takeLast(); +} + +void ScxmlDocument::deleteRootTags() +{ + while (m_rootTags.count() > 0) + delete m_rootTags.takeLast(); +} + +QUndoStack *ScxmlDocument::undoStack() const +{ + return m_undoStack; +} + +void ScxmlDocument::addChild(ScxmlTag *tag) +{ + if (!m_tags.contains(tag)) + m_tags << tag; +} + +void ScxmlDocument::removeChild(ScxmlTag *tag) +{ + m_tags.removeAll(tag); +} + +void ScxmlDocument::setUndoRedoRunning(bool para) +{ + m_undoRedoRunning = para; +} + +QFileInfo ScxmlDocument::qtBinDir() const +{ + return m_qtBinDir; +} |