From 94264617bfdda8fbfc31921dc9ba49ffff84eaeb Mon Sep 17 00:00:00 2001
From: Kai Koehne <kai.koehne@nokia.com>
Date: Mon, 12 Jul 2010 14:45:22 +0200
Subject: [PATCH] QmlJSEditor: Use QmlOutline model also in combo box

The drop down combo box and the Outline in the sidebar now share
the same model.
---
 src/plugins/qmljseditor/qmljseditor.cpp     | 99 +++++++++++++++------
 src/plugins/qmljseditor/qmljseditor.h       | 10 ++-
 src/plugins/qmljseditor/qmljsoutline.cpp    | 83 +++--------------
 src/plugins/qmljseditor/qmljsoutline.h      |  7 +-
 src/plugins/qmljseditor/qmloutlinemodel.cpp |  2 +
 src/plugins/qmljseditor/qmloutlinemodel.h   |  3 +
 6 files changed, 102 insertions(+), 102 deletions(-)

diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp
index 1d51d1be336..9142231c5c7 100644
--- a/src/plugins/qmljseditor/qmljseditor.cpp
+++ b/src/plugins/qmljseditor/qmljseditor.cpp
@@ -32,6 +32,7 @@
 #include "qmljshighlighter.h"
 #include "qmljseditorplugin.h"
 #include "qmljsmodelmanager.h"
+#include "qmloutlinemodel.h"
 
 #include <qmljs/qmljsindenter.h>
 #include <qmljs/qmljsinterpreter.h>
@@ -70,9 +71,11 @@
 
 #include <QtGui/QMenu>
 #include <QtGui/QComboBox>
+#include <QtGui/QHeaderView>
 #include <QtGui/QInputDialog>
 #include <QtGui/QMainWindow>
 #include <QtGui/QToolBar>
+#include <QtGui/QTreeView>
 
 enum {
     UPDATE_DOCUMENT_DEFAULT_INTERVAL = 50,
@@ -612,6 +615,7 @@ QString QmlJSEditorEditable::preferredMode() const
 QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) :
     TextEditor::BaseTextEditor(parent),
     m_methodCombo(0),
+    m_outlineModel(new QmlOutlineModel(this)),
     m_modelManager(0),
     m_contextPane(0)
 {
@@ -688,6 +692,16 @@ bool QmlJSTextEditor::isOutdated() const
     return false;
 }
 
+QmlOutlineModel *QmlJSTextEditor::outlineModel() const
+{
+    return m_outlineModel;
+}
+
+QModelIndex QmlJSTextEditor::outlineModelIndex() const
+{
+    return m_outlineModelIndex;
+}
+
 Core::IEditor *QmlJSEditorEditable::duplicate(QWidget *parent)
 {
     QmlJSTextEditor *newEditor = new QmlJSTextEditor(parent);
@@ -794,33 +808,37 @@ void QmlJSTextEditor::modificationChanged(bool changed)
         m_modelManager->fileChangedOnDisk(file()->fileName());
 }
 
