diff options
Diffstat (limited to 'tools/qmldebugger/engine.cpp')
-rw-r--r-- | tools/qmldebugger/engine.cpp | 407 |
1 files changed, 398 insertions, 9 deletions
diff --git a/tools/qmldebugger/engine.cpp b/tools/qmldebugger/engine.cpp index 6e163e6..5a1a01b 100644 --- a/tools/qmldebugger/engine.cpp +++ b/tools/qmldebugger/engine.cpp @@ -6,13 +6,278 @@ #include <QLineEdit> #include <QTreeWidget> #include <QTableWidget> +#include <QTabWidget> +#include <QMouseEvent> +#include <QAction> +#include <QMenu> +#include <QInputDialog> #include <QFile> +#include <QPointer> #include <private/qmlenginedebug_p.h> #include <QtDeclarative/qmlcomponent.h> #include <QtDeclarative/qfxitem.h> +#include <QtDeclarative/qmldebugservice.h> QT_BEGIN_NAMESPACE +class QmlObjectTree : public QTreeWidget +{ + Q_OBJECT +public: + enum AdditionalRoles { + ContextIdRole = Qt::UserRole + 1 + }; + + QmlObjectTree(QWidget *parent = 0); + + QTreeWidgetItem *findItemByObjectId(int debugId) const; + +signals: + void addExpressionWatch(int debugId, const QString &); + +protected: + virtual void mousePressEvent(QMouseEvent *); + +private: + QTreeWidgetItem *findItem(QTreeWidgetItem *item, int debugId) const; +}; + +QmlObjectTree::QmlObjectTree(QWidget *parent) +: QTreeWidget(parent) +{ +} + +QTreeWidgetItem *QmlObjectTree::findItemByObjectId(int debugId) const +{ + for (int i=0; i<topLevelItemCount(); i++) { + QTreeWidgetItem *item = findItem(topLevelItem(i), debugId); + if (item) + return item; + } + + return 0; +} + +QTreeWidgetItem *QmlObjectTree::findItem(QTreeWidgetItem *item, int debugId) const +{ + if (item->data(0, Qt::UserRole).toInt() == debugId) + return item; + + QTreeWidgetItem *child; + for (int i=0; i<item->childCount(); i++) { + child = findItem(item->child(i), debugId); + if (child) + return child; + } + + return 0; +} + +void QmlObjectTree::mousePressEvent(QMouseEvent *me) +{ + QTreeWidget::mousePressEvent(me); + if (!currentItem()) + return; + if(me->button() == Qt::RightButton && me->type() == QEvent::MouseButtonPress) { + QAction action(tr("Add watch..."), 0); + QList<QAction *> actions; + actions << &action; + int debugId = currentItem()->data(0, Qt::UserRole).toInt(); + if (debugId >= 0 && QMenu::exec(actions, me->globalPos())) { + bool ok = false; + QString watch = QInputDialog::getText(this, tr("Watch expression"), + tr("Expression:"), QLineEdit::Normal, QString(), &ok); + if (ok && !watch.isEmpty()) + emit addExpressionWatch(debugId, watch); + } + } +} + + +class WatchTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + WatchTableModel(QObject *parent = 0); + + void addWatch(QmlDebugWatch *watch, const QString &title); + QmlDebugWatch *removeWatch(QmlDebugWatch *watch); + + void updateWatch(QmlDebugWatch *watch, const QVariant &value); + + QmlDebugWatch *watchFromIndex(const QModelIndex &index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +private: + int columnForWatch(QmlDebugWatch *watch) const; + void addValue(int column, const QVariant &value); + + struct WatchedEntity + { + QString title; + bool hasFirstValue; + QPointer<QmlDebugWatch> watch; + }; + + struct Value { + int column; + QVariant variant; + bool first; + }; + + QList<WatchedEntity> m_columns; + QList<Value> m_values; +}; + +WatchTableModel::WatchTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +void WatchTableModel::addWatch(QmlDebugWatch *watch, const QString &title) +{ + int col = columnCount(QModelIndex()); + beginInsertColumns(QModelIndex(), col, col); + + WatchedEntity e; + e.title = title; + e.hasFirstValue = false; + e.watch = watch; + m_columns.append(e); + + endInsertColumns(); +} + +QmlDebugWatch *WatchTableModel::removeWatch(QmlDebugWatch *watch) +{ + int column = columnForWatch(watch); + if (column == -1) + return 0; + + WatchedEntity entity = m_columns.takeAt(column); + + for (QList<Value>::Iterator iter = m_values.begin(); iter != m_values.end();) { + if (iter->column == column) { + iter = m_values.erase(iter); + } else { + if(iter->column > column) + --iter->column; + ++iter; + } + } + + reset(); + + return entity.watch; +} + +void WatchTableModel::updateWatch(QmlDebugWatch *watch, const QVariant &value) +{ + int column = columnForWatch(watch); + if (column == -1) + return; + + addValue(column, value); + + if (!m_columns[column].hasFirstValue) { + m_columns[column].hasFirstValue = true; + m_values[m_values.count() - 1].first = true; + } +} + +QmlDebugWatch *WatchTableModel::watchFromIndex(const QModelIndex &index) const +{ + if (index.isValid() && index.column() < m_columns.count()) + return m_columns.at(index.column()).watch; + return 0; +} + +int WatchTableModel::rowCount(const QModelIndex &) const +{ + return m_values.count(); +} + +int WatchTableModel::columnCount(const QModelIndex &) const +{ + return m_columns.count(); +} + +QVariant WatchTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (section < m_columns.count() && role == Qt::DisplayRole) + return m_columns.at(section).title; + } else { + if (role == Qt::DisplayRole) + return section + 1; + } + return QVariant(); +} + +QVariant WatchTableModel::data(const QModelIndex &idx, int role) const +{ + if (m_values.at(idx.row()).column == idx.column()) { + if (role == Qt::DisplayRole) { + const QVariant &value = m_values.at(idx.row()).variant; + QString str = value.toString(); + + if (str.isEmpty() && QmlMetaType::isObject(value.userType())) { + QObject *o = QmlMetaType::toQObject(value); + if(o) { + QString objectName = o->objectName(); + if(objectName.isEmpty()) + objectName = QLatin1String("<unnamed>"); + str = QLatin1String(o->metaObject()->className()) + + QLatin1String(": ") + objectName; + } + } + + if(str.isEmpty()) { + QDebug d(&str); + d << value; + } + return QVariant(str); + } else if(role == Qt::BackgroundRole) { + if(m_values.at(idx.row()).first) + return QColor(Qt::green); + else + return QVariant(); + } else { + return QVariant(); + } + } else { + return QVariant(); + } +} + +int WatchTableModel::columnForWatch(QmlDebugWatch *watch) const +{ + for (int i=0; i<m_columns.count(); i++) { + if (m_columns.at(i).watch == watch) + return i; + } + return -1; +} + +void WatchTableModel::addValue(int column, const QVariant &value) +{ + int row = columnCount(QModelIndex()); + beginInsertRows(QModelIndex(), row, row); + + Value v; + v.column = column; + v.variant = value; + v.first = false; + m_values.append(v); + + endInsertRows(); +} + + class DebuggerEngineItem : public QObject { Q_OBJECT @@ -32,7 +297,7 @@ private: }; EnginePane::EnginePane(QmlDebugConnection *client, QWidget *parent) -: QWidget(parent), m_client(client), m_engines(0), m_context(0), m_object(0) +: QWidget(parent), m_client(client), m_engines(0), m_context(0), m_object(0), m_watchedObject(0), m_watchTableModel(0) { QVBoxLayout *layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); @@ -67,18 +332,31 @@ EnginePane::EnginePane(QmlDebugConnection *client, QWidget *parent) QHBoxLayout *hbox = new QHBoxLayout; hbox->setContentsMargins(0, 0, 0, 0); - m_objTree = new QTreeWidget(this); + m_objTree = new QmlObjectTree(this); m_objTree->setHeaderHidden(true); connect(m_objTree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(itemClicked(QTreeWidgetItem *))); + connect(m_objTree, SIGNAL(addExpressionWatch(int,QString)), this, SLOT(addExpressionWatch(int,QString))); hbox->addWidget(m_objTree); m_propTable = new QTableWidget(this); + connect(m_propTable, SIGNAL(itemDoubleClicked(QTableWidgetItem *)), this, SLOT(propertyDoubleClicked(QTableWidgetItem *))); m_propTable->setColumnCount(2); m_propTable->setColumnWidth(0, 150); m_propTable->setColumnWidth(1, 400); m_propTable->setHorizontalHeaderLabels(QStringList() << "name" << "value"); - hbox->addWidget(m_propTable); - hbox->setStretchFactor(m_propTable, 2); + + m_watchTableModel = new WatchTableModel(this); + m_watchTable = new QTableView(this); + m_watchTable->setModel(m_watchTableModel); + QObject::connect(m_watchTable, SIGNAL(activated(QModelIndex)), + this, SLOT(watchedItemActivated(QModelIndex))); + + m_tabs = new QTabWidget(this); + m_tabs->addTab(m_propTable, tr("Properties")); + m_tabs->addTab(m_watchTable, tr("Watching")); + + hbox->addWidget(m_tabs); + hbox->setStretchFactor(m_tabs, 2); layout->addLayout(hbox); } @@ -112,15 +390,113 @@ void EnginePane::showProperties() m_propTable->setRowCount(obj.properties().count()); for (int ii = 0; ii < obj.properties().count(); ++ii) { QTableWidgetItem *name = new QTableWidgetItem(obj.properties().at(ii).name()); + name->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); m_propTable->setItem(ii, 0, name); QTableWidgetItem *value; if (!obj.properties().at(ii).binding().isEmpty()) value = new QTableWidgetItem(obj.properties().at(ii).binding()); else value = new QTableWidgetItem(obj.properties().at(ii).value().toString()); + value->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); m_propTable->setItem(ii, 1, value); } - delete m_object; m_object = 0; + + if (m_watchedObject) { + m_client.removeWatch(m_watchedObject); + delete m_watchedObject; + m_watchedObject = 0; + } + + QmlDebugWatch *watch = m_client.addWatch(obj, this); + m_watchedObject = watch; + QObject::connect(watch, SIGNAL(valueChanged(QByteArray,QVariant)), + this, SLOT(valueChanged(QByteArray,QVariant))); + + // don't delete, keep it for when property table cells are clicked + //delete m_object; m_object = 0; +} + +void EnginePane::addExpressionWatch(int debugId, const QString &expr) +{ + QmlDebugWatch *watch = m_client.addWatch(QmlDebugObjectReference(debugId), expr, this); + + QObject::connect(watch, SIGNAL(valueChanged(QByteArray,QVariant)), + this, SLOT(valueChanged(QByteArray,QVariant))); + m_watchTableModel->addWatch(watch, expr); + m_watchTable->resizeColumnsToContents(); +} + +void EnginePane::valueChanged(const QByteArray &propertyName, const QVariant &value) +{ + if (!m_object) + return; + + QmlDebugWatch *watch = qobject_cast<QmlDebugWatch*>(sender()); + + m_watchTableModel->updateWatch(watch, value); + + if (!propertyName.isEmpty()) { + QmlDebugObjectReference obj = m_object->object(); + if (obj.debugId() == watch->objectDebugId()) { + for (int ii=0; ii<m_propTable->rowCount(); ii++) { + if (m_propTable->item(ii, 0)->text() == propertyName) { + m_propTable->item(ii, 1)->setText(value.toString()); + break; + } + } + } + } +} + +void EnginePane::propertyDoubleClicked(QTableWidgetItem *item) +{ + if (!m_object || item->column() > 0) + return; + QList<QmlDebugPropertyReference> props = m_object->object().properties(); + if (item->row() < props.count()) { + bool watching = togglePropertyWatch(m_object->object(), props.at(item->row())); + if (watching) + item->setForeground(Qt::red); + else + item->setForeground(QBrush()); + } +} + +bool EnginePane::togglePropertyWatch(const QmlDebugObjectReference &object, const QmlDebugPropertyReference &property) +{ + QPair<int, QString> objProperty(object.debugId(), property.name()); + + if (m_watchedProps.contains(objProperty)) { + QmlDebugWatch *watch = m_watchedProps.take(objProperty); + m_watchTableModel->removeWatch(watch); + delete watch; + return false; + } else { + QmlDebugWatch *watch = m_client.addWatch(property, this); + m_watchedProps.insert(objProperty, watch); + QObject::connect(watch, SIGNAL(valueChanged(QByteArray,QVariant)), + this, SLOT(valueChanged(QByteArray,QVariant))); + QString desc = property.name() + + QLatin1String(" on\n") + + object.className() + + QLatin1String(": ") + + (object.name().isEmpty() ? QLatin1String("<unnamed>") : object.name()); + m_watchTableModel->addWatch(watch, desc); + m_watchTable->resizeColumnsToContents(); + return true; + } +} + +void EnginePane::watchedItemActivated(const QModelIndex &index) +{ + QmlDebugWatch *watch = m_watchTableModel->watchFromIndex(index); + if (!watch) + return; + QTreeWidgetItem *item = m_objTree->findItemByObjectId(watch->objectDebugId()); + if (item) { + m_objTree->setCurrentItem(item); + item->setExpanded(true); + } } void EnginePane::queryContext(int id) @@ -143,6 +519,7 @@ void EnginePane::contextChanged() dump(m_context->rootContext(), 0); foreach (const QmlDebugObjectReference &object, m_context->rootContext().objects()) fetchObject(object.debugId()); + delete m_context; m_context = 0; } @@ -175,17 +552,29 @@ void EnginePane::buildTree(const QmlDebugObjectReference &obj, QTreeWidgetItem * { if (!parent) m_objTree->clear(); - m_objTree->expandAll(); QTreeWidgetItem *item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem(m_objTree); item->setText(0, obj.className()); item->setData(0, Qt::UserRole, obj.debugId()); + item->setData(0, QmlObjectTree::ContextIdRole, obj.contextDebugId()); + + if (parent && obj.contextDebugId() >= 0 + && obj.contextDebugId() != parent->data(0, QmlObjectTree::ContextIdRole).toInt()) { + QmlDebugFileReference source = obj.source(); + if (!source.url().isEmpty()) { + QString toolTipString = QLatin1String("URL: ") + source.url().toString(); + item->setToolTip(0, toolTipString); + } + item->setForeground(0, QColor("orange")); + } else { + item->setExpanded(true); + } + + if (obj.contextDebugId() < 0) + item->setForeground(0, Qt::lightGray); for (int ii = 0; ii < obj.children().count(); ++ii) buildTree(obj.children().at(ii), item); - - if (!parent) - m_objTree->expandAll(); } void EnginePane::queryEngines() |