From 6eadeb052cea9a14cde26c733475b3afdc945702 Mon Sep 17 00:00:00 2001 From: Kai Koehne <kai.koehne@nokia.com> Date: Mon, 19 Jul 2010 11:39:41 +0200 Subject: [PATCH] QmlOutline: Show sensible tooltips Show the same tooltips as in the text editor. To accomplish this I refactored the QmlOutlineModel quite a bit ... --- src/plugins/qmljseditor/qmljseditor.cpp | 14 +- src/plugins/qmljseditor/qmloutlinemodel.cpp | 307 ++++++++++++-------- src/plugins/qmljseditor/qmloutlinemodel.h | 58 +++- 3 files changed, 239 insertions(+), 140 deletions(-) diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 3cf317bd2ad..4bd4dd88e48 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -826,8 +826,6 @@ void QmlJSTextEditor::onDocumentUpdated(QmlJS::Document::Ptr doc) const SemanticHighlighter::Source source = currentSource(/*force = */ true); m_semanticHighlighter->rehighlight(source); - - m_updateOutlineTimer->start(); } else { // show parsing errors QList<QTextEdit::ExtraSelection> selections; @@ -860,18 +858,15 @@ void QmlJSTextEditor::jumpToOutlineElement(int /*index*/) void QmlJSTextEditor::updateOutlineNow() { - const Snapshot snapshot = m_modelManager->snapshot(); - Document::Ptr document = snapshot.document(file()->fileName()); - - if (!document) + if (!m_semanticInfo.document) return; - if (document->editorRevision() != editorRevision()) { + if (m_semanticInfo.document->editorRevision() != editorRevision()) { m_updateOutlineTimer->start(); return; } - m_outlineModel->update(document, snapshot); + m_outlineModel->update(m_semanticInfo); QTreeView *treeView = static_cast<QTreeView*>(m_outlineCombo->view()); treeView->expandAll(); @@ -1590,6 +1585,9 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) } } + // update outline + m_updateOutlineTimer->start(); + // update warning/error extra selections QList<QTextEdit::ExtraSelection> selections; appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document()); diff --git a/src/plugins/qmljseditor/qmloutlinemodel.cpp b/src/plugins/qmljseditor/qmloutlinemodel.cpp index f91b78121c5..2dd378787b2 100644 --- a/src/plugins/qmljseditor/qmloutlinemodel.cpp +++ b/src/plugins/qmljseditor/qmloutlinemodel.cpp @@ -1,4 +1,5 @@ #include "qmloutlinemodel.h" +#include "qmljseditor.h" #include <qmljs/parser/qmljsastvisitor_p.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljslookupcontext.h> @@ -18,6 +19,32 @@ enum { namespace QmlJSEditor { namespace Internal { +QmlOutlineItem::QmlOutlineItem(QmlOutlineModel *model) : + m_outlineModel(model) +{ +} + +QVariant QmlOutlineItem::data(int role) const +{ + if (role == Qt::ToolTipRole) { + AST::SourceLocation location = data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>(); + AST::UiQualifiedId *uiQualifiedId = data(QmlOutlineModel::IdPointerRole).value<AST::UiQualifiedId*>(); + if (!uiQualifiedId) + return QVariant(); + + QList<AST::Node *> astPath = m_outlineModel->m_semanticInfo.astPath(location.begin()); + + QmlJS::Document::Ptr document = m_outlineModel->m_semanticInfo.document; + QmlJS::Snapshot snapshot = m_outlineModel->m_semanticInfo.snapshot; + LookupContext::Ptr lookupContext = LookupContext::create(document, snapshot, astPath); + const Interpreter::Value *value = lookupContext->evaluate(uiQualifiedId); + + return prettyPrint(value, lookupContext->context()); + } + + return QStandardItem::data(role); +} + QmlOutlineItem &QmlOutlineItem::copyValues(const QmlOutlineItem &other) { *this = other; @@ -25,6 +52,26 @@ QmlOutlineItem &QmlOutlineItem::copyValues(const QmlOutlineItem &other) return *this; } +QString QmlOutlineItem::prettyPrint(const QmlJS::Interpreter::Value *value, QmlJS::Interpreter::Context *context) const +{ + if (! value) + return QString(); + + if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { + const QString className = objectValue->className(); + if (!className.isEmpty()) { + return className; + } + } + + const QString typeId = context->engine()->typeId(value); + if (typeId == QLatin1String("undefined")) { + return QString(); + } + + return typeId; +} + class QmlOutlineModelSync : protected AST::Visitor { public: @@ -34,23 +81,14 @@ public: { } - void operator()(Document::Ptr doc, const Snapshot &snapshot) + void operator()(Document::Ptr doc) { m_nodeToIndex.clear(); - // Set up lookup context once to do the element type lookup - // - // We're simplifying here by using the root context everywhere - // (empty node list). However, creating the LookupContext is quite expensive (about 3ms), - // and there is AFAIK no way to introduce new type names in a sub-context. - m_context = LookupContext::create(doc, snapshot, QList<AST::Node*>()); - if (debug) qDebug() << "QmlOutlineModel ------"; if (doc && doc->ast()) doc->ast()->accept(this); - - m_context.clear(); } private: @@ -68,51 +106,22 @@ private: 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; - } - - typedef QPair<QString,QString> ElementType; bool visit(AST::UiObjectDefinition *objDef) { - AST::SourceLocation location = getLocation(objDef); - - const QString typeName = asString(objDef->qualifiedTypeNameId); - - if (!m_typeToIcon.contains(typeName)) { - m_typeToIcon.insert(typeName, getIcon(objDef)); - } - const QIcon icon = m_typeToIcon.value(typeName); - QString id = getId(objDef); - - QModelIndex index = m_model->enterElement(typeName, id, icon, location); + QModelIndex index = m_model->enterObjectDefinition(objDef); m_nodeToIndex.insert(objDef, index); return true; } void endVisit(AST::UiObjectDefinition * /*objDef*/) { - m_model->leaveElement(); + m_model->leaveObjectDefiniton(); } bool visit(AST::UiScriptBinding *scriptBinding) { - AST::SourceLocation location = getLocation(scriptBinding); - - QModelIndex index = m_model->enterProperty(asString(scriptBinding->qualifiedId), false, location); + QModelIndex index = m_model->enterScriptBinding(scriptBinding); m_nodeToIndex.insert(scriptBinding, index); return true; @@ -120,13 +129,12 @@ private: void endVisit(AST::UiScriptBinding * /*scriptBinding*/) { - m_model->leaveProperty(); + m_model->leaveScriptBinding(); } bool visit(AST::UiPublicMember *publicMember) { - AST::SourceLocation location = getLocation(publicMember); - QModelIndex index = m_model->enterProperty(publicMember->name->asString(), true, location); + QModelIndex index = m_model->enterPublicMember(publicMember); m_nodeToIndex.insert(publicMember, index); return true; @@ -134,65 +142,12 @@ private: void endVisit(AST::UiPublicMember * /*publicMember*/) { - m_model->leaveProperty(); - } - - QIcon getIcon(AST::UiObjectDefinition *objDef) { - const QmlJS::Interpreter::Value *value = m_context->evaluate(objDef->qualifiedTypeNameId); - - if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { - do { - QString module; - QString typeName; - if (const Interpreter::QmlObjectValue *qmlObjectValue = - dynamic_cast<const Interpreter::QmlObjectValue*>(objectValue)) { - module = qmlObjectValue->packageName(); - } - typeName = objectValue->className(); - - QIcon icon = m_model->m_icons->icon(module, typeName); - if (! icon.isNull()) - return icon; - - objectValue = objectValue->prototype(m_context->context()); - } while (objectValue); - } - return QIcon(); - } - - QString getId(AST::UiObjectDefinition *objDef) { - QString id; - for (AST::UiObjectMemberList *it = objDef->initializer->members; it; it = it->next) { - if (AST::UiScriptBinding *binding = dynamic_cast<AST::UiScriptBinding*>(it->member)) { - if (binding->qualifiedId->name->asString() == "id") { - AST::ExpressionStatement *expr = dynamic_cast<AST::ExpressionStatement*>(binding->statement); - if (!expr) - continue; - AST::IdentifierExpression *idExpr = dynamic_cast<AST::IdentifierExpression*>(expr->expression); - if (!idExpr) - continue; - id = idExpr->name->asString(); - break; - } - } - } - return id; - } - - AST::SourceLocation getLocation(AST::UiObjectMember *objMember) { - AST::SourceLocation location; - location.offset = objMember->firstSourceLocation().offset; - location.length = objMember->lastSourceLocation().offset - - objMember->firstSourceLocation().offset - + objMember->lastSourceLocation().length; - return location; + m_model->leavePublicMember(); } QmlOutlineModel *m_model; - LookupContext::Ptr m_context; QHash<AST::Node*, QModelIndex> m_nodeToIndex; - QHash<QString, QIcon> m_typeToIcon; int indent; }; @@ -206,66 +161,106 @@ QmlOutlineModel::QmlOutlineModel(QObject *parent) : QmlJS::Document::Ptr QmlOutlineModel::document() const { - return m_document; + return m_semanticInfo.document; } -void QmlOutlineModel::update(QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot) +void QmlOutlineModel::update(const SemanticInfo &semanticInfo) { - m_document = doc; + m_semanticInfo = semanticInfo; m_treePos.clear(); m_treePos.append(0); m_currentItem = invisibleRootItem(); + // Set up lookup context once to do the element type lookup + // + // We're simplifying here by using the root context everywhere + // (empty node list). However, creating the LookupContext is quite expensive (about 3ms), + // and there is AFAIK no way to introduce new type names in a sub-context. + m_context = LookupContext::create(semanticInfo.document, semanticInfo.snapshot, QList<AST::Node*>()); + m_typeToIcon.clear(); + QmlOutlineModelSync syncModel(this); - syncModel(doc, snapshot); + syncModel(m_semanticInfo.document); + + m_context.clear(); emit updated(); } -QModelIndex QmlOutlineModel::enterElement(const QString &type, const QString &id, const QIcon &icon, const AST::SourceLocation &sourceLocation) +QModelIndex QmlOutlineModel::enterObjectDefinition(AST::UiObjectDefinition *objDef) { - QmlOutlineItem prototype; + QmlOutlineItem prototype(this); + const QString typeName = asString(objDef->qualifiedTypeNameId); + const QString id = getId(objDef); if (!id.isEmpty()) { prototype.setText(id); } else { - prototype.setText(type); + prototype.setText(typeName); } - prototype.setIcon(icon); - prototype.setToolTip(type); + if (!m_typeToIcon.contains(typeName)) { + m_typeToIcon.insert(typeName, getIcon(objDef)); + } + prototype.setIcon(m_typeToIcon.value(typeName)); prototype.setData(ElementType, ItemTypeRole); - prototype.setData(QVariant::fromValue(sourceLocation), SourceLocationRole); + prototype.setData(QVariant::fromValue(getLocation(objDef)), SourceLocationRole); + prototype.setData(QVariant::fromValue(static_cast<AST::Node*>(objDef)), NodePointerRole); + prototype.setData(QVariant::fromValue(static_cast<AST::UiQualifiedId*>(objDef->qualifiedTypeNameId)), IdPointerRole); return enterNode(prototype); } -void QmlOutlineModel::leaveElement() +void QmlOutlineModel::leaveObjectDefiniton() { leaveNode(); } -QModelIndex QmlOutlineModel::enterProperty(const QString &name, bool isCustomProperty, const AST::SourceLocation &sourceLocation) +QModelIndex QmlOutlineModel::enterScriptBinding(AST::UiScriptBinding *scriptBinding) { - QmlOutlineItem prototype; + QmlOutlineItem prototype(this); - prototype.setText(name); - if (isCustomProperty) { - prototype.setIcon(m_icons->publicMemberIcon()); - } else { - prototype.setIcon(m_icons->scriptBindingIcon()); - } + prototype.setText(asString(scriptBinding->qualifiedId)); + prototype.setIcon(m_icons->scriptBindingIcon()); prototype.setData(PropertyType, ItemTypeRole); - prototype.setData(QVariant::fromValue(sourceLocation), SourceLocationRole); + prototype.setData(QVariant::fromValue(getLocation(scriptBinding)), SourceLocationRole); + prototype.setData(QVariant::fromValue(static_cast<AST::Node*>(scriptBinding)), NodePointerRole); + prototype.setData(QVariant::fromValue(static_cast<AST::UiQualifiedId*>(scriptBinding->qualifiedId)), IdPointerRole); return enterNode(prototype); } -void QmlOutlineModel::leaveProperty() +void QmlOutlineModel::leaveScriptBinding() { leaveNode(); } +QModelIndex QmlOutlineModel::enterPublicMember(AST::UiPublicMember *publicMember) +{ + QmlOutlineItem prototype(this); + + prototype.setText(publicMember->name->asString()); + prototype.setIcon(m_icons->publicMemberIcon()); + prototype.setData(PropertyType, ItemTypeRole); + prototype.setData(QVariant::fromValue(getLocation(publicMember)), SourceLocationRole); + prototype.setData(QVariant::fromValue(static_cast<AST::Node*>(publicMember)), NodePointerRole); + + return enterNode(prototype); +} + +void QmlOutlineModel::leavePublicMember() +{ + leaveNode(); +} + +QmlJS::AST::Node *QmlOutlineModel::nodeForIndex(const QModelIndex &index) +{ + if (index.isValid()) { + return index.data(NodePointerRole).value<QmlJS::AST::Node*>(); + } + return 0; +} + QModelIndex QmlOutlineModel::enterNode(const QmlOutlineItem &prototype) { int siblingIndex = m_treePos.last(); @@ -275,7 +270,7 @@ QModelIndex QmlOutlineModel::enterNode(const QmlOutlineItem &prototype) if (debug) qDebug() << "QmlOutlineModel - Adding" << "element to" << m_currentItem->text(); - QmlOutlineItem *newItem = new QmlOutlineItem; + QmlOutlineItem *newItem = new QmlOutlineItem(this); newItem->copyValues(prototype); newItem->setEditable(false); m_currentItem->appendRow(newItem); @@ -293,7 +288,7 @@ QModelIndex QmlOutlineModel::enterNode(const QmlOutlineItem &prototype) if (debug) qDebug() << "QmlOutlineModel - Adding" << "element to" << m_currentItem->text(); - QmlOutlineItem *newItem = new QmlOutlineItem; + QmlOutlineItem *newItem = new QmlOutlineItem(this); newItem->copyValues(prototype); newItem->setEditable(false); m_currentItem->appendRow(newItem); @@ -345,5 +340,73 @@ QStandardItem *QmlOutlineModel::parentItem() return parent; } + +QString QmlOutlineModel::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; +} + +AST::SourceLocation QmlOutlineModel::getLocation(AST::UiObjectMember *objMember) { + AST::SourceLocation location; + location.offset = objMember->firstSourceLocation().offset; + location.length = objMember->lastSourceLocation().offset + - objMember->firstSourceLocation().offset + + objMember->lastSourceLocation().length; + return location; +} + +QIcon QmlOutlineModel::getIcon(AST::UiObjectDefinition *objDef) { + const QmlJS::Interpreter::Value *value = m_context->evaluate(objDef->qualifiedTypeNameId); + + if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { + do { + QString module; + QString typeName; + if (const Interpreter::QmlObjectValue *qmlObjectValue = + dynamic_cast<const Interpreter::QmlObjectValue*>(objectValue)) { + module = qmlObjectValue->packageName(); + } + typeName = objectValue->className(); + + QIcon icon = m_icons->icon(module, typeName); + if (! icon.isNull()) + return icon; + + objectValue = objectValue->prototype(m_context->context()); + } while (objectValue); + } + return QIcon(); +} + +QString QmlOutlineModel::getId(AST::UiObjectDefinition *objDef) { + QString id; + for (AST::UiObjectMemberList *it = objDef->initializer->members; it; it = it->next) { + if (AST::UiScriptBinding *binding = dynamic_cast<AST::UiScriptBinding*>(it->member)) { + if (binding->qualifiedId->name->asString() == "id") { + AST::ExpressionStatement *expr = dynamic_cast<AST::ExpressionStatement*>(binding->statement); + if (!expr) + continue; + AST::IdentifierExpression *idExpr = dynamic_cast<AST::IdentifierExpression*>(expr->expression); + if (!idExpr) + continue; + id = idExpr->name->asString(); + break; + } + } + } + return id; +} + } // namespace Internal } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmloutlinemodel.h b/src/plugins/qmljseditor/qmloutlinemodel.h index 38fb36284eb..acc0890bc7a 100644 --- a/src/plugins/qmljseditor/qmloutlinemodel.h +++ b/src/plugins/qmljseditor/qmloutlinemodel.h @@ -1,18 +1,39 @@ #ifndef QMLOUTLINEMODEL_H #define QMLOUTLINEMODEL_H +#include "qmljseditor.h" #include <qmljs/qmljsdocument.h> #include <qmljs/qmljsicons.h> +#include <qmljs/qmljslookupcontext.h> #include <QStandardItemModel> +namespace QmlJS { +namespace Interpreter { +class Value; +class Context; +} +} + namespace QmlJSEditor { namespace Internal { +class QmlOutlineModel; + class QmlOutlineItem : public QStandardItem { public: - QmlOutlineItem ©Values(const QmlOutlineItem &other); // so that we can assign all values at once + QmlOutlineItem(QmlOutlineModel *model); + + //QStandardItem + QVariant data(int role = Qt::UserRole + 1) const; + + QmlOutlineItem ©Values(const QmlOutlineItem &other); // so that we can assign all values at onc + +private: + QString prettyPrint(const QmlJS::Interpreter::Value *value, QmlJS::Interpreter::Context *context) const; + + QmlOutlineModel *m_outlineModel; }; class QmlOutlineModel : public QStandardItemModel @@ -22,7 +43,9 @@ public: enum CustomRoles { SourceLocationRole = Qt::UserRole + 1, - ItemTypeRole = SourceLocationRole + 1 + ItemTypeRole, + NodePointerRole, + IdPointerRole }; enum ItemTypes { @@ -33,15 +56,18 @@ public: QmlOutlineModel(QObject *parent = 0); QmlJS::Document::Ptr document() const; - void update(QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot); + void update(const SemanticInfo &semanticInfo); + + QModelIndex enterObjectDefinition(QmlJS::AST::UiObjectDefinition *objectDefinition); + void leaveObjectDefiniton(); + + QModelIndex enterScriptBinding(QmlJS::AST::UiScriptBinding *scriptBinding); + void leaveScriptBinding(); - QModelIndex enterElement(const QString &typeName, const QString &id, const QIcon &icon, - const QmlJS::AST::SourceLocation &location); - void leaveElement(); + QModelIndex enterPublicMember(QmlJS::AST::UiPublicMember *publicMember); + void leavePublicMember(); - QModelIndex enterProperty(const QString &name, bool isCustomProperty, - const QmlJS::AST::SourceLocation &location); - void leaveProperty(); + QmlJS::AST::Node *nodeForIndex(const QModelIndex &index); signals: void updated(); @@ -52,17 +78,29 @@ private: QStandardItem *parentItem(); - QmlJS::Document::Ptr m_document; + static QString asString(QmlJS::AST::UiQualifiedId *id); + static QmlJS::AST::SourceLocation getLocation(QmlJS::AST::UiObjectMember *objMember); + QIcon getIcon(QmlJS::AST::UiObjectDefinition *objDef); + static QString getId(QmlJS::AST::UiObjectDefinition *objDef); + + + SemanticInfo m_semanticInfo; QList<int> m_treePos; QStandardItem *m_currentItem; QmlJS::Icons *m_icons; + QmlJS::LookupContext::Ptr m_context; + QHash<QString, QIcon> m_typeToIcon; + friend class QmlOutlineModelSync; + friend class QmlOutlineItem; }; } // namespace Internal } // namespace QmlJSEditor Q_DECLARE_METATYPE(QmlJS::AST::SourceLocation); +Q_DECLARE_METATYPE(QmlJS::AST::Node*); +Q_DECLARE_METATYPE(QmlJS::AST::UiQualifiedId*); #endif // QMLOUTLINEMODEL_H -- GitLab