| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSCriptTools module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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 Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** 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-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qscriptdebuggerlocalsmodel_p.h" |
| #include "qscriptdebuggercommandschedulerjob_p.h" |
| #include "qscriptdebuggervalue_p.h" |
| #include "qscriptdebuggerresponse_p.h" |
| #include "qscriptdebuggerevent_p.h" |
| #include "qscriptdebuggervalueproperty_p.h" |
| #include "qscriptdebuggercommandschedulerinterface_p.h" |
| #include "qscriptdebuggercommandschedulerfrontend_p.h" |
| #include "qscriptdebuggerjobschedulerinterface_p.h" |
| #include "qscriptdebuggerobjectsnapshotdelta_p.h" |
| |
| #include "private/qabstractitemmodel_p.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qcoreapplication.h> |
| #include <QtCore/qpointer.h> |
| #include <QtGui/qbrush.h> |
| #include <QtGui/qfont.h> |
| |
| Q_DECLARE_METATYPE(QScriptDebuggerObjectSnapshotDelta) |
| |
| QT_BEGIN_NAMESPACE |
| |
| struct QScriptDebuggerLocalsModelNode |
| { |
| enum PopulationState { |
| NotPopulated, |
| Populating, |
| Populated |
| }; |
| |
| QScriptDebuggerLocalsModelNode() |
| : parent(0), populationState(NotPopulated), snapshotId(-1), changed(false) {} |
| |
| QScriptDebuggerLocalsModelNode( |
| const QScriptDebuggerValueProperty &prop, |
| QScriptDebuggerLocalsModelNode *par) |
| : property(prop), parent(par), |
| populationState(NotPopulated), snapshotId(-1), changed(false) |
| { |
| parent->children.append(this); |
| } |
| |
| ~QScriptDebuggerLocalsModelNode() { qDeleteAll(children); } |
| |
| QScriptDebuggerLocalsModelNode *findChild(const QString &name) |
| { |
| for (int i = 0; i < children.size(); ++i) { |
| QScriptDebuggerLocalsModelNode *child = children.at(i); |
| if (child->property.name() == name) |
| return child; |
| } |
| return 0; |
| } |
| |
| QScriptDebuggerValueProperty property; |
| QScriptDebuggerLocalsModelNode *parent; |
| QList<QScriptDebuggerLocalsModelNode*> children; |
| PopulationState populationState; |
| int snapshotId; |
| bool changed; |
| }; |
| |
| class QScriptDebuggerLocalsModelPrivate |
| : public QAbstractItemModelPrivate |
| { |
| Q_DECLARE_PUBLIC(QScriptDebuggerLocalsModel) |
| public: |
| QScriptDebuggerLocalsModelPrivate(); |
| ~QScriptDebuggerLocalsModelPrivate(); |
| |
| static QScriptDebuggerLocalsModelPrivate *get(QScriptDebuggerLocalsModel *q); |
| |
| QModelIndex addTopLevelObject(const QString &name, const QScriptDebuggerValue &object); |
| |
| QScriptDebuggerLocalsModelNode *nodeFromIndex(const QModelIndex &index) const; |
| QModelIndex indexFromNode(QScriptDebuggerLocalsModelNode *node) const; |
| bool isTopLevelNode(QScriptDebuggerLocalsModelNode *node) const; |
| |
| void populateIndex(const QModelIndex &index); |
| void reallyPopulateIndex(const QModelIndex &index, |
| const QScriptDebuggerValuePropertyList &props); |
| void syncIndex(const QModelIndex &index); |
| void reallySyncIndex(const QModelIndex &index, |
| const QScriptDebuggerObjectSnapshotDelta &delta); |
| void syncTopLevelNodes(); |
| void removeTopLevelNodes(); |
| void emitScopeObjectAvailable(const QModelIndex &index); |
| |
| void emitDataChanged(const QModelIndex &tl, const QModelIndex &br); |
| void removeChild(const QModelIndex &parentIndex, |
| QScriptDebuggerLocalsModelNode *parentNode, int row); |
| void depopulate(QScriptDebuggerLocalsModelNode *node); |
| void repopulate(QScriptDebuggerLocalsModelNode *node); |
| void addChildren(const QModelIndex &parentIndex, |
| QScriptDebuggerLocalsModelNode *parentNode, |
| const QScriptDebuggerValuePropertyList &props); |
| |
| void deleteObjectSnapshots(const QList<qint64> &snapshotIds); |
| void deleteAllObjectSnapshots(); |
| |
| QScriptDebuggerJobSchedulerInterface *jobScheduler; |
| QScriptDebuggerCommandSchedulerInterface *commandScheduler; |
| QScriptDebuggerLocalsModelNode *invisibleRootNode; |
| int frameIndex; |
| }; |
| |
| QScriptDebuggerLocalsModelPrivate::QScriptDebuggerLocalsModelPrivate() |
| { |
| invisibleRootNode = new QScriptDebuggerLocalsModelNode(); |
| frameIndex = -1; |
| } |
| |
| QScriptDebuggerLocalsModelPrivate::~QScriptDebuggerLocalsModelPrivate() |
| { |
| delete invisibleRootNode; |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::emitDataChanged(const QModelIndex &tl, const QModelIndex &br) |
| { |
| q_func()->dataChanged(tl, br); |
| } |
| |
| static QList<qint64> findSnapshotIdsRecursively(QScriptDebuggerLocalsModelNode *root) |
| { |
| QList<qint64> result; |
| if (root->snapshotId == -1) { |
| Q_ASSERT(root->children.isEmpty()); |
| return result; |
| } |
| QList<QScriptDebuggerLocalsModelNode*> nodeStack; |
| nodeStack.append(root); |
| while (!nodeStack.isEmpty()) { |
| QScriptDebuggerLocalsModelNode *node = nodeStack.takeFirst(); |
| result.append(node->snapshotId); |
| for (int i = 0; i < node->children.count(); ++i) { |
| QScriptDebuggerLocalsModelNode *child = node->children.at(i); |
| if (child->snapshotId != -1) |
| nodeStack.prepend(child); |
| } |
| } |
| return result; |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::removeChild(const QModelIndex &parentIndex, |
| QScriptDebuggerLocalsModelNode *parentNode, |
| int row) |
| { |
| Q_Q(QScriptDebuggerLocalsModel); |
| q->beginRemoveRows(parentIndex, row, row); |
| QScriptDebuggerLocalsModelNode *child = parentNode->children.takeAt(row); |
| QList<qint64> snapshotIds = findSnapshotIdsRecursively(child); |
| delete child; |
| q->endRemoveRows(); |
| deleteObjectSnapshots(snapshotIds); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::depopulate(QScriptDebuggerLocalsModelNode *node) |
| { |
| Q_Q(QScriptDebuggerLocalsModel); |
| bool hasChildren = !node->children.isEmpty(); |
| if (hasChildren) |
| q->beginRemoveRows(indexFromNode(node), 0, node->children.count() - 1); |
| QList<qint64> snapshotIds = findSnapshotIdsRecursively(node); |
| qDeleteAll(node->children); |
| node->children.clear(); |
| node->snapshotId = -1; |
| node->populationState = QScriptDebuggerLocalsModelNode::NotPopulated; |
| if (hasChildren) |
| q->endRemoveRows(); |
| deleteObjectSnapshots(snapshotIds); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::repopulate(QScriptDebuggerLocalsModelNode *node) |
| { |
| if (node->populationState != QScriptDebuggerLocalsModelNode::Populated) |
| return; |
| depopulate(node); |
| if (node->property.value().type() == QScriptDebuggerValue::ObjectValue) |
| populateIndex(indexFromNode(node)); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::addChildren(const QModelIndex &parentIndex, |
| QScriptDebuggerLocalsModelNode *parentNode, |
| const QScriptDebuggerValuePropertyList &props) |
| { |
| Q_Q(QScriptDebuggerLocalsModel); |
| if (props.isEmpty()) |
| return; |
| int first = parentNode->children.size(); |
| int last = first + props.size() - 1; |
| q->beginInsertRows(parentIndex, first, last); |
| for (int i = 0; i < props.size(); ++i) |
| new QScriptDebuggerLocalsModelNode(props.at(i), parentNode); |
| q->endInsertRows(); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::deleteObjectSnapshots(const QList<qint64> &snapshotIds) |
| { |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler, 0); |
| for (int i = 0; i < snapshotIds.size(); ++i) |
| frontend.scheduleDeleteScriptObjectSnapshot(snapshotIds.at(i)); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::deleteAllObjectSnapshots() |
| { |
| QList<qint64> snapshotIds; |
| for (int i = 0; i < invisibleRootNode->children.count(); ++i) |
| snapshotIds += findSnapshotIdsRecursively(invisibleRootNode->children.at(i)); |
| deleteObjectSnapshots(snapshotIds); |
| } |
| |
| QScriptDebuggerLocalsModelPrivate *QScriptDebuggerLocalsModelPrivate::get(QScriptDebuggerLocalsModel *q) |
| { |
| return q->d_func(); |
| } |
| |
| namespace { |
| |
| class SetPropertyJob : public QScriptDebuggerCommandSchedulerJob |
| { |
| public: |
| SetPropertyJob(const QPersistentModelIndex &index, |
| const QString &expression, |
| QScriptDebuggerCommandSchedulerInterface *scheduler) |
| : QScriptDebuggerCommandSchedulerJob(scheduler), |
| m_index(index), m_expression(expression), m_state(0) {} |
| |
| QScriptDebuggerLocalsModelPrivate *model() const |
| { |
| if (!m_index.isValid()) |
| return 0; |
| QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model()); |
| QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m); |
| return QScriptDebuggerLocalsModelPrivate::get(lm); |
| } |
| |
| void start() |
| { |
| if (!m_index.isValid()) { |
| // nothing to do, the node has been removed |
| return; |
| } |
| QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index); |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleEvaluate(model()->frameIndex, m_expression, |
| QString::fromLatin1("set property '%0' (%1)") |
| .arg(node->property.name()) |
| .arg(QDateTime::currentDateTime().toString())); |
| } |
| |
| void handleResponse(const QScriptDebuggerResponse &, int) |
| { |
| switch (m_state) { |
| case 0: |
| hibernateUntilEvaluateFinished(); |
| ++m_state; |
| break; |
| case 1: |
| finish(); |
| break; |
| } |
| } |
| |
| void evaluateFinished(const QScriptDebuggerValue &result) |
| { |
| if (!m_index.isValid()) { |
| // nothing to do, the node has been removed |
| return; |
| } |
| QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index); |
| Q_ASSERT(node->parent != 0); |
| QScriptDebuggerValue object = node->parent->property.value(); |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleSetScriptValueProperty(object, node->property.name(), result); |
| } |
| |
| private: |
| QPersistentModelIndex m_index; |
| QString m_expression; |
| int m_state; |
| }; |
| |
| } // namespace |
| |
| QScriptDebuggerLocalsModelNode *QScriptDebuggerLocalsModelPrivate::nodeFromIndex( |
| const QModelIndex &index) const |
| { |
| if (!index.isValid()) |
| return invisibleRootNode; |
| return static_cast<QScriptDebuggerLocalsModelNode*>(index.internalPointer()); |
| } |
| |
| QModelIndex QScriptDebuggerLocalsModelPrivate::indexFromNode( |
| QScriptDebuggerLocalsModelNode *node) const |
| { |
| if (!node || (node == invisibleRootNode)) |
| return QModelIndex(); |
| QScriptDebuggerLocalsModelNode *par = node->parent; |
| int row = par ? par->children.indexOf(node) : 0; |
| return createIndex(row, 0, node); |
| } |
| |
| bool QScriptDebuggerLocalsModelPrivate::isTopLevelNode(QScriptDebuggerLocalsModelNode *node) const |
| { |
| return node && (node->parent == invisibleRootNode); |
| } |
| |
| namespace { |
| |
| class PopulateModelIndexJob : public QScriptDebuggerCommandSchedulerJob |
| { |
| public: |
| PopulateModelIndexJob(const QPersistentModelIndex &index, |
| QScriptDebuggerCommandSchedulerInterface *scheduler) |
| : QScriptDebuggerCommandSchedulerJob(scheduler), |
| m_index(index), m_state(0) |
| { } |
| |
| QScriptDebuggerLocalsModelPrivate *model() const |
| { |
| if (!m_index.isValid()) |
| return 0; |
| QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model()); |
| QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m); |
| return QScriptDebuggerLocalsModelPrivate::get(lm); |
| } |
| |
| void start() |
| { |
| if (!m_index.isValid()) { |
| // nothing to do, the node has been removed |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleNewScriptObjectSnapshot(); |
| } |
| |
| void handleResponse(const QScriptDebuggerResponse &response, |
| int) |
| { |
| if (!m_index.isValid()) { |
| // the node has been removed |
| finish(); |
| return; |
| } |
| switch (m_state) { |
| case 0: { |
| QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index); |
| Q_ASSERT(node->populationState == QScriptDebuggerLocalsModelNode::Populating); |
| node->snapshotId = response.resultAsInt(); |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleScriptObjectSnapshotCapture(node->snapshotId, node->property.value()); |
| ++m_state; |
| } break; |
| case 1: { |
| QScriptDebuggerObjectSnapshotDelta delta; |
| delta = qvariant_cast<QScriptDebuggerObjectSnapshotDelta>(response.result()); |
| Q_ASSERT(delta.removedProperties.isEmpty()); |
| Q_ASSERT(delta.changedProperties.isEmpty()); |
| QScriptDebuggerValuePropertyList props = delta.addedProperties; |
| model()->reallyPopulateIndex(m_index, props); |
| finish(); |
| } break; |
| } |
| } |
| |
| private: |
| QPersistentModelIndex m_index; |
| int m_state; |
| }; |
| |
| } // namespace |
| |
| void QScriptDebuggerLocalsModelPrivate::populateIndex( |
| const QModelIndex &index) |
| { |
| if (!index.isValid()) |
| return; |
| QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index); |
| if (node->populationState != QScriptDebuggerLocalsModelNode::NotPopulated) |
| return; |
| if (node->property.value().type() != QScriptDebuggerValue::ObjectValue) |
| return; |
| node->populationState = QScriptDebuggerLocalsModelNode::Populating; |
| QScriptDebuggerJob *job = new PopulateModelIndexJob(index, commandScheduler); |
| jobScheduler->scheduleJob(job); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::reallyPopulateIndex( |
| const QModelIndex &index, |
| const QScriptDebuggerValuePropertyList &props) |
| { |
| if (!index.isValid()) |
| return; |
| QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index); |
| Q_ASSERT(node->populationState == QScriptDebuggerLocalsModelNode::Populating); |
| node->populationState = QScriptDebuggerLocalsModelNode::Populated; |
| addChildren(index, node, props); |
| } |
| |
| QScriptDebuggerLocalsModel::QScriptDebuggerLocalsModel( |
| QScriptDebuggerJobSchedulerInterface *jobScheduler, |
| QScriptDebuggerCommandSchedulerInterface *commandScheduler, |
| QObject *parent) |
| : QAbstractItemModel(*new QScriptDebuggerLocalsModelPrivate, parent) |
| { |
| Q_D(QScriptDebuggerLocalsModel); |
| d->jobScheduler = jobScheduler; |
| d->commandScheduler = commandScheduler; |
| } |
| |
| QScriptDebuggerLocalsModel::~QScriptDebuggerLocalsModel() |
| { |
| } |
| |
| QModelIndex QScriptDebuggerLocalsModelPrivate::addTopLevelObject(const QString &name, const QScriptDebuggerValue &object) |
| { |
| Q_Q(QScriptDebuggerLocalsModel); |
| QScriptDebuggerLocalsModelNode *node = invisibleRootNode->findChild(name); |
| if (node) |
| return indexFromNode(node); |
| QScriptDebuggerValueProperty prop(name, object, |
| QString::fromLatin1(""), // ### string representation of object |
| /*flags=*/0); |
| int rowIndex = invisibleRootNode->children.size(); |
| q->beginInsertRows(QModelIndex(), rowIndex, rowIndex); |
| node = new QScriptDebuggerLocalsModelNode(prop, invisibleRootNode); |
| q->endInsertRows(); |
| return indexFromNode(node); |
| } |
| |
| namespace { |
| |
| class InitModelJob : public QScriptDebuggerCommandSchedulerJob |
| { |
| public: |
| InitModelJob(QScriptDebuggerLocalsModel *model, |
| int frameIndex, |
| QScriptDebuggerCommandSchedulerInterface *scheduler) |
| : QScriptDebuggerCommandSchedulerJob(scheduler), |
| m_model(model), m_frameIndex(frameIndex), m_state(0) |
| { } |
| |
| void start() |
| { |
| if (!m_model) { |
| // Model has been deleted. |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleGetScopeChain(m_frameIndex); |
| } |
| |
| void handleResponse(const QScriptDebuggerResponse &response, |
| int) |
| { |
| if (!m_model) { |
| // Model has been deleted. |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| QScriptDebuggerLocalsModelPrivate *model_d = QScriptDebuggerLocalsModelPrivate::get(m_model); |
| switch (m_state) { |
| case 0: { |
| QScriptDebuggerValueList scopeChain = response.resultAsScriptValueList(); |
| for (int i = 0; i < scopeChain.size(); ++i) { |
| const QScriptDebuggerValue &scopeObject = scopeChain.at(i); |
| QString name = QString::fromLatin1("Scope"); |
| if (i > 0) |
| name.append(QString::fromLatin1(" (%0)").arg(i)); |
| QModelIndex index = model_d->addTopLevelObject(name, scopeObject); |
| if (i == 0) |
| model_d->emitScopeObjectAvailable(index); |
| } |
| frontend.scheduleGetThisObject(m_frameIndex); |
| ++m_state; |
| } break; |
| case 1: { |
| QScriptDebuggerValue thisObject = response.resultAsScriptValue(); |
| model_d->addTopLevelObject(QLatin1String("this"), thisObject); |
| finish(); |
| } break; |
| } |
| } |
| |
| private: |
| QPointer<QScriptDebuggerLocalsModel> m_model; |
| int m_frameIndex; |
| int m_state; |
| }; |
| |
| } // namespace |
| |
| void QScriptDebuggerLocalsModel::init(int frameIndex) |
| { |
| Q_D(QScriptDebuggerLocalsModel); |
| d->frameIndex = frameIndex; |
| QScriptDebuggerJob *job = new InitModelJob(this, frameIndex, d->commandScheduler); |
| d->jobScheduler->scheduleJob(job); |
| } |
| |
| namespace { |
| |
| class SyncModelJob : public QScriptDebuggerCommandSchedulerJob |
| { |
| public: |
| SyncModelJob(QScriptDebuggerLocalsModel *model, |
| int frameIndex, |
| QScriptDebuggerCommandSchedulerInterface *scheduler) |
| : QScriptDebuggerCommandSchedulerJob(scheduler), |
| m_model(model), m_frameIndex(frameIndex), m_state(0) |
| { } |
| |
| void start() |
| { |
| if (!m_model) { |
| // Model has been deleted. |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| frontend.scheduleGetScopeChain(m_frameIndex); |
| } |
| |
| void handleResponse(const QScriptDebuggerResponse &response, |
| int) |
| { |
| if (!m_model) { |
| // Model has been deleted. |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| switch (m_state) { |
| case 0: { |
| QScriptDebuggerValueList scopeChain = response.resultAsScriptValueList(); |
| m_topLevelObjects << scopeChain; |
| frontend.scheduleGetThisObject(m_frameIndex); |
| ++m_state; |
| } break; |
| case 1: { |
| QScriptDebuggerLocalsModelPrivate *model_d = QScriptDebuggerLocalsModelPrivate::get(m_model); |
| QScriptDebuggerValue thisObject = response.resultAsScriptValue(); |
| m_topLevelObjects.append(thisObject); |
| bool equal = (m_topLevelObjects.size() == model_d->invisibleRootNode->children.size()); |
| for (int i = 0; equal && (i < m_topLevelObjects.size()); ++i) { |
| const QScriptDebuggerValue &object = m_topLevelObjects.at(i); |
| equal = (object == model_d->invisibleRootNode->children.at(i)->property.value()); |
| } |
| if (!equal) { |
| // the scope chain and/or this-object changed, so invalidate the model. |
| // we could try to be more clever, i.e. figure out |
| // exactly which objects were popped/pushed |
| model_d->removeTopLevelNodes(); |
| for (int j = 0; j < m_topLevelObjects.size(); ++j) { |
| const QScriptDebuggerValue &object = m_topLevelObjects.at(j); |
| QString name; |
| if (j == m_topLevelObjects.size()-1) { |
| name = QString::fromLatin1("this"); |
| } else { |
| name = QString::fromLatin1("Scope"); |
| if (j > 0) |
| name.append(QString::fromLatin1(" (%0)").arg(j)); |
| } |
| QModelIndex index = model_d->addTopLevelObject(name, object); |
| if (j == 0) |
| model_d->emitScopeObjectAvailable(index); |
| } |
| } else { |
| model_d->syncTopLevelNodes(); |
| } |
| finish(); |
| } break; |
| } |
| } |
| |
| private: |
| QPointer<QScriptDebuggerLocalsModel> m_model; |
| int m_frameIndex; |
| int m_state; |
| QScriptDebuggerValueList m_topLevelObjects; |
| }; |
| |
| } // namespace |
| |
| void QScriptDebuggerLocalsModel::sync(int frameIndex) |
| { |
| Q_D(QScriptDebuggerLocalsModel); |
| d->frameIndex = frameIndex; |
| QScriptDebuggerJob *job = new SyncModelJob(this, frameIndex, d->commandScheduler); |
| d->jobScheduler->scheduleJob(job); |
| } |
| |
| namespace { |
| |
| class SyncModelIndexJob : public QScriptDebuggerCommandSchedulerJob |
| { |
| public: |
| SyncModelIndexJob(const QPersistentModelIndex &index, |
| QScriptDebuggerCommandSchedulerInterface *scheduler) |
| : QScriptDebuggerCommandSchedulerJob(scheduler), |
| m_index(index) |
| { } |
| |
| QScriptDebuggerLocalsModelPrivate *model() const |
| { |
| if (!m_index.isValid()) |
| return 0; |
| QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model()); |
| QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m); |
| return QScriptDebuggerLocalsModelPrivate::get(lm); |
| } |
| |
| void start() |
| { |
| if (!m_index.isValid()) { |
| // nothing to do, the node has been removed |
| finish(); |
| return; |
| } |
| QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this); |
| QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index); |
| frontend.scheduleScriptObjectSnapshotCapture(node->snapshotId, node->property.value()); |
| } |
| |
| void handleResponse(const QScriptDebuggerResponse &response, |
| int) |
| { |
| QScriptDebuggerObjectSnapshotDelta delta; |
| delta = qvariant_cast<QScriptDebuggerObjectSnapshotDelta>(response.result()); |
| model()->reallySyncIndex(m_index, delta); |
| finish(); |
| } |
| |
| private: |
| QPersistentModelIndex m_index; |
| }; |
| |
| } // namespace |
| |
| void QScriptDebuggerLocalsModelPrivate::syncIndex(const QModelIndex &index) |
| { |
| if (!index.isValid()) |
| return; |
| QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index); |
| if (node->populationState != QScriptDebuggerLocalsModelNode::Populated) |
| return; |
| QScriptDebuggerJob *job = new SyncModelIndexJob(index, commandScheduler); |
| jobScheduler->scheduleJob(job); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::reallySyncIndex(const QModelIndex &index, |
| const QScriptDebuggerObjectSnapshotDelta &delta) |
| { |
| if (!index.isValid()) |
| return; |
| QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index); |
| // update or remove existing children |
| for (int i = 0; i < node->children.count(); ++i) { |
| QScriptDebuggerLocalsModelNode *child = node->children.at(i); |
| int j; |
| for (j = 0; j < delta.changedProperties.count(); ++j) { |
| if (child->property.name() == delta.changedProperties.at(j).name()) { |
| child->property = delta.changedProperties.at(j); |
| child->changed = true; |
| emitDataChanged(index, index.sibling(0, 1)); |
| repopulate(child); |
| break; |
| } |
| } |
| if (j != delta.changedProperties.count()) |
| continue; // was changed |
| for (j = 0; j < delta.removedProperties.count(); ++j) { |
| if (child->property.name() == delta.removedProperties.at(j)) { |
| removeChild(index, node, i); |
| --i; |
| break; |
| } |
| } |
| if (j != delta.removedProperties.count()) |
| continue; // was removed |
| // neither changed nor removed, but its children might be |
| if (child->populationState == QScriptDebuggerLocalsModelNode::Populated) { |
| QScriptDebuggerJob *job = new SyncModelIndexJob(indexFromNode(child), commandScheduler); |
| jobScheduler->scheduleJob(job); |
| } |
| } |
| addChildren(index, node, delta.addedProperties); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::syncTopLevelNodes() |
| { |
| Q_Q(QScriptDebuggerLocalsModel); |
| for (int i = 0; i < invisibleRootNode->children.count(); ++i) { |
| QModelIndex index = q->index(i, 0, QModelIndex()); |
| syncIndex(index); |
| if (i == 0) |
| emit q->scopeObjectAvailable(index); |
| } |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::removeTopLevelNodes() |
| { |
| while (!invisibleRootNode->children.isEmpty()) |
| removeChild(QModelIndex(), invisibleRootNode, 0); |
| } |
| |
| void QScriptDebuggerLocalsModelPrivate::emitScopeObjectAvailable(const QModelIndex &index) |
| { |
| emit q_func()->scopeObjectAvailable(index); |
| } |
| |
| int QScriptDebuggerLocalsModel::frameIndex() const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| return d->frameIndex; |
| } |
| |
| /*! |
| \reimp |
| */ |
| QModelIndex QScriptDebuggerLocalsModel::index(int row, int column, const QModelIndex &parent) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent); |
| if ((row < 0) || (row >= node->children.count())) |
| return QModelIndex(); |
| return createIndex(row, column, node->children.at(row)); |
| } |
| |
| /*! |
| \reimp |
| */ |
| QModelIndex QScriptDebuggerLocalsModel::parent(const QModelIndex &index) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| if (!index.isValid()) |
| return QModelIndex(); |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index); |
| return d->indexFromNode(node->parent); |
| } |
| |
| /*! |
| \reimp |
| */ |
| int QScriptDebuggerLocalsModel::columnCount(const QModelIndex &) const |
| { |
| return 2; |
| } |
| |
| /*! |
| \reimp |
| */ |
| int QScriptDebuggerLocalsModel::rowCount(const QModelIndex &parent) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| // ### need this to make it work with a sortfilterproxymodel (QSFPM is too eager) |
| const_cast<QScriptDebuggerLocalsModel*>(this)->fetchMore(parent); |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent); |
| return node ? node->children.count() : 0; |
| } |
| |
| /*! |
| \reimp |
| */ |
| QVariant QScriptDebuggerLocalsModel::data(const QModelIndex &index, int role) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| if (!index.isValid()) |
| return QVariant(); |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index); |
| if (role == Qt::DisplayRole) { |
| if (index.column() == 0) |
| return node->property.name(); |
| else if (index.column() == 1) { |
| QString str = node->property.valueAsString(); |
| if (str.indexOf(QLatin1Char('\n')) != -1) { |
| QStringList lines = str.split(QLatin1Char('\n')); |
| int lineCount = lines.size(); |
| if (lineCount > 1) { |
| lines = lines.mid(0, 1); |
| lines.append(QString::fromLatin1("(... %0 more lines ...)").arg(lineCount - 1)); |
| } |
| str = lines.join(QLatin1String("\n")); |
| } |
| return str; |
| } |
| } else if (role == Qt::EditRole) { |
| if ((index.column() == 1) && !d->isTopLevelNode(node)) { |
| QString str = node->property.valueAsString(); |
| if (node->property.value().type() == QScriptDebuggerValue::StringValue) { |
| // escape |
| str.replace(QLatin1Char('\"'), QLatin1String("\\\"")); |
| str.prepend(QLatin1Char('\"')); |
| str.append(QLatin1Char('\"')); |
| } |
| return str; |
| } |
| } else if (role == Qt::ToolTipRole) { |
| if (index.column() == 1) { |
| QString str = node->property.valueAsString(); |
| if (str.indexOf(QLatin1Char('\n')) != -1) |
| return str; |
| } |
| } |
| // ### do this in the delegate |
| else if (role == Qt::BackgroundRole) { |
| if (d->isTopLevelNode(node)) |
| return QBrush(Qt::darkGray); |
| } else if (role == Qt::ForegroundRole) { |
| if (d->isTopLevelNode(node)) |
| return QColor(Qt::white); |
| } else if (role == Qt::FontRole) { |
| if (d->isTopLevelNode(node) || node->changed) { |
| QFont fnt; |
| fnt.setBold(true); |
| return fnt; |
| } |
| } |
| return QVariant(); |
| } |
| |
| /*! |
| \reimp |
| */ |
| bool QScriptDebuggerLocalsModel::setData(const QModelIndex &index, const QVariant &value, int role) |
| { |
| Q_D(QScriptDebuggerLocalsModel); |
| if (!index.isValid()) |
| return false; |
| if (role != Qt::EditRole) |
| return false; |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index); |
| if (!node) |
| return false; |
| QString expr = value.toString().trimmed(); |
| if (expr.isEmpty()) |
| return false; |
| QScriptDebuggerJob *job = new SetPropertyJob(index, expr, d->commandScheduler); |
| d->jobScheduler->scheduleJob(job); |
| return true; |
| } |
| |
| /*! |
| \reimp |
| */ |
| QVariant QScriptDebuggerLocalsModel::headerData(int section, Qt::Orientation orient, int role) const |
| { |
| if (orient == Qt::Horizontal) { |
| if (role == Qt::DisplayRole) { |
| if (section == 0) |
| return QCoreApplication::translate("QScriptDebuggerLocalsModel", "Name"); |
| else if (section == 1) |
| return QCoreApplication::translate("QScriptDebuggerLocalsModel", "Value"); |
| } |
| } |
| return QVariant(); |
| } |
| |
| /*! |
| \reimp |
| */ |
| Qt::ItemFlags QScriptDebuggerLocalsModel::flags(const QModelIndex &index) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| if (!index.isValid()) |
| return 0; |
| Qt::ItemFlags ret = QAbstractItemModel::flags(index); |
| if ((index.column() == 1) && index.parent().isValid()) { |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index); |
| if (!(node->property.flags() & QScriptValue::ReadOnly)) |
| ret |= Qt::ItemIsEditable; |
| } |
| return ret; |
| } |
| |
| /*! |
| \reimp |
| */ |
| bool QScriptDebuggerLocalsModel::hasChildren(const QModelIndex &parent) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent); |
| if (!node) |
| return false; |
| return !node->children.isEmpty() |
| || ((node->property.value().type() == QScriptDebuggerValue::ObjectValue) |
| && (node->populationState == QScriptDebuggerLocalsModelNode::NotPopulated)); |
| } |
| |
| /*! |
| \reimp |
| */ |
| bool QScriptDebuggerLocalsModel::canFetchMore(const QModelIndex &parent) const |
| { |
| Q_D(const QScriptDebuggerLocalsModel); |
| if (!parent.isValid()) |
| return false; |
| QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent); |
| return node |
| && (node->property.value().type() == QScriptDebuggerValue::ObjectValue) |
| && (node->populationState == QScriptDebuggerLocalsModelNode::NotPopulated); |
| } |
| |
| /*! |
| \reimp |
| */ |
| void QScriptDebuggerLocalsModel::fetchMore(const QModelIndex &parent) |
| { |
| Q_D(QScriptDebuggerLocalsModel); |
| d->populateIndex(parent); |
| } |
| |
| QT_END_NAMESPACE |