-void QmlJSTextEditor::jumpToMethod(int index)
+void QmlJSTextEditor::jumpToMethod(int /*index*/)
 {
-    if (index > 0 && index <= m_semanticInfo.declarations.size()) { // indexes are 1-based
-        Declaration d = m_semanticInfo.declarations.at(index - 1);
-        gotoLine(d.startLine, d.startColumn - 1);
-        setFocus();
-    }
+    QModelIndex index = m_methodCombo->view()->currentIndex();
+    AST::SourceLocation location = index.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>();
+
+    QTextCursor cursor = textCursor();
+    cursor.setPosition(location.offset);
+    setTextCursor(cursor);
+
+    setFocus();
 }
 
 void QmlJSTextEditor::updateMethodBoxIndex()
 {
-    int line = 0, column = 0;
-    convertPosition(position(), &line, &column);
+    m_outlineModelIndex = indexForPosition(position());
+    emit outlineModelIndexChanged(m_outlineModelIndex);
 
-    int currentSymbolIndex = 0;
+    QModelIndex comboIndex = m_outlineModelIndex;
 
-    int index = 0;
-    while (index < m_semanticInfo.declarations.size()) {
-        const Declaration &d = m_semanticInfo.declarations.at(index++);
+    if (comboIndex.isValid()) {
+        bool blocked = m_methodCombo->blockSignals(true);
 
-        if (line < d.startLine)
-            break;
-        else
-            currentSymbolIndex = index;
+        // There is no direct way to select a non-root item
+        m_methodCombo->setRootModelIndex(comboIndex.parent());
+        m_methodCombo->setCurrentIndex(comboIndex.row());
+        m_methodCombo->setRootModelIndex(QModelIndex());
+
+        updateMethodBoxToolTip();
+        m_methodCombo->blockSignals(blocked);
     }
 
-    m_methodCombo->setCurrentIndex(currentSymbolIndex);
     updateUses();
 }
 
@@ -983,6 +1001,14 @@ void QmlJSTextEditor::createToolBar(QmlJSEditorEditable *editable)
 {
     m_methodCombo = new QComboBox;
     m_methodCombo->setMinimumContentsLength(22);
+    m_methodCombo->setModel(m_outlineModel);
+
+    QTreeView *treeView = new QTreeView;
+    treeView->header()->hide();
+    treeView->setItemsExpandable(false);
+    m_methodCombo->setView(treeView);
+    treeView->expandAll();
+
     //m_methodCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
 
     // Make the combo box prefer to expand
@@ -1332,15 +1358,14 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo)
         FindDeclarations findDeclarations;
         m_semanticInfo.declarations = findDeclarations(doc->ast());
 
-        QStringList items;
-        items.append(tr("<Select Symbol>"));
+        m_outlineModel->update(doc);
+        updateMethodBoxIndex();
 
-        foreach (Declaration decl, m_semanticInfo.declarations)
-            items.append(decl.text);
+        QTreeView *treeView = static_cast<QTreeView*>(m_methodCombo->view());
+        treeView->expandAll();
+        // ComboBox only let's you select top level indexes for a QAbstractItemModel!
+        // therefore we've to fake a treeview by listview + indentation
 
-        m_methodCombo->clear();
-        m_methodCombo->addItems(items);
-        updateMethodBoxIndex();
         if (m_contextPane) {
             Node *newNode = m_semanticInfo.declaringMember(position());
             if (newNode) {
@@ -1355,8 +1380,6 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo)
     appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
     appendExtraSelectionsForMessages(&selections, m_semanticInfo.semanticMessages, document());
     setExtraSelections(CodeWarningsSelection, selections);
-
-    emit semanticInfoUpdated(semanticInfo);
 }
 
 void QmlJSTextEditor::onCursorPositionChanged()
@@ -1372,6 +1395,30 @@ void QmlJSTextEditor::onCursorPositionChanged()
     }
 }
 
+QModelIndex QmlJSTextEditor::indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex) const
+{
+    QModelIndex lastIndex = rootIndex;
+
+
+    const int rowCount = m_outlineModel->rowCount(rootIndex);
+    for (int i = 0; i < rowCount; ++i) {
+        QModelIndex childIndex = m_outlineModel->index(i, 0, rootIndex);
+        AST::SourceLocation location = childIndex.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>();
+
+        if ((cursorPosition >= location.offset)
+              && (cursorPosition <= location.offset + location.length)) {
+            lastIndex = childIndex;
+            break;
+        }
+    }
+
+    if (lastIndex != rootIndex) {
+        // recurse
+        lastIndex = indexForPosition(cursorPosition, lastIndex);
+    }
+    return lastIndex;
+}
+
 SemanticHighlighter::Source QmlJSTextEditor::currentSource(bool force)
 {
     int line = 0, column = 0;
diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h
index 0239050713c..49910dbf545 100644
--- a/src/plugins/qmljseditor/qmljseditor.h
+++ b/src/plugins/qmljseditor/qmljseditor.h
@@ -35,6 +35,7 @@
 #include <texteditor/basetexteditor.h>
 
 #include <QtCore/QWaitCondition>
+#include <QtCore/QModelIndex>
 #include <QtCore/QMutex>
 #include <QtCore/QThread>
 
@@ -58,6 +59,7 @@ class Highlighter;
 namespace Internal {
 
 class QmlJSTextEditor;
+class QmlOutlineModel;
 
 class QmlJSEditorEditable : public TextEditor::BaseTextEditorEditable
 {
@@ -214,12 +216,15 @@ public:
     int documentRevision() const;
     bool isOutdated() const;
 
+    QmlOutlineModel *outlineModel() const;
+    QModelIndex outlineModelIndex() const;
+
 public slots:
     void followSymbolUnderCursor();
     virtual void setFontSettings(const TextEditor::FontSettings &);
 
 signals:
-    void semanticInfoUpdated(const QmlJSEditor::Internal::SemanticInfo &semanticInfo);
+    void outlineModelIndexChanged(const QModelIndex &index);
 
 private slots:
     void onDocumentUpdated(QmlJS::Document::Ptr doc);
@@ -266,6 +271,7 @@ private:
     QString wordUnderCursor() const;
 
     SemanticHighlighter::Source currentSource(bool force = false);
+    QModelIndex indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex = QModelIndex()) const;
 
     const Core::Context m_context;
 
@@ -273,6 +279,8 @@ private:
     QTimer *m_updateUsesTimer;
     QTimer *m_semanticRehighlightTimer;
     QComboBox *m_methodCombo;
+    QmlOutlineModel *m_outlineModel;
+    QModelIndex m_outlineModelIndex;
     QmlJS::ModelManagerInterface *m_modelManager;
     QTextCharFormat m_occurrencesFormat;
     QTextCharFormat m_occurrencesUnusedFormat;
diff --git a/src/plugins/qmljseditor/qmljsoutline.cpp b/src/plugins/qmljseditor/qmljsoutline.cpp
index a95c0842c90..a283ed8d16a 100644
--- a/src/plugins/qmljseditor/qmljsoutline.cpp
+++ b/src/plugins/qmljseditor/qmljsoutline.cpp
@@ -4,6 +4,7 @@
 #include <coreplugin/ifile.h>
 #include <QtGui/QVBoxLayout>
 
+#include <QDebug>
 using namespace QmlJS;
 
 enum {
@@ -29,8 +30,7 @@ QmlJSOutlineTreeView::QmlJSOutlineTreeView(QWidget *parent) :
 
 QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent) :
     TextEditor::IOutlineWidget(parent),
-    m_treeView(new QmlJSOutlineTreeView()),
-    m_model(new QmlOutlineModel),
+    m_treeView(new QmlJSOutlineTreeView(this)),
     m_enableCursorSync(true),
     m_blockCursorSync(false)
 {
@@ -41,91 +41,40 @@ QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent) :
     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()));
