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

source code of qtscript/src/scripttools/debugging/qscriptdebuggerlocalsmodel.cpp