diff options
author | Kai Koehne <kai.koehne@nokia.com> | 2010-07-08 11:32:45 +0200 |
---|---|---|
committer | Kai Koehne <kai.koehne@nokia.com> | 2010-07-08 14:02:51 +0200 |
commit | 02923cf2580ece7f1680059a3ed99f326b5dc724 (patch) | |
tree | dc0d5110396413fd690eb8bce20d4dce1fa863c6 /src/plugins/qmljseditor | |
parent | 1541dec6f34a6cbcec44391f87de2a7c2c00048b (diff) | |
download | qt-creator-02923cf2580ece7f1680059a3ed99f326b5dc724.tar.gz |
Support Outline sidebar for QML files
Diffstat (limited to 'src/plugins/qmljseditor')
-rw-r--r-- | src/plugins/qmljseditor/qmljseditor.pro | 6 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljseditorplugin.cpp | 3 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljsoutline.cpp | 393 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljsoutline.h | 108 |
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 |