+    m_treeView->setModel(m_editor.data()->outlineModel());
+    connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+            this, SLOT(updateSelectionInText(QItemSelection)));
 
-    updateOutline(m_editor.data()->semanticInfo());
+    connect(m_editor.data(), SIGNAL(outlineModelIndexChanged(QModelIndex)),
+            this, SLOT(updateSelectionInTree(QModelIndex)));
+    connect(m_editor.data()->outlineModel(), SIGNAL(updated()),
+            this, SLOT(modelUpdated()));
 }
 
 void QmlJSOutlineWidget::setCursorSynchronization(bool syncWithCursor)
 {
     m_enableCursorSync = syncWithCursor;
     if (m_enableCursorSync)
-        updateSelectionInTree();
+        updateSelectionInTree(m_editor.data()->outlineModelIndex());
 }
 
-void QmlJSOutlineWidget::updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo)
+void QmlJSOutlineWidget::modelUpdated()
 {
-    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)) {
-            qmlModel->update(doc);
-        }
-    } else {
-        // TODO: Maybe disable view?
-    }
-
     m_treeView->expandAll();
+    updateSelectionInTree(m_editor.data()->outlineModelIndex());
 }
 
-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()
+void QmlJSOutlineWidget::updateSelectionInTree(const QModelIndex &index)
 {
     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_treeView->scrollTo(index);
@@ -150,12 +99,6 @@ void QmlJSOutlineWidget::updateSelectionInText(const QItemSelection &selection)
     }
 }
 
-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;
diff --git a/src/plugins/qmljseditor/qmljsoutline.h b/src/plugins/qmljseditor/qmljsoutline.h
index 14441eff865..c42685dd8d6 100644
--- a/src/plugins/qmljseditor/qmljsoutline.h
+++ b/src/plugins/qmljseditor/qmljsoutline.h
@@ -37,18 +37,15 @@ public:
     virtual void setCursorSynchronization(bool syncWithCursor);
 
 private slots:
-    void updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo);
-    void updateSelectionInTree();
+    void modelUpdated();
+    void updateSelectionInTree(const QModelIndex &index);
     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;
diff --git a/src/plugins/qmljseditor/qmloutlinemodel.cpp b/src/plugins/qmljseditor/qmloutlinemodel.cpp
index e0d7129640a..33e2269cf97 100644
--- a/src/plugins/qmljseditor/qmloutlinemodel.cpp
+++ b/src/plugins/qmljseditor/qmloutlinemodel.cpp
@@ -124,6 +124,8 @@ void QmlOutlineModel::update(QmlJS::Document::Ptr doc)
 
     QmlOutlineModelSync syncModel(this);
     syncModel(doc);
+
+    emit updated();
 }
 
 QModelIndex QmlOutlineModel::enterElement(const QString &type, const AST::SourceLocation &sourceLocation)
diff --git a/src/plugins/qmljseditor/qmloutlinemodel.h b/src/plugins/qmljseditor/qmloutlinemodel.h
index 67c2628f0dc..e5065238d91 100644
--- a/src/plugins/qmljseditor/qmloutlinemodel.h
+++ b/src/plugins/qmljseditor/qmloutlinemodel.h
@@ -27,6 +27,9 @@ public:
     QModelIndex enterProperty(const QString &name, const QmlJS::AST::SourceLocation &location);
     void leaveProperty();
 
+signals:
+    void updated();
+
 private:
     QStandardItem *enterNode(const QmlJS::AST::SourceLocation &location);
     void leaveNode();
-- 
GitLab