summaryrefslogtreecommitdiff
path: root/src/plugins/qmljseditor
diff options
context:
space:
mode:
authorKai Koehne <kai.koehne@nokia.com>2010-07-08 11:32:45 +0200
committerKai Koehne <kai.koehne@nokia.com>2010-07-08 14:02:51 +0200
commit02923cf2580ece7f1680059a3ed99f326b5dc724 (patch)
treedc0d5110396413fd690eb8bce20d4dce1fa863c6 /src/plugins/qmljseditor
parent1541dec6f34a6cbcec44391f87de2a7c2c00048b (diff)
downloadqt-creator-02923cf2580ece7f1680059a3ed99f326b5dc724.tar.gz
Support Outline sidebar for QML files
Diffstat (limited to 'src/plugins/qmljseditor')
-rw-r--r--src/plugins/qmljseditor/qmljseditor.pro6
-rw-r--r--src/plugins/qmljseditor/qmljseditorplugin.cpp3
-rw-r--r--src/plugins/qmljseditor/qmljsoutline.cpp393
-rw-r--r--src/plugins/qmljseditor/qmljsoutline.h108
4 files changed, 508 insertions, 2 deletions
diff --git a/src/plugins/qmljseditor/qmljseditor.pro b/src/plugins/qmljseditor/qmljseditor.pro
index 88daee54c7..f92ba23c6c 100644
--- a/src/plugins/qmljseditor/qmljseditor.pro
+++ b/src/plugins/qmljseditor/qmljseditor.pro
@@ -23,7 +23,8 @@ HEADERS += \
qmljspreviewrunner.h \
qmljsquickfix.h \
qmljsrefactoringchanges.h \
- qmljscomponentfromobjectdef.h
+ qmljscomponentfromobjectdef.h \
+ qmljsoutline.h
SOURCES += \
qmljscodecompletion.cpp \
@@ -39,7 +40,8 @@ SOURCES += \
qmljspreviewrunner.cpp \
qmljsquickfix.cpp \
qmljsrefactoringchanges.cpp \
- qmljscomponentfromobjectdef.cpp
+ qmljscomponentfromobjectdef.cpp \
+ qmljsoutline.cpp
RESOURCES += qmljseditor.qrc
OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml
diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp
index f57d201c19..c611c36aa4 100644
--- a/src/plugins/qmljseditor/qmljseditorplugin.cpp
+++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp
@@ -36,6 +36,7 @@
#include "qmljshoverhandler.h"
#include "qmljsmodelmanager.h"
#include "qmlfilewizard.h"
+#include "qmljsoutline.h"
#include "qmljspreviewrunner.h"
#include "qmljsquickfix.h"
@@ -176,6 +177,8 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e
m_quickFixCollector = new QmlJSQuickFixCollector;
addAutoReleasedObject(m_quickFixCollector);
+ addAutoReleasedObject(new QmlJSOutlineWidgetFactory);
+
return true;
}
diff --git a/src/plugins/qmljseditor/qmljsoutline.cpp b/src/plugins/qmljseditor/qmljsoutline.cpp
new file mode 100644
index 0000000000..d808a2e2d1
--- /dev/null
+++ b/src/plugins/qmljseditor/qmljsoutline.cpp
@@ -0,0 +1,393 @@
+#include "qmljsoutline.h"
+
+#include <coreplugin/icore.h>
+#include <extensionsystem/pluginmanager.h>
+#include <qmljs/parser/qmljsast_p.h>
+#include <qmljs/qmljsmodelmanagerinterface.h>
+
+#include <QtCore/QDebug>
+#include <QtGui/QVBoxLayout>
+
+#include <typeinfo>
+
+using namespace QmlJS;
+
+enum {
+ debug = false
+};
+
+namespace QmlJSEditor {
+namespace Internal {
+
+QmlOutlineModel::QmlOutlineModel(QObject *parent) :
+ QStandardItemModel(parent)
+{
+}
+
+QmlJSOutlineTreeView::QmlJSOutlineTreeView(QWidget *parent) :
+ QTreeView(parent)
+{
+ // see also CppOutlineTreeView
+ setFocusPolicy(Qt::NoFocus);
+ setFrameStyle(QFrame::NoFrame);
+ setAttribute(Qt::WA_MacShowFocusRect, false);
+ setUniformRowHeights(true);
+ setHeaderHidden(true);
+ setTextElideMode(Qt::ElideNone);
+ setIndentation(20);
+ setExpandsOnDoubleClick(false);
+}
+
+void QmlOutlineModel::startSync()
+{
+ m_treePos.clear();
+ m_treePos.append(0);
+ m_currentItem = invisibleRootItem();
+}
+
+QModelIndex QmlOutlineModel::enterElement(const QString &type, const AST::SourceLocation &sourceLocation)
+{
+ QStandardItem *item = enterNode(sourceLocation);
+ item->setText(type);
+ item->setIcon(m_icons.objectDefinitionIcon());
+ return item->index();
+}
+
+void QmlOutlineModel::leaveElement()
+{
+ leaveNode();
+}
+
+QModelIndex QmlOutlineModel::enterProperty(const QString &name, const AST::SourceLocation &sourceLocation)
+{
+ QStandardItem *item = enterNode(sourceLocation);
+ item->setText(name);
+ item->setIcon(m_icons.scriptBindingIcon());
+ return item->index();
+}
+
+void QmlOutlineModel::leaveProperty()
+{
+ leaveNode();
+}
+
+QStandardItem *QmlOutlineModel::enterNode(const QmlJS::AST::SourceLocation &location)
+{
+ int siblingIndex = m_treePos.last();
+ if (siblingIndex == 0) {
+ // first child
+ if (!m_currentItem->hasChildren()) {
+ QStandardItem *parentItem = m_currentItem;
+ m_currentItem = new QStandardItem;
+ m_currentItem->setEditable(false);
+ parentItem->appendRow(m_currentItem);
+ if (debug)
+ qDebug() << "QmlOutlineModel - Adding" << "element to" << parentItem->text();
+ } else {
+ m_currentItem = m_currentItem->child(0);
+ }
+ } else {
+ // sibling
+ if (m_currentItem->rowCount() <= siblingIndex) {
+ // attach
+ QStandardItem *oldItem = m_currentItem;
+ m_currentItem = new QStandardItem;
+ m_currentItem->setEditable(false);
+ oldItem->appendRow(m_currentItem);
+ if (debug)
+ qDebug() << "QmlOutlineModel - Adding" << "element to" << oldItem->text();
+ } else {
+ m_currentItem = m_currentItem->child(siblingIndex);
+ }
+ }
+
+ m_treePos.append(0);
+ m_currentItem->setData(QVariant::fromValue(location), SourceLocationRole);
+
+ return m_currentItem;
+}
+
+void QmlOutlineModel::leaveNode()
+{
+ int lastIndex = m_treePos.takeLast();
+
+
+ if (lastIndex > 0) {
+ // element has children
+ if (lastIndex < m_currentItem->rowCount()) {
+ if (debug)
+ qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << lastIndex << m_currentItem->rowCount() - lastIndex;
+ m_currentItem->removeRows(lastIndex, m_currentItem->rowCount() - lastIndex);
+ }
+ m_currentItem = parentItem();
+ } else {
+ if (m_currentItem->hasChildren()) {
+ if (debug)
+ qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << 0 << m_currentItem->rowCount();
+ m_currentItem->removeRows(0, m_currentItem->rowCount());
+ }
+ m_currentItem = parentItem();
+ }
+
+
+ m_treePos.last()++;
+}
+
+QStandardItem *QmlOutlineModel::parentItem()
+{
+ QStandardItem *parent = m_currentItem->parent();
+ if (!parent)
+ parent = invisibleRootItem();
+ return parent;
+}
+
+class QmlOutlineModelSync : protected AST::Visitor
+{
+public:
+ QmlOutlineModelSync(QmlOutlineModel *model) :
+ m_model(model),
+ indent(0)
+ {
+ }
+
+ void operator()(Document::Ptr doc)
+ {
+ m_nodeToIndex.clear();
+
+ if (debug)
+ qDebug() << "QmlOutlineModel ------";
+ m_model->startSync();
+ if (doc && doc->ast())
+ doc->ast()->accept(this);
+ }
+
+private:
+ bool preVisit(AST::Node *node)
+ {
+ if (!node)
+ return false;
+ if (debug)
+ qDebug() << "QmlOutlineModel -" << QByteArray(indent++, '-').constData() << node << typeid(*node).name();
+ return true;
+ }
+
+ void postVisit(AST::Node *)
+ {
+ indent--;
+ }
+
+ QString asString(AST::UiQualifiedId *id)
+ {
+ QString text;
+ for (; id; id = id->next) {
+ if (id->name)
+ text += id->name->asString();
+ else
+ text += QLatin1Char('?');
+
+ if (id->next)
+ text += QLatin1Char('.');
+ }
+
+ return text;
+ }
+
+ bool visit(AST::UiObjectDefinition *objDef)
+ {
+ AST::SourceLocation location;
+ location.offset = objDef->firstSourceLocation().offset;
+ location.length = objDef->lastSourceLocation().offset
+ - objDef->firstSourceLocation().offset
+ + objDef->lastSourceLocation().length;
+
+ QModelIndex index = m_model->enterElement(asString(objDef->qualifiedTypeNameId), location);
+ m_nodeToIndex.insert(objDef, index);
+ return true;
+ }
+
+ void endVisit(AST::UiObjectDefinition * /*objDefinition*/)
+ {
+ m_model->leaveElement();
+ }
+
+ bool visit(AST::UiScriptBinding *scriptBinding)
+ {
+ AST::SourceLocation location;
+ location.offset = scriptBinding->firstSourceLocation().offset;
+ location.length = scriptBinding->lastSourceLocation().offset
+ - scriptBinding->firstSourceLocation().offset
+ + scriptBinding->lastSourceLocation().length;
+
+ QModelIndex index = m_model->enterProperty(asString(scriptBinding->qualifiedId), location);
+ m_nodeToIndex.insert(scriptBinding, index);
+
+ return true;
+ }
+
+ void endVisit(AST::UiScriptBinding * /*scriptBinding*/)
+ {
+ m_model->leaveProperty();
+ }
+
+ QmlOutlineModel *m_model;
+ QHash<AST::Node*, QModelIndex> m_nodeToIndex;
+ int indent;
+};
+
+
+QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent) :
+ TextEditor::IOutlineWidget(parent),
+ m_treeView(new QmlJSOutlineTreeView()),
+ m_model(new QmlOutlineModel),
+ m_enableCursorSync(true),
+ m_blockCursorSync(false)
+{
+ QVBoxLayout *layout = new QVBoxLayout;
+
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ layout->addWidget(m_treeView);
+
+ setLayout(layout);
+
+ m_treeView->setModel(m_model);
+
+ connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+ this, SLOT(updateSelectionInText(QItemSelection)));
+}
+
+void QmlJSOutlineWidget::setEditor(QmlJSTextEditor *editor)
+{
+ m_editor = editor;
+
+ connect(m_editor.data(), SIGNAL(semanticInfoUpdated(QmlJSEditor::Internal::SemanticInfo)),
+ this, SLOT(updateOutline(QmlJSEditor::Internal::SemanticInfo)));
+ connect(m_editor.data(), SIGNAL(cursorPositionChanged()),
+ this, SLOT(updateSelectionInTree()));
+
+ updateOutline(m_editor.data()->semanticInfo());
+}
+
+void QmlJSOutlineWidget::setCursorSynchronization(bool syncWithCursor)
+{
+ m_enableCursorSync = syncWithCursor;
+ if (m_enableCursorSync)
+ updateSelectionInTree();
+}
+
+void QmlJSOutlineWidget::updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo)
+{
+ Document::Ptr doc = semanticInfo.document;
+
+ if (!doc) {
+ return;
+ }
+
+ if (!m_editor
+ || m_editor.data()->file()->fileName() != doc->fileName()
+ || m_editor.data()->documentRevision() != doc->editorRevision()) {
+ return;
+ }
+
+ if (doc->ast()
+ && m_model) {
+
+ // got a correctly parsed (or recovered) file.
+
+ if (QmlOutlineModel *qmlModel = qobject_cast<QmlOutlineModel*>(m_model)) {
+ QmlOutlineModelSync syncModel(qmlModel);
+ syncModel(doc);
+ }
+ } else {
+ // TODO: Maybe disable view?
+ }
+
+ m_treeView->expandAll();
+}
+
+QModelIndex QmlJSOutlineWidget::indexForPosition(const QModelIndex &rootIndex, int cursorPosition)
+{
+ if (!rootIndex.isValid())
+ return QModelIndex();
+
+ AST::SourceLocation location = rootIndex.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>();
+
+ if (!offsetInsideLocation(cursorPosition, location)) {
+ return QModelIndex();
+ }
+
+ const int rowCount = rootIndex.model()->rowCount(rootIndex);
+ for (int i = 0; i < rowCount; ++i) {
+ QModelIndex childIndex = rootIndex.child(i, 0);
+ QModelIndex resultIndex = indexForPosition(childIndex, cursorPosition);
+ if (resultIndex.isValid())
+ return resultIndex;
+ }
+
+ return rootIndex;
+}
+
+void QmlJSOutlineWidget::updateSelectionInTree()
+{
+ if (!syncCursor())
+ return;
+
+ int absoluteCursorPos = m_editor.data()->textCursor().position();
+ QModelIndex index = indexForPosition(m_model->index(0, 0), absoluteCursorPos);
+
+ m_blockCursorSync = true;
+ m_treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
+ m_blockCursorSync = false;
+}
+
+void QmlJSOutlineWidget::updateSelectionInText(const QItemSelection &selection)
+{
+ if (!syncCursor())
+ return;
+
+
+ if (!selection.indexes().isEmpty()) {
+ QModelIndex index = selection.indexes().first();
+ AST::SourceLocation location = index.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>();
+
+ QTextCursor textCursor = m_editor.data()->textCursor();
+ m_blockCursorSync = true;
+ textCursor.setPosition(location.offset);
+ m_editor.data()->setTextCursor(textCursor);
+ m_blockCursorSync = false;
+ }
+}
+
+bool QmlJSOutlineWidget::offsetInsideLocation(quint32 offset, const QmlJS::AST::SourceLocation &location)
+{
+ return ((offset >= location.offset)
+ && (offset <= location.offset + location.length));
+}
+
+bool QmlJSOutlineWidget::syncCursor()
+{
+ return m_enableCursorSync && !m_blockCursorSync;
+}
+
+bool QmlJSOutlineWidgetFactory::supportsEditor(Core::IEditor *editor) const
+{
+ if (qobject_cast<QmlJSEditorEditable*>(editor))
+ return true;
+ return false;
+}
+
+TextEditor::IOutlineWidget *QmlJSOutlineWidgetFactory::createWidget(Core::IEditor *editor)
+{
+ QmlJSOutlineWidget *widget = new QmlJSOutlineWidget;
+
+ QmlJSEditorEditable *qmlJSEditable = qobject_cast<QmlJSEditorEditable*>(editor);
+ QmlJSTextEditor *qmlJSEditor = qobject_cast<QmlJSTextEditor*>(qmlJSEditable->widget());
+ Q_ASSERT(qmlJSEditor);
+
+ widget->setEditor(qmlJSEditor);
+
+ return widget;
+}
+
+} // namespace Internal
+} // namespace QmlJSEditor
diff --git a/src/plugins/qmljseditor/qmljsoutline.h b/src/plugins/qmljseditor/qmljsoutline.h
new file mode 100644
index 0000000000..d2cef659c4
--- /dev/null
+++ b/src/plugins/qmljseditor/qmljsoutline.h
@@ -0,0 +1,108 @@
+#ifndef QMLJSOUTLINE_H
+#define QMLJSOUTLINE_H
+
+#include <qmljseditor.h>
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/inavigationwidgetfactory.h>
+#include <texteditor/ioutlinewidget.h>
+#include <qmljs/parser/qmljsastvisitor_p.h>
+#include <qmljs/qmljsdocument.h>
+#include <qmljs/qmljsicons.h>
+
+#include <QtGui/QStandardItemModel>
+#include <QtGui/QTreeView>
+#include <QtGui/QWidget>
+
+namespace Core {
+class IEditor;
+}
+
+namespace QmlJS {
+class Editor;
+};
+
+namespace QmlJSEditor {
+namespace Internal {
+
+class QmlOutlineModel : public QStandardItemModel
+{
+ Q_OBJECT
+public:
+ enum CustomRoles {
+ SourceLocationRole = Qt::UserRole + 1
+ };
+
+ QmlOutlineModel(QObject *parent = 0);
+
+ void startSync();
+
+ QModelIndex enterElement(const QString &typeName, const QmlJS::AST::SourceLocation &location);
+ void leaveElement();
+
+ QModelIndex enterProperty(const QString &name, const QmlJS::AST::SourceLocation &location);
+ void leaveProperty();
+
+private:
+ QStandardItem *enterNode(const QmlJS::AST::SourceLocation &location);
+ void leaveNode();
+
+ QStandardItem *parentItem();
+
+ QList<int> m_treePos;
+ QStandardItem *m_currentItem;
+ QmlJS::Icons m_icons;
+};
+
+class QmlJSOutlineTreeView : public QTreeView
+{
+ Q_OBJECT
+public:
+ QmlJSOutlineTreeView(QWidget *parent = 0);
+};
+
+
+class QmlJSOutlineWidget : public TextEditor::IOutlineWidget
+{
+ Q_OBJECT
+public:
+ QmlJSOutlineWidget(QWidget *parent = 0);
+
+ void setEditor(QmlJSTextEditor *editor);
+
+ // IOutlineWidget
+ virtual void setCursorSynchronization(bool syncWithCursor);
+
+private slots:
+ void updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo);
+ void updateSelectionInTree();
+ void updateSelectionInText(const QItemSelection &selection);
+
+private:
+ QModelIndex indexForPosition(const QModelIndex &rootIndex, int cursorPosition);
+ bool offsetInsideLocation(quint32 offset, const QmlJS::AST::SourceLocation &location);
+ bool syncCursor();
+
+private:
+ QmlJSOutlineTreeView *m_treeView;
+ QAbstractItemModel *m_model;
+ QWeakPointer<QmlJSTextEditor> m_editor;
+
+ bool m_enableCursorSync;
+ bool m_blockCursorSync;
+};
+
+class QmlJSOutlineWidgetFactory : public TextEditor::IOutlineWidgetFactory
+{
+ Q_OBJECT
+public:
+ bool supportsEditor(Core::IEditor *editor) const;
+ TextEditor::IOutlineWidget *createWidget(Core::IEditor *editor);
+};
+
+} // namespace Internal
+} // namespace QmlJSEditor
+
+Q_DECLARE_METATYPE(QmlJS::AST::SourceLocation);
+
+#endif // QMLJSOUTLINE_H