util/src/scripttools/debugging/qscriptdebuggerlocalsmodel.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtSCriptTools module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qscriptdebuggerlocalsmodel_p.h"
       
    43 #include "qscriptdebuggercommandschedulerjob_p.h"
       
    44 #include "qscriptdebuggervalue_p.h"
       
    45 #include "qscriptdebuggerresponse_p.h"
       
    46 #include "qscriptdebuggerevent_p.h"
       
    47 #include "qscriptdebuggervalueproperty_p.h"
       
    48 #include "qscriptdebuggercommandschedulerinterface_p.h"
       
    49 #include "qscriptdebuggercommandschedulerfrontend_p.h"
       
    50 #include "qscriptdebuggerjobschedulerinterface_p.h"
       
    51 #include "qscriptdebuggerobjectsnapshotdelta_p.h"
       
    52 
       
    53 #include "private/qabstractitemmodel_p.h"
       
    54 
       
    55 #include <QtCore/qdebug.h>
       
    56 #include <QtCore/qcoreapplication.h>
       
    57 #include <QtGui/qbrush.h>
       
    58 #include <QtGui/qfont.h>
       
    59 
       
    60 Q_DECLARE_METATYPE(QScriptDebuggerObjectSnapshotDelta)
       
    61 
       
    62 QT_BEGIN_NAMESPACE
       
    63 
       
    64 struct QScriptDebuggerLocalsModelNode
       
    65 {
       
    66     enum PopulationState {
       
    67         NotPopulated,
       
    68         Populating,
       
    69         Populated
       
    70     };
       
    71 
       
    72     QScriptDebuggerLocalsModelNode()
       
    73         : parent(0), populationState(NotPopulated), snapshotId(-1), changed(false) {}
       
    74 
       
    75     QScriptDebuggerLocalsModelNode(
       
    76         const QScriptDebuggerValueProperty &prop,
       
    77         QScriptDebuggerLocalsModelNode *par)
       
    78         : property(prop), parent(par),
       
    79           populationState(NotPopulated), snapshotId(-1), changed(false)
       
    80     {
       
    81         parent->children.append(this);
       
    82     }
       
    83 
       
    84     ~QScriptDebuggerLocalsModelNode() { qDeleteAll(children); }
       
    85 
       
    86     QScriptDebuggerLocalsModelNode *findChild(const QString &name)
       
    87     {
       
    88         for (int i = 0; i < children.size(); ++i) {
       
    89             QScriptDebuggerLocalsModelNode *child = children.at(i);
       
    90             if (child->property.name() == name)
       
    91                 return child;
       
    92         }
       
    93         return 0;
       
    94     }
       
    95 
       
    96     QScriptDebuggerValueProperty property;
       
    97     QScriptDebuggerLocalsModelNode *parent;
       
    98     QList<QScriptDebuggerLocalsModelNode*> children;
       
    99     PopulationState populationState;
       
   100     int snapshotId;
       
   101     bool changed;
       
   102 };
       
   103 
       
   104 class QScriptDebuggerLocalsModelPrivate
       
   105     : public QAbstractItemModelPrivate
       
   106 {
       
   107     Q_DECLARE_PUBLIC(QScriptDebuggerLocalsModel)
       
   108 public:
       
   109     QScriptDebuggerLocalsModelPrivate();
       
   110     ~QScriptDebuggerLocalsModelPrivate();
       
   111 
       
   112     static QScriptDebuggerLocalsModelPrivate *get(QScriptDebuggerLocalsModel *q);
       
   113 
       
   114     QModelIndex addTopLevelObject(const QString &name, const QScriptDebuggerValue &object);
       
   115 
       
   116     QScriptDebuggerLocalsModelNode *nodeFromIndex(const QModelIndex &index) const;
       
   117     QModelIndex indexFromNode(QScriptDebuggerLocalsModelNode *node) const;
       
   118     bool isTopLevelNode(QScriptDebuggerLocalsModelNode *node) const;
       
   119 
       
   120     void populateIndex(const QModelIndex &index);
       
   121     void reallyPopulateIndex(const QModelIndex &index,
       
   122                              const QScriptDebuggerValuePropertyList &props);
       
   123     void syncIndex(const QModelIndex &index);
       
   124     void reallySyncIndex(const QModelIndex &index,
       
   125                          const QScriptDebuggerObjectSnapshotDelta &delta);
       
   126     void syncTopLevelNodes();
       
   127     void removeTopLevelNodes();
       
   128     void emitScopeObjectAvailable(const QModelIndex &index);
       
   129 
       
   130     void emitDataChanged(const QModelIndex &tl, const QModelIndex &br);
       
   131     void removeChild(const QModelIndex &parentIndex,
       
   132                      QScriptDebuggerLocalsModelNode *parentNode, int row);
       
   133     void depopulate(QScriptDebuggerLocalsModelNode *node);
       
   134     void repopulate(QScriptDebuggerLocalsModelNode *node);
       
   135     void addChildren(const QModelIndex &parentIndex,
       
   136                      QScriptDebuggerLocalsModelNode *parentNode,
       
   137                      const QScriptDebuggerValuePropertyList &props);
       
   138 
       
   139     void deleteObjectSnapshots(const QList<qint64> &snapshotIds);
       
   140     void deleteAllObjectSnapshots();
       
   141 
       
   142     QScriptDebuggerJobSchedulerInterface *jobScheduler;
       
   143     QScriptDebuggerCommandSchedulerInterface *commandScheduler;
       
   144     QScriptDebuggerLocalsModelNode *invisibleRootNode;
       
   145     int frameIndex;
       
   146 };
       
   147 
       
   148 QScriptDebuggerLocalsModelPrivate::QScriptDebuggerLocalsModelPrivate()
       
   149 {
       
   150     invisibleRootNode = new QScriptDebuggerLocalsModelNode();
       
   151     frameIndex = -1;
       
   152 }
       
   153 
       
   154 QScriptDebuggerLocalsModelPrivate::~QScriptDebuggerLocalsModelPrivate()
       
   155 {
       
   156     delete invisibleRootNode;
       
   157 }
       
   158 
       
   159 void QScriptDebuggerLocalsModelPrivate::emitDataChanged(const QModelIndex &tl, const QModelIndex &br)
       
   160 {
       
   161     q_func()->dataChanged(tl, br);
       
   162 }
       
   163 
       
   164 static QList<qint64> findSnapshotIdsRecursively(QScriptDebuggerLocalsModelNode *root)
       
   165 {
       
   166     QList<qint64> result;
       
   167     if (root->snapshotId == -1) {
       
   168         Q_ASSERT(root->children.isEmpty());
       
   169         return result;
       
   170     }
       
   171     QList<QScriptDebuggerLocalsModelNode*> nodeStack;
       
   172     nodeStack.append(root);
       
   173     while (!nodeStack.isEmpty()) {
       
   174         QScriptDebuggerLocalsModelNode *node = nodeStack.takeFirst();
       
   175         result.append(node->snapshotId);
       
   176         for (int i = 0; i < node->children.count(); ++i) {
       
   177             QScriptDebuggerLocalsModelNode *child = node->children.at(i);
       
   178             if (child->snapshotId != -1)
       
   179                 nodeStack.prepend(child);
       
   180         }
       
   181     }
       
   182     return result;
       
   183 }
       
   184 
       
   185 void QScriptDebuggerLocalsModelPrivate::removeChild(const QModelIndex &parentIndex,
       
   186                                                     QScriptDebuggerLocalsModelNode *parentNode,
       
   187                                                     int row)
       
   188 {
       
   189     Q_Q(QScriptDebuggerLocalsModel);
       
   190     q->beginRemoveRows(parentIndex, row, row);
       
   191     QScriptDebuggerLocalsModelNode *child = parentNode->children.takeAt(row);
       
   192     QList<qint64> snapshotIds = findSnapshotIdsRecursively(child);
       
   193     delete child;
       
   194     q->endRemoveRows();
       
   195     deleteObjectSnapshots(snapshotIds);
       
   196 }
       
   197 
       
   198 void QScriptDebuggerLocalsModelPrivate::depopulate(QScriptDebuggerLocalsModelNode *node)
       
   199 {
       
   200     Q_Q(QScriptDebuggerLocalsModel);
       
   201     bool hasChildren = !node->children.isEmpty();
       
   202     if (hasChildren)
       
   203         q->beginRemoveRows(indexFromNode(node), 0, node->children.count() - 1);
       
   204     QList<qint64> snapshotIds = findSnapshotIdsRecursively(node);
       
   205     qDeleteAll(node->children);
       
   206     node->children.clear();
       
   207     node->snapshotId = -1;
       
   208     node->populationState = QScriptDebuggerLocalsModelNode::NotPopulated;
       
   209     if (hasChildren)
       
   210         q->endRemoveRows();
       
   211     deleteObjectSnapshots(snapshotIds);
       
   212 }
       
   213 
       
   214 void QScriptDebuggerLocalsModelPrivate::repopulate(QScriptDebuggerLocalsModelNode *node)
       
   215 {
       
   216     if (node->populationState != QScriptDebuggerLocalsModelNode::Populated)
       
   217         return;
       
   218     depopulate(node);
       
   219     if (node->property.value().type() == QScriptDebuggerValue::ObjectValue)
       
   220         populateIndex(indexFromNode(node));
       
   221 }
       
   222 
       
   223 void QScriptDebuggerLocalsModelPrivate::addChildren(const QModelIndex &parentIndex,
       
   224                                                     QScriptDebuggerLocalsModelNode *parentNode,
       
   225                                                     const QScriptDebuggerValuePropertyList &props)
       
   226 {
       
   227     Q_Q(QScriptDebuggerLocalsModel);
       
   228     if (props.isEmpty())
       
   229         return;
       
   230     int first = parentNode->children.size();
       
   231     int last = first + props.size() - 1;
       
   232     q->beginInsertRows(parentIndex, first, last);
       
   233     for (int i = 0; i < props.size(); ++i)
       
   234         new QScriptDebuggerLocalsModelNode(props.at(i), parentNode);
       
   235     q->endInsertRows();
       
   236 }
       
   237 
       
   238 void QScriptDebuggerLocalsModelPrivate::deleteObjectSnapshots(const QList<qint64> &snapshotIds)
       
   239 {
       
   240     QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler, 0);
       
   241     for (int i = 0; i < snapshotIds.size(); ++i)
       
   242         frontend.scheduleDeleteScriptObjectSnapshot(snapshotIds.at(i));
       
   243 }
       
   244 
       
   245 void QScriptDebuggerLocalsModelPrivate::deleteAllObjectSnapshots()
       
   246 {
       
   247     QList<qint64> snapshotIds;
       
   248     for (int i = 0; i < invisibleRootNode->children.count(); ++i)
       
   249         snapshotIds += findSnapshotIdsRecursively(invisibleRootNode->children.at(i));
       
   250     deleteObjectSnapshots(snapshotIds);
       
   251 }
       
   252 
       
   253 QScriptDebuggerLocalsModelPrivate *QScriptDebuggerLocalsModelPrivate::get(QScriptDebuggerLocalsModel *q)
       
   254 {
       
   255     return q->d_func();
       
   256 }
       
   257 
       
   258 namespace {
       
   259 
       
   260 class SetPropertyJob : public QScriptDebuggerCommandSchedulerJob
       
   261 {
       
   262 public:
       
   263     SetPropertyJob(const QPersistentModelIndex &index,
       
   264                    const QString &expression,
       
   265                    QScriptDebuggerCommandSchedulerInterface *scheduler)
       
   266         : QScriptDebuggerCommandSchedulerJob(scheduler),
       
   267           m_index(index), m_expression(expression), m_state(0) {}
       
   268 
       
   269     QScriptDebuggerLocalsModelPrivate *model() const
       
   270     {
       
   271         if (!m_index.isValid())
       
   272             return 0;
       
   273         QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model());
       
   274         QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m);
       
   275         return QScriptDebuggerLocalsModelPrivate::get(lm);
       
   276     }
       
   277 
       
   278     void start()
       
   279     {
       
   280         if (!m_index.isValid()) {
       
   281             // nothing to do, the node has been removed
       
   282             return;
       
   283         }
       
   284         QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index);
       
   285         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   286         frontend.scheduleEvaluate(model()->frameIndex, m_expression,
       
   287                                   QString::fromLatin1("set property '%0' (%1)")
       
   288                                   .arg(node->property.name())
       
   289                                   .arg(QDateTime::currentDateTime().toString()));
       
   290     }
       
   291 
       
   292     void handleResponse(const QScriptDebuggerResponse &, int)
       
   293     {
       
   294         switch (m_state) {
       
   295         case 0:
       
   296             hibernateUntilEvaluateFinished();
       
   297             ++m_state;
       
   298             break;
       
   299         case 1:
       
   300             finish();
       
   301             break;
       
   302         }
       
   303     }
       
   304 
       
   305     void evaluateFinished(const QScriptDebuggerValue &result)
       
   306     {
       
   307         if (!m_index.isValid()) {
       
   308             // nothing to do, the node has been removed
       
   309             return;
       
   310         }
       
   311         QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index);
       
   312         Q_ASSERT(node->parent != 0);
       
   313         QScriptDebuggerValue object = node->parent->property.value();
       
   314         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   315         frontend.scheduleSetScriptValueProperty(object, node->property.name(), result);
       
   316     }
       
   317     
       
   318 private:
       
   319     QPersistentModelIndex m_index;
       
   320     QString m_expression;
       
   321     int m_state;
       
   322 };
       
   323 
       
   324 } // namespace
       
   325 
       
   326 QScriptDebuggerLocalsModelNode *QScriptDebuggerLocalsModelPrivate::nodeFromIndex(
       
   327     const QModelIndex &index) const
       
   328 {
       
   329     if (!index.isValid())
       
   330         return invisibleRootNode;
       
   331     return static_cast<QScriptDebuggerLocalsModelNode*>(index.internalPointer());
       
   332 }
       
   333 
       
   334 QModelIndex QScriptDebuggerLocalsModelPrivate::indexFromNode(
       
   335     QScriptDebuggerLocalsModelNode *node) const
       
   336 {
       
   337     if (!node || (node == invisibleRootNode))
       
   338         return QModelIndex();
       
   339     QScriptDebuggerLocalsModelNode *par = node->parent;
       
   340     int row = par ? par->children.indexOf(node) : 0;
       
   341     return createIndex(row, 0, node);
       
   342 }
       
   343 
       
   344 bool QScriptDebuggerLocalsModelPrivate::isTopLevelNode(QScriptDebuggerLocalsModelNode *node) const
       
   345 {
       
   346     return node && (node->parent == invisibleRootNode);
       
   347 }
       
   348 
       
   349 namespace {
       
   350 
       
   351 class PopulateModelIndexJob : public QScriptDebuggerCommandSchedulerJob
       
   352 {
       
   353 public:
       
   354     PopulateModelIndexJob(const QPersistentModelIndex &index,
       
   355                           QScriptDebuggerCommandSchedulerInterface *scheduler)
       
   356         : QScriptDebuggerCommandSchedulerJob(scheduler),
       
   357           m_index(index), m_state(0)
       
   358     { }
       
   359 
       
   360     QScriptDebuggerLocalsModelPrivate *model() const
       
   361     {
       
   362         if (!m_index.isValid())
       
   363             return 0;
       
   364         QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model());
       
   365         QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m);
       
   366         return QScriptDebuggerLocalsModelPrivate::get(lm);
       
   367     }
       
   368 
       
   369     void start()
       
   370     {
       
   371         if (!m_index.isValid()) {
       
   372             // nothing to do, the node has been removed
       
   373             return;
       
   374         }
       
   375         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   376         frontend.scheduleNewScriptObjectSnapshot();
       
   377     }
       
   378 
       
   379     void handleResponse(const QScriptDebuggerResponse &response,
       
   380                         int)
       
   381     {
       
   382         if (!m_index.isValid()) {
       
   383             // the node has been removed
       
   384             finish();
       
   385             return;
       
   386         }
       
   387         switch (m_state) {
       
   388         case 0: {
       
   389             QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index);
       
   390             Q_ASSERT(node->populationState == QScriptDebuggerLocalsModelNode::Populating);
       
   391             node->snapshotId = response.resultAsInt();
       
   392             QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   393             frontend.scheduleScriptObjectSnapshotCapture(node->snapshotId, node->property.value());
       
   394             ++m_state;
       
   395         }   break;
       
   396         case 1: {
       
   397             QScriptDebuggerObjectSnapshotDelta delta;
       
   398             delta = qvariant_cast<QScriptDebuggerObjectSnapshotDelta>(response.result());
       
   399             Q_ASSERT(delta.removedProperties.isEmpty());
       
   400             Q_ASSERT(delta.changedProperties.isEmpty());
       
   401             QScriptDebuggerValuePropertyList props = delta.addedProperties;
       
   402             model()->reallyPopulateIndex(m_index, props);
       
   403             finish();
       
   404           } break;
       
   405         }
       
   406     }
       
   407 
       
   408 private:
       
   409     QPersistentModelIndex m_index;
       
   410     int m_state;
       
   411 };
       
   412 
       
   413 } // namespace
       
   414 
       
   415 void QScriptDebuggerLocalsModelPrivate::populateIndex(
       
   416     const QModelIndex &index)
       
   417 {
       
   418     if (!index.isValid())
       
   419         return;
       
   420     QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index);
       
   421     if (node->populationState != QScriptDebuggerLocalsModelNode::NotPopulated)
       
   422         return;
       
   423     if (node->property.value().type() != QScriptDebuggerValue::ObjectValue)
       
   424         return;
       
   425     node->populationState = QScriptDebuggerLocalsModelNode::Populating;
       
   426     QScriptDebuggerJob *job = new PopulateModelIndexJob(index, commandScheduler);
       
   427     jobScheduler->scheduleJob(job);
       
   428 }
       
   429 
       
   430 void QScriptDebuggerLocalsModelPrivate::reallyPopulateIndex(
       
   431     const QModelIndex &index,
       
   432     const QScriptDebuggerValuePropertyList &props)
       
   433 {
       
   434     if (!index.isValid())
       
   435         return;
       
   436     QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index);
       
   437     Q_ASSERT(node->populationState == QScriptDebuggerLocalsModelNode::Populating);
       
   438     node->populationState = QScriptDebuggerLocalsModelNode::Populated;
       
   439     addChildren(index, node, props);
       
   440 }
       
   441 
       
   442 QScriptDebuggerLocalsModel::QScriptDebuggerLocalsModel(
       
   443     QScriptDebuggerJobSchedulerInterface *jobScheduler,
       
   444     QScriptDebuggerCommandSchedulerInterface *commandScheduler,
       
   445     QObject *parent)
       
   446     : QAbstractItemModel(*new QScriptDebuggerLocalsModelPrivate, parent)
       
   447 {
       
   448     Q_D(QScriptDebuggerLocalsModel);
       
   449     d->jobScheduler = jobScheduler;
       
   450     d->commandScheduler = commandScheduler;
       
   451 }
       
   452 
       
   453 QScriptDebuggerLocalsModel::~QScriptDebuggerLocalsModel()
       
   454 {
       
   455 }
       
   456 
       
   457 QModelIndex QScriptDebuggerLocalsModelPrivate::addTopLevelObject(const QString &name, const QScriptDebuggerValue &object)
       
   458 {
       
   459     Q_Q(QScriptDebuggerLocalsModel);
       
   460     QScriptDebuggerLocalsModelNode *node = invisibleRootNode->findChild(name);
       
   461     if (node)
       
   462         return indexFromNode(node);
       
   463     QScriptDebuggerValueProperty prop(name, object,
       
   464                                       QString::fromLatin1(""), // ### string representation of object
       
   465                                       /*flags=*/0);
       
   466     int rowIndex = invisibleRootNode->children.size();
       
   467     q->beginInsertRows(QModelIndex(), rowIndex, rowIndex);
       
   468     node = new QScriptDebuggerLocalsModelNode(prop, invisibleRootNode);
       
   469     q->endInsertRows();
       
   470     return indexFromNode(node);
       
   471 }
       
   472 
       
   473 namespace {
       
   474 
       
   475 class InitModelJob : public QScriptDebuggerCommandSchedulerJob
       
   476 {
       
   477 public:
       
   478     InitModelJob(QScriptDebuggerLocalsModelPrivate *model,
       
   479                  int frameIndex,
       
   480                  QScriptDebuggerCommandSchedulerInterface *scheduler)
       
   481         : QScriptDebuggerCommandSchedulerJob(scheduler),
       
   482           m_model(model), m_frameIndex(frameIndex), m_state(0)
       
   483     { }
       
   484 
       
   485     void start()
       
   486     {
       
   487         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   488         frontend.scheduleGetScopeChain(m_frameIndex);
       
   489     }
       
   490 
       
   491     void handleResponse(const QScriptDebuggerResponse &response,
       
   492                         int)
       
   493     {
       
   494         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   495         switch (m_state) {
       
   496         case 0: {
       
   497             QScriptDebuggerValueList scopeChain = response.resultAsScriptValueList();
       
   498             for (int i = 0; i < scopeChain.size(); ++i) {
       
   499                 const QScriptDebuggerValue &scopeObject = scopeChain.at(i);
       
   500                 QString name = QString::fromLatin1("Scope");
       
   501                 if (i > 0)
       
   502                     name.append(QString::fromLatin1(" (%0)").arg(i));
       
   503                 QModelIndex index = m_model->addTopLevelObject(name, scopeObject);
       
   504                 if (i == 0)
       
   505                     m_model->emitScopeObjectAvailable(index);
       
   506             }
       
   507             frontend.scheduleGetThisObject(m_frameIndex);
       
   508             ++m_state;
       
   509         }   break;
       
   510         case 1: {
       
   511             QScriptDebuggerValue thisObject = response.resultAsScriptValue();
       
   512             m_model->addTopLevelObject(QLatin1String("this"), thisObject);
       
   513             finish();
       
   514           } break;
       
   515         }
       
   516     }
       
   517 
       
   518 private:
       
   519     QScriptDebuggerLocalsModelPrivate *m_model;
       
   520     int m_frameIndex;
       
   521     int m_state;
       
   522 };
       
   523 
       
   524 } // namespace
       
   525 
       
   526 void QScriptDebuggerLocalsModel::init(int frameIndex)
       
   527 {
       
   528     Q_D(QScriptDebuggerLocalsModel);
       
   529     d->frameIndex = frameIndex;
       
   530     QScriptDebuggerJob *job = new InitModelJob(d, frameIndex, d->commandScheduler);
       
   531     d->jobScheduler->scheduleJob(job);
       
   532 }
       
   533 
       
   534 namespace {
       
   535 
       
   536 class SyncModelJob : public QScriptDebuggerCommandSchedulerJob
       
   537 {
       
   538 public:
       
   539     SyncModelJob(QScriptDebuggerLocalsModelPrivate *model,
       
   540                  int frameIndex,
       
   541                  QScriptDebuggerCommandSchedulerInterface *scheduler)
       
   542         : QScriptDebuggerCommandSchedulerJob(scheduler),
       
   543           m_model(model), m_frameIndex(frameIndex), m_state(0)
       
   544     { }
       
   545 
       
   546     void start()
       
   547     {
       
   548         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   549         frontend.scheduleGetScopeChain(m_frameIndex);
       
   550     }
       
   551 
       
   552     void handleResponse(const QScriptDebuggerResponse &response,
       
   553                         int)
       
   554     {
       
   555         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   556         switch (m_state) {
       
   557         case 0: {
       
   558             QScriptDebuggerValueList scopeChain = response.resultAsScriptValueList();
       
   559             m_topLevelObjects << scopeChain;
       
   560             frontend.scheduleGetThisObject(m_frameIndex);
       
   561             ++m_state;
       
   562         }   break;
       
   563         case 1: {
       
   564             QScriptDebuggerValue thisObject = response.resultAsScriptValue();
       
   565             m_topLevelObjects.append(thisObject);
       
   566             bool equal = (m_topLevelObjects.size() == m_model->invisibleRootNode->children.size());
       
   567             for (int i = 0; equal && (i < m_topLevelObjects.size()); ++i) {
       
   568                 const QScriptDebuggerValue &object = m_topLevelObjects.at(i);
       
   569                 equal = (object == m_model->invisibleRootNode->children.at(i)->property.value());
       
   570             }
       
   571             if (!equal) {
       
   572                 // the scope chain and/or this-object changed, so invalidate the model.
       
   573                 // we could try to be more clever, i.e. figure out
       
   574                 // exactly which objects were popped/pushed
       
   575                 m_model->removeTopLevelNodes();
       
   576                 for (int j = 0; j < m_topLevelObjects.size(); ++j) {
       
   577                     const QScriptDebuggerValue &object = m_topLevelObjects.at(j);
       
   578                     QString name;
       
   579                     if (j == m_topLevelObjects.size()-1) {
       
   580                         name = QString::fromLatin1("this");
       
   581                     } else {
       
   582                         name = QString::fromLatin1("Scope");
       
   583                         if (j > 0)
       
   584                             name.append(QString::fromLatin1(" (%0)").arg(j));
       
   585                     }
       
   586                     QModelIndex index = m_model->addTopLevelObject(name, object);
       
   587                     if (j == 0)
       
   588                         m_model->emitScopeObjectAvailable(index);
       
   589                 }
       
   590             } else {
       
   591                 m_model->syncTopLevelNodes();
       
   592             }
       
   593             finish();
       
   594           } break;
       
   595         }
       
   596     }
       
   597 
       
   598 private:
       
   599     QScriptDebuggerLocalsModelPrivate *m_model;
       
   600     int m_frameIndex;
       
   601     int m_state;
       
   602     QScriptDebuggerValueList m_topLevelObjects;
       
   603 };
       
   604 
       
   605 } // namespace
       
   606 
       
   607 void QScriptDebuggerLocalsModel::sync(int frameIndex)
       
   608 {
       
   609     Q_D(QScriptDebuggerLocalsModel);
       
   610     d->frameIndex = frameIndex;
       
   611     QScriptDebuggerJob *job = new SyncModelJob(d, frameIndex, d->commandScheduler);
       
   612     d->jobScheduler->scheduleJob(job);
       
   613 }
       
   614 
       
   615 namespace {
       
   616 
       
   617 class SyncModelIndexJob : public QScriptDebuggerCommandSchedulerJob
       
   618 {
       
   619 public:
       
   620     SyncModelIndexJob(const QPersistentModelIndex &index,
       
   621                       QScriptDebuggerCommandSchedulerInterface *scheduler)
       
   622         : QScriptDebuggerCommandSchedulerJob(scheduler),
       
   623           m_index(index)
       
   624     { }
       
   625 
       
   626     QScriptDebuggerLocalsModelPrivate *model() const
       
   627     {
       
   628         if (!m_index.isValid())
       
   629             return 0;
       
   630         QAbstractItemModel *m = const_cast<QAbstractItemModel*>(m_index.model());
       
   631         QScriptDebuggerLocalsModel *lm = qobject_cast<QScriptDebuggerLocalsModel*>(m);
       
   632         return QScriptDebuggerLocalsModelPrivate::get(lm);
       
   633     }
       
   634 
       
   635     void start()
       
   636     {
       
   637         if (!m_index.isValid()) {
       
   638             // nothing to do, the node has been removed
       
   639             return;
       
   640         }
       
   641         QScriptDebuggerCommandSchedulerFrontend frontend(commandScheduler(), this);
       
   642         QScriptDebuggerLocalsModelNode *node = model()->nodeFromIndex(m_index);
       
   643         frontend.scheduleScriptObjectSnapshotCapture(node->snapshotId, node->property.value());
       
   644     }
       
   645 
       
   646     void handleResponse(const QScriptDebuggerResponse &response,
       
   647                         int)
       
   648     {
       
   649         QScriptDebuggerObjectSnapshotDelta delta;
       
   650         delta = qvariant_cast<QScriptDebuggerObjectSnapshotDelta>(response.result());
       
   651         model()->reallySyncIndex(m_index, delta);
       
   652         finish();
       
   653     }
       
   654 
       
   655 private:
       
   656     QPersistentModelIndex m_index;
       
   657 };
       
   658 
       
   659 } // namespace
       
   660 
       
   661 void QScriptDebuggerLocalsModelPrivate::syncIndex(const QModelIndex &index)
       
   662 {
       
   663     if (!index.isValid())
       
   664         return;
       
   665     QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index);
       
   666     if (node->populationState != QScriptDebuggerLocalsModelNode::Populated)
       
   667         return;
       
   668     QScriptDebuggerJob *job = new SyncModelIndexJob(index, commandScheduler);
       
   669     jobScheduler->scheduleJob(job);
       
   670 }
       
   671 
       
   672 void QScriptDebuggerLocalsModelPrivate::reallySyncIndex(const QModelIndex &index,
       
   673                                                         const QScriptDebuggerObjectSnapshotDelta &delta)
       
   674 {
       
   675     if (!index.isValid())
       
   676         return;
       
   677     QScriptDebuggerLocalsModelNode *node = nodeFromIndex(index);
       
   678     // update or remove existing children
       
   679     for (int i = 0; i < node->children.count(); ++i) {
       
   680         QScriptDebuggerLocalsModelNode *child = node->children.at(i);
       
   681         int j;
       
   682         for (j = 0; j < delta.changedProperties.count(); ++j) {
       
   683             if (child->property.name() == delta.changedProperties.at(j).name()) {
       
   684                 child->property = delta.changedProperties.at(j);
       
   685                 child->changed = true;
       
   686                 emitDataChanged(index, index.sibling(0, 1));
       
   687                 repopulate(child);
       
   688                 break;
       
   689             }
       
   690         }
       
   691         if (j != delta.changedProperties.count())
       
   692             continue; // was changed
       
   693         for (j = 0; j < delta.removedProperties.count(); ++j) {
       
   694             if (child->property.name() == delta.removedProperties.at(j)) {
       
   695                 removeChild(index, node, i);
       
   696                 --i;
       
   697                 break;
       
   698             }
       
   699         }
       
   700         if (j != delta.removedProperties.count())
       
   701             continue; // was removed
       
   702         // neither changed nor removed, but its children might be
       
   703         if (child->populationState == QScriptDebuggerLocalsModelNode::Populated) {
       
   704             QScriptDebuggerJob *job = new SyncModelIndexJob(indexFromNode(child), commandScheduler);
       
   705             jobScheduler->scheduleJob(job);
       
   706         }
       
   707     }
       
   708     addChildren(index, node, delta.addedProperties);
       
   709 }
       
   710 
       
   711 void QScriptDebuggerLocalsModelPrivate::syncTopLevelNodes()
       
   712 {
       
   713     Q_Q(QScriptDebuggerLocalsModel);
       
   714     for (int i = 0; i < invisibleRootNode->children.count(); ++i) {
       
   715         QModelIndex index = q->index(i, 0, QModelIndex());
       
   716         syncIndex(index);
       
   717         if (i == 0)
       
   718             emit q->scopeObjectAvailable(index);
       
   719     }
       
   720 }
       
   721 
       
   722 void QScriptDebuggerLocalsModelPrivate::removeTopLevelNodes()
       
   723 {
       
   724     while (!invisibleRootNode->children.isEmpty())
       
   725         removeChild(QModelIndex(), invisibleRootNode, 0);
       
   726 }
       
   727 
       
   728 void QScriptDebuggerLocalsModelPrivate::emitScopeObjectAvailable(const QModelIndex &index)
       
   729 {
       
   730     emit q_func()->scopeObjectAvailable(index);
       
   731 }
       
   732 
       
   733 int QScriptDebuggerLocalsModel::frameIndex() const
       
   734 {
       
   735     Q_D(const QScriptDebuggerLocalsModel);
       
   736     return d->frameIndex;
       
   737 }
       
   738 
       
   739 /*!
       
   740   \reimp
       
   741 */
       
   742 QModelIndex QScriptDebuggerLocalsModel::index(int row, int column, const QModelIndex &parent) const
       
   743 {
       
   744     Q_D(const QScriptDebuggerLocalsModel);
       
   745     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent);
       
   746     if ((row < 0) || (row >= node->children.count()))
       
   747         return QModelIndex();
       
   748     return createIndex(row, column, node->children.at(row));
       
   749 }
       
   750 
       
   751 /*!
       
   752   \reimp
       
   753 */
       
   754 QModelIndex QScriptDebuggerLocalsModel::parent(const QModelIndex &index) const
       
   755 {
       
   756     Q_D(const QScriptDebuggerLocalsModel);
       
   757     if (!index.isValid())
       
   758         return QModelIndex();
       
   759     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index);
       
   760     return d->indexFromNode(node->parent);
       
   761 }
       
   762 
       
   763 /*!
       
   764   \reimp
       
   765 */
       
   766 int QScriptDebuggerLocalsModel::columnCount(const QModelIndex &) const
       
   767 {
       
   768     return 2;
       
   769 }
       
   770 
       
   771 /*!
       
   772   \reimp
       
   773 */
       
   774 int QScriptDebuggerLocalsModel::rowCount(const QModelIndex &parent) const
       
   775 {
       
   776     Q_D(const QScriptDebuggerLocalsModel);
       
   777     // ### need this to make it work with a sortfilterproxymodel (QSFPM is too eager)
       
   778     const_cast<QScriptDebuggerLocalsModel*>(this)->fetchMore(parent);
       
   779     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent);
       
   780     return node ? node->children.count() : 0;
       
   781 }
       
   782 
       
   783 /*!
       
   784   \reimp
       
   785 */
       
   786 QVariant QScriptDebuggerLocalsModel::data(const QModelIndex &index, int role) const
       
   787 {
       
   788     Q_D(const QScriptDebuggerLocalsModel);
       
   789     if (!index.isValid())
       
   790         return QVariant();
       
   791     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index);
       
   792     if (role == Qt::DisplayRole) {
       
   793         if (index.column() == 0)
       
   794             return node->property.name();
       
   795         else if (index.column() == 1) {
       
   796             QString str = node->property.valueAsString();
       
   797             if (str.indexOf(QLatin1Char('\n')) != -1) {
       
   798                 QStringList lines = str.split(QLatin1Char('\n'));
       
   799                 int lineCount = lines.size();
       
   800                 if (lineCount > 1) {
       
   801                     lines = lines.mid(0, 1);
       
   802                     lines.append(QString::fromLatin1("(... %0 more lines ...)").arg(lineCount - 1));
       
   803                 }
       
   804                 str = lines.join(QLatin1String("\n"));
       
   805             }
       
   806             return str;
       
   807         }
       
   808     } else if (role == Qt::EditRole) {
       
   809         if ((index.column() == 1) && !d->isTopLevelNode(node)) {
       
   810             QString str = node->property.valueAsString();
       
   811             if (node->property.value().type() == QScriptDebuggerValue::StringValue) {
       
   812                 // escape
       
   813                 str.replace(QLatin1Char('\"'), QLatin1String("\\\""));
       
   814                 str.prepend(QLatin1Char('\"'));
       
   815                 str.append(QLatin1Char('\"'));
       
   816             }
       
   817             return str;
       
   818         }
       
   819     } else if (role == Qt::ToolTipRole) {
       
   820         if (index.column() == 1) {
       
   821             QString str = node->property.valueAsString();
       
   822             if (str.indexOf(QLatin1Char('\n')) != -1)
       
   823                 return str;
       
   824         }
       
   825     }
       
   826     // ### do this in the delegate
       
   827     else if (role == Qt::BackgroundRole) {
       
   828         if (d->isTopLevelNode(node))
       
   829             return QBrush(Qt::darkGray);
       
   830     } else if (role == Qt::TextColorRole) {
       
   831         if (d->isTopLevelNode(node))
       
   832             return QColor(Qt::white);
       
   833     } else if (role == Qt::FontRole) {
       
   834         if (d->isTopLevelNode(node) || node->changed) {
       
   835             QFont fnt;
       
   836             fnt.setBold(true);
       
   837             return fnt;
       
   838         }
       
   839     }
       
   840     return QVariant();
       
   841 }
       
   842 
       
   843 /*!
       
   844   \reimp
       
   845 */
       
   846 bool QScriptDebuggerLocalsModel::setData(const QModelIndex &index, const QVariant &value, int role)
       
   847 {
       
   848     Q_D(QScriptDebuggerLocalsModel);
       
   849     if (!index.isValid())
       
   850         return false;
       
   851     if (role != Qt::EditRole)
       
   852         return false;
       
   853     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index);
       
   854     if (!node)
       
   855         return false;
       
   856     QString expr = value.toString().trimmed();
       
   857     if (expr.isEmpty())
       
   858         return false;
       
   859     QScriptDebuggerJob *job = new SetPropertyJob(index, expr, d->commandScheduler);
       
   860     d->jobScheduler->scheduleJob(job);
       
   861     return true;
       
   862 }
       
   863 
       
   864 /*!
       
   865   \reimp
       
   866 */
       
   867 QVariant QScriptDebuggerLocalsModel::headerData(int section, Qt::Orientation orient, int role) const
       
   868 {
       
   869     if (orient == Qt::Horizontal) {
       
   870         if (role == Qt::DisplayRole) {
       
   871             if (section == 0)
       
   872                 return QCoreApplication::translate("QScriptDebuggerLocalsModel", "Name");
       
   873             else if (section == 1)
       
   874                 return QCoreApplication::translate("QScriptDebuggerLocalsModel", "Value");
       
   875         }
       
   876     }
       
   877     return QVariant();
       
   878 }
       
   879 
       
   880 /*!
       
   881   \reimp
       
   882 */
       
   883 Qt::ItemFlags QScriptDebuggerLocalsModel::flags(const QModelIndex &index) const
       
   884 {
       
   885     Q_D(const QScriptDebuggerLocalsModel);
       
   886     if (!index.isValid())
       
   887         return 0;
       
   888     Qt::ItemFlags ret = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
       
   889     if ((index.column() == 1) && index.parent().isValid()) {
       
   890         QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(index);
       
   891         if (!(node->property.flags() & QScriptValue::ReadOnly))
       
   892             ret |= Qt::ItemIsEditable;
       
   893     }
       
   894     return ret;
       
   895 }
       
   896 
       
   897 /*!
       
   898   \reimp
       
   899 */
       
   900 bool QScriptDebuggerLocalsModel::hasChildren(const QModelIndex &parent) const
       
   901 {
       
   902     Q_D(const QScriptDebuggerLocalsModel);
       
   903     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent);
       
   904     if (!node)
       
   905         return false;
       
   906     return !node->children.isEmpty()
       
   907         || ((node->property.value().type() == QScriptDebuggerValue::ObjectValue)
       
   908             && (node->populationState == QScriptDebuggerLocalsModelNode::NotPopulated));
       
   909 }
       
   910 
       
   911 /*!
       
   912   \reimp
       
   913 */
       
   914 bool QScriptDebuggerLocalsModel::canFetchMore(const QModelIndex &parent) const
       
   915 {
       
   916     Q_D(const QScriptDebuggerLocalsModel);
       
   917     if (!parent.isValid())
       
   918         return false;
       
   919     QScriptDebuggerLocalsModelNode *node = d->nodeFromIndex(parent);
       
   920     return node
       
   921         && (node->property.value().type() == QScriptDebuggerValue::ObjectValue)
       
   922         && (node->populationState == QScriptDebuggerLocalsModelNode::NotPopulated);
       
   923 }
       
   924 
       
   925 /*!
       
   926   \reimp
       
   927 */
       
   928 void QScriptDebuggerLocalsModel::fetchMore(const QModelIndex &parent)
       
   929 {
       
   930     Q_D(QScriptDebuggerLocalsModel);
       
   931     d->populateIndex(parent);
       
   932 }
       
   933 
       
   934 QT_END_NAMESPACE