Commit f5039a4a authored by Christiaan Janssen's avatar Christiaan Janssen

QmlJsInspector: implemented Property Inspector

Reviewed-by: Kai Koehne
parent 6a829f5a
......@@ -57,6 +57,9 @@ public:
explicit CrumblePathButton(const QString &title, QWidget *parent = 0);
void setSegmentType(int type);
void select(bool s);
void setData(QVariant data);
QVariant data() const;
protected:
void paintEvent(QPaintEvent *);
void mouseMoveEvent(QMouseEvent *e);
......@@ -70,6 +73,7 @@ private:
private:
bool m_isHovering;
bool m_isPressed;
bool m_isSelected;
bool m_isEnd;
QColor m_baseColor;
QImage m_segment;
......@@ -79,10 +83,12 @@ private:
QImage m_segmentHover;
QImage m_segmentHoverEnd;
QPoint m_textPos;
QVariant m_data;
};
CrumblePathButton::CrumblePathButton(const QString &title, QWidget *parent)
: QPushButton(title, parent), m_isHovering(false), m_isPressed(false), m_isEnd(true)
: QPushButton(title, parent), m_isHovering(false), m_isPressed(false), m_isSelected(false), m_isEnd(true)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
setToolTip(title);
......@@ -114,7 +120,7 @@ void CrumblePathButton::paintEvent(QPaintEvent *)
}
if (m_isEnd) {
if (m_isPressed) {
if (m_isPressed || m_isSelected) {
Utils::StyleHelper::drawCornerImage(m_segmentSelectedEnd, &p, geom, 2, 0, 2, 0);
} else if (m_isHovering) {
Utils::StyleHelper::drawCornerImage(m_segmentHoverEnd, &p, geom, 2, 0, 2, 0);
......@@ -122,7 +128,7 @@ void CrumblePathButton::paintEvent(QPaintEvent *)
Utils::StyleHelper::drawCornerImage(m_segmentEnd, &p, geom, 2, 0, 2, 0);
}
} else {
if (m_isPressed) {
if (m_isPressed || m_isSelected) {
Utils::StyleHelper::drawCornerImage(m_segmentSelected, &p, geom, 2, 0, 12, 0);
} else if (m_isHovering) {
Utils::StyleHelper::drawCornerImage(m_segmentHover, &p, geom, 2, 0, 12, 0);
......@@ -175,6 +181,12 @@ void CrumblePathButton::mouseReleaseEvent(QMouseEvent *e)
update();
}
void CrumblePathButton::select(bool s)
{
m_isSelected = s;
update();
}
void CrumblePathButton::setSegmentType(int type)
{
bool useLeftPadding = !(type & FirstSegment);
......@@ -182,6 +194,16 @@ void CrumblePathButton::setSegmentType(int type)
m_textPos.setX(useLeftPadding ? 18 : 4);
}
void CrumblePathButton::setData(QVariant data)
{
m_data = data;
}
QVariant CrumblePathButton::data() const
{
return m_data;
}
struct CrumblePathPrivate {
explicit CrumblePathPrivate(CrumblePath *q);
......@@ -216,12 +238,25 @@ CrumblePath::~CrumblePath()
d->m_buttons.clear();
}
void CrumblePath::selectIndex(int index)
{
if ((index > -1) && (index < d->m_buttons.length()))
d->m_buttons[index]->select(true);
}
QVariant CrumblePath::dataForIndex(int index) const
{
if ((index > -1) && (index < d->m_buttons.length()))
return d->m_buttons[index]->data();
return QVariant();
}
void CrumblePath::setBackgroundStyle()
{
d->m_background->setStyleSheet("QWidget { background-color:" + d->m_baseColor.name() + ";}");
}
void CrumblePath::pushElement(const QString &title)
void CrumblePath::pushElement(const QString &title, const QVariant data)
{
CrumblePathButton *newButton = new CrumblePathButton(title, this);
newButton->hide();
......@@ -237,6 +272,7 @@ void CrumblePath::pushElement(const QString &title)
segType = CrumblePathButton::FirstSegment | CrumblePathButton::LastSegment;
newButton->setSegmentType(segType);
}
newButton->setData(data);
d->m_buttons.append(newButton);
resizeButtons();
......@@ -272,8 +308,6 @@ void CrumblePath::resizeEvent(QResizeEvent *)
void CrumblePath::resizeButtons()
{
int buttonMinWidth = 0;
int buttonMaxWidth = 0;
int totalWidthLeft = width();
if (d->m_buttons.length() >= 1) {
......@@ -281,26 +315,34 @@ void CrumblePath::resizeButtons()
d->m_buttons[0]->raise();
// rearrange all items so that the first item is on top (added last).
// compute relative sizes
QList <int> sizes;
int totalSize = 0;
for(int i = 0; i < d->m_buttons.length() ; ++i) {
CrumblePathButton *button = d->m_buttons[i];
QFontMetrics fm(button->font());
buttonMinWidth = ArrowBorderSize + fm.width(button->text()) + ArrowBorderSize * 2 ;
buttonMaxWidth = (totalWidthLeft + ArrowBorderSize * (d->m_buttons.length() - i)) / (d->m_buttons.length() - i);
if (buttonMinWidth > buttonMaxWidth && i < d->m_buttons.length() - 1) {
buttonMinWidth = buttonMaxWidth;
} else if (i > 3 && (i == d->m_buttons.length() - 1)) {
buttonMinWidth = width() - nextElementPosition.x();
buttonMaxWidth = buttonMinWidth;
}
button->setMinimumWidth(buttonMinWidth);
button->setMaximumWidth(buttonMaxWidth);
int originalSize = ArrowBorderSize + fm.width(button->text()) + ArrowBorderSize + 12;
sizes << originalSize;
totalSize += originalSize - ArrowBorderSize;
}
for (int i = 0; i < d->m_buttons.length() ; ++i) {
CrumblePathButton *button = d->m_buttons[i];
int candidateSize = (sizes[i]*totalWidthLeft)/totalSize;
if (candidateSize < ArrowBorderSize)
candidateSize = ArrowBorderSize;
if (candidateSize > sizes[i]*1.3)
candidateSize = sizes[i]*1.3;
button->setMinimumWidth(candidateSize);
button->setMaximumWidth(candidateSize);
button->move(nextElementPosition);
nextElementPosition.rx() += button->width() - ArrowBorderSize;
totalWidthLeft -= button->width();
button->show();
if (i > 0)
......
......@@ -37,6 +37,7 @@
#include "utils_global.h"
#include <QtGui/QWidget>
#include <QVariant>
QT_FORWARD_DECLARE_CLASS(QResizeEvent)
......@@ -50,9 +51,11 @@ class QTCREATOR_UTILS_EXPORT CrumblePath : public QWidget
public:
explicit CrumblePath(QWidget *parent = 0);
~CrumblePath();
void selectIndex(int index);
QVariant dataForIndex(int index) const;
public slots:
void pushElement(const QString &title);
void pushElement(const QString &title, const QVariant data = QVariant());
void popElement();
void clear();
......
......@@ -1054,22 +1054,6 @@ protected:
return 0;
}
inline bool hasVisualPresentation(Node *ast)
{
Bind *bind = m_lookupContext->document()->bind();
const Interpreter::ObjectValue *objValue = bind->findQmlObject(ast);
if (!objValue)
return false;
QStringList prototypes;
foreach (const Interpreter::ObjectValue *value,
Interpreter::PrototypeIterator(objValue, m_lookupContext->context()).all()) {
prototypes.append(value->className());
}
return prototypes.contains(QString("QGraphicsObject"));
}
inline bool isIdBinding(UiObjectMember *member) const
{
if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
......@@ -1116,7 +1100,7 @@ protected:
if ((isRangeSelected() && intersectsCursor(begin, end))
|| (!isRangeSelected() && containsCursor(begin, end)))
{
if (initializer(member) && isSelectable(member) && hasVisualPresentation(member)) {
if (initializer(member) && isSelectable(member)) {
m_selectedMembers << member;
// move start towards end; this facilitates multiselection so that root is usually ignored.
m_cursorPositionStart = qMin(end, m_cursorPositionEnd);
......
......@@ -43,13 +43,13 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/styledbar.h>
#include <utils/filterlineedit.h>
#include <QAction>
#include <QActionGroup>
#include <QHBoxLayout>
#include <QMenu>
#include <QToolButton>
#include <QLineEdit>
namespace QmlJSInspector {
namespace Internal {
......@@ -78,6 +78,7 @@ QmlInspectorToolbar::QmlInspectorToolbar(QObject *parent) :
m_menuPauseAction(0),
m_playIcon(QIcon(QLatin1String(":/qml/images/play-small.png"))),
m_pauseIcon(QIcon(QLatin1String(":/qml/images/pause-small.png"))),
m_filterExp(0),
m_colorBox(0),
m_emitSignals(true),
m_isRunning(false),
......@@ -98,6 +99,7 @@ void QmlInspectorToolbar::setEnabled(bool value)
m_zoomAction->setEnabled(value);
m_colorPickerAction->setEnabled(value);
m_colorBox->setEnabled(value);
m_filterExp->setEnabled(value);
}
void QmlInspectorToolbar::enable()
......@@ -295,6 +297,10 @@ void QmlInspectorToolbar::createActions(const Core::Context &context)
m_colorBox->setInnerBorderColor(QColor(192,192,192));
m_colorBox->setOuterBorderColor(QColor(58,58,58));
configBarLayout->addWidget(m_colorBox);
m_filterExp = new QLineEdit(m_barWidget);
m_filterExp->setPlaceholderText("<filter property list>");
configBarLayout->addWidget(m_filterExp);
configBarLayout->addStretch();
setEnabled(false);
......@@ -307,6 +313,7 @@ void QmlInspectorToolbar::createActions(const Core::Context &context)
connect(m_selectAction, SIGNAL(triggered()), SLOT(activateSelectToolOnClick()));
connect(m_zoomAction, SIGNAL(triggered()), SLOT(activateZoomOnClick()));
connect(m_colorPickerAction, SIGNAL(triggered()), SLOT(activateColorPickerOnClick()));
connect(m_filterExp, SIGNAL(textChanged(QString)), SIGNAL(filterTextChanged(QString)));
}
QWidget *QmlInspectorToolbar::widget() const
......
......@@ -40,6 +40,7 @@
QT_FORWARD_DECLARE_CLASS(QAction)
QT_FORWARD_DECLARE_CLASS(QColor)
QT_FORWARD_DECLARE_CLASS(QToolButton)
QT_FORWARD_DECLARE_CLASS(QLineEdit)
namespace Core {
class Context;
......@@ -98,6 +99,7 @@ signals:
void showAppOnTopSelected(bool isChecked);
void animationSpeedChanged(qreal slowdownFactor = 1.0f);
void filterTextChanged(const QString &);
private slots:
void activateDesignModeOnClick();
......@@ -140,6 +142,8 @@ private:
QIcon m_playIcon;
QIcon m_pauseIcon;
QLineEdit *m_filterExp;
ToolBarColorBox *m_colorBox;
bool m_emitSignals;
......
......@@ -157,6 +157,8 @@ void ClientProxy::disconnectFromServer()
qDeleteAll(m_objectTreeQuery);
m_objectTreeQuery.clear();
removeAllObjectWatches();
updateConnected();
}
......@@ -226,7 +228,7 @@ QDeclarativeDebugObjectReference ClientProxy::objectReferenceForId(int debugId,
if (objectRef.debugId() == debugId)
return objectRef;
foreach(const QDeclarativeDebugObjectReference &child, objectRef.children()) {
foreach (const QDeclarativeDebugObjectReference &child, objectRef.children()) {
QDeclarativeDebugObjectReference result = objectReferenceForId(debugId, child);
if (result.debugId() == debugId)
return result;
......@@ -261,7 +263,7 @@ QDeclarativeDebugObjectReference ClientProxy::objectReferenceForLocation(const i
QList<QDeclarativeDebugObjectReference> ClientProxy::objectReferences() const
{
QList<QDeclarativeDebugObjectReference> result;
foreach(const QDeclarativeDebugObjectReference &it, m_rootObjects) {
foreach (const QDeclarativeDebugObjectReference &it, m_rootObjects) {
result.append(objectReferences(it));
}
return result;
......@@ -272,7 +274,7 @@ QList<QDeclarativeDebugObjectReference> ClientProxy::objectReferences(const QDec
QList<QDeclarativeDebugObjectReference> result;
result.append(objectRef);
foreach(const QDeclarativeDebugObjectReference &child, objectRef.children()) {
foreach (const QDeclarativeDebugObjectReference &child, objectRef.children()) {
result.append(objectReferences(child));
}
......@@ -331,6 +333,64 @@ void ClientProxy::clearComponentCache()
m_observerClient->clearComponentCache();
}
bool ClientProxy::addObjectWatch(int objectDebugId)
{
if (debug)
qDebug() << "addObjectWatch():" << objectDebugId;
if (objectDebugId == -1)
return false;
// already set
if (m_objectWatches.keys().contains(objectDebugId))
return true;
QDeclarativeDebugObjectReference ref = objectReferenceForId(objectDebugId);
if (ref.debugId() != objectDebugId)
return false;
QDeclarativeDebugWatch *watch = m_engineClient->addWatch(ref, this);
m_objectWatches.insert(objectDebugId, watch);
connect(watch,SIGNAL(valueChanged(QByteArray,QVariant)),this,SLOT(objectWatchTriggered(QByteArray,QVariant)));
return false;
}
void ClientProxy::objectWatchTriggered(const QByteArray &propertyName, const QVariant &propertyValue)
{
QDeclarativeDebugWatch *watch = dynamic_cast<QDeclarativeDebugWatch *>(QObject::sender());
if (watch)
emit propertyChanged(watch->objectDebugId(),propertyName, propertyValue);
}
bool ClientProxy::removeObjectWatch(int objectDebugId)
{
if (debug)
qDebug() << "removeObjectWatch():" << objectDebugId;
if (objectDebugId == -1)
return false;
if (!m_objectWatches.keys().contains(objectDebugId))
return false;
QDeclarativeDebugWatch *watch = m_objectWatches.value(objectDebugId);
disconnect(watch,SIGNAL(valueChanged(QByteArray,QVariant)), this, SLOT(objectWatchTriggered(QByteArray,QVariant)));
m_engineClient->removeWatch(watch);
delete watch;
m_objectWatches.remove(objectDebugId);
return true;
}
void ClientProxy::removeAllObjectWatches()
{
foreach (int watchedObject, m_objectWatches.keys())
removeObjectWatch(watchedObject);
Q_ASSERT(m_objectWatches.count() == 0);
}
void ClientProxy::queryEngineContext(int id)
{
if (id < 0)
......@@ -403,7 +463,7 @@ void ClientProxy::objectTreeFetched(QDeclarativeDebugQuery::State state)
int old_count = m_debugIdHash.count();
m_debugIdHash.clear();
m_debugIdHash.reserve(old_count + 1);
foreach(const QDeclarativeDebugObjectReference &it, m_rootObjects)
foreach (const QDeclarativeDebugObjectReference &it, m_rootObjects)
buildDebugIdHashRecursive(it);
emit objectTreeUpdated();
......@@ -445,7 +505,7 @@ void ClientProxy::buildDebugIdHashRecursive(const QDeclarativeDebugObjectReferen
// append the debug ids in the hash
m_debugIdHash[qMakePair<QString, int>(filename, rev)][qMakePair<int, int>(lineNum, colNum)].append(ref.debugId());
foreach(const QDeclarativeDebugObjectReference &it, ref.children())
foreach (const QDeclarativeDebugObjectReference &it, ref.children())
buildDebugIdHashRecursive(it);
}
......
......@@ -70,6 +70,10 @@ public:
QDeclarativeDebugExpressionQuery *queryExpressionResult(int objectDebugId, const QString &expr, QObject *parent=0);
void clearComponentCache();
bool addObjectWatch(int objectDebugId);
bool removeObjectWatch(int objectDebugId);
void removeAllObjectWatches();
// returns the object references
QList<QDeclarativeDebugObjectReference> objectReferences() const;
QDeclarativeDebugObjectReference objectReferenceForId(int debugId) const;
......@@ -108,6 +112,7 @@ signals:
void serverReloaded();
void selectedColorChanged(const QColor &color);
void contextPathUpdated(const QStringList &contextPath);
void propertyChanged(int debugId, const QByteArray &propertyName, const QVariant &propertyValue);
public slots:
void refreshObjectTree();
......@@ -139,6 +144,7 @@ private slots:
void objectTreeFetched(QDeclarativeDebugQuery::State state = QDeclarativeDebugQuery::Completed);
void fetchContextObjectRecursive(const QmlJsDebugClient::QDeclarativeDebugContextReference& context);
void newObjects();
void objectWatchTriggered(const QByteArray &propertyName, const QVariant &propertyValue);
private:
void updateConnected();
......@@ -164,6 +170,8 @@ private:
QTimer m_requestObjectsTimer;
DebugIdHash m_debugIdHash;
QHash<int, QDeclarativeDebugWatch *> m_objectWatches;
bool m_isConnected;
};
......
......@@ -42,7 +42,7 @@ ContextCrumblePath::ContextCrumblePath(QWidget *parent)
: CrumblePath(parent), m_isEmpty(true)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
updateContextPath(QStringList());
updateContextPath(QStringList(),QList<int>());
}
ContextCrumblePath::~ContextCrumblePath()
......@@ -50,11 +50,14 @@ ContextCrumblePath::~ContextCrumblePath()
}
void ContextCrumblePath::updateContextPath(const QStringList &path)
void ContextCrumblePath::updateContextPath(const QStringList &path, const QList<int> &debugIds)
{
Q_ASSERT(path.count() == debugIds.count());
clear();
foreach(const QString &pathPart, path) {
pushElement(pathPart);
for (int i=0; i<path.count(); i++) {
pushElement(path[i],QVariant(debugIds[i]));
}
m_isEmpty = path.isEmpty();
......@@ -63,10 +66,20 @@ void ContextCrumblePath::updateContextPath(const QStringList &path)
}
}
void ContextCrumblePath::selectIndex(int index)
{
CrumblePath::selectIndex(index);
}
bool ContextCrumblePath::isEmpty() const
{
return m_isEmpty;
}
int ContextCrumblePath::debugIdForIndex(int index) const
{
return CrumblePath::dataForIndex(index).toInt();
}
} // namespace Internal
} // namespace QmlJSInspector
......@@ -46,9 +46,11 @@ public:
ContextCrumblePath(QWidget *parent = 0);
virtual ~ContextCrumblePath();
bool isEmpty() const;
int debugIdForIndex(int index) const;
public slots:
void updateContextPath(const QStringList &path);
void updateContextPath(const QStringList &path, const QList<int> &debugIds);
void selectIndex(int index);
private:
bool m_isEmpty;
};
......
......@@ -39,7 +39,7 @@
#include "qmljsprivateapi.h"
#include "qmljscontextcrumblepath.h"
#include "qmljsinspectorsettings.h"
#include "qmljsobjecttree.h"
#include "qmljspropertyinspector.h"
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsdocument.h>
......@@ -126,13 +126,13 @@ InspectorUi::InspectorUi(QObject *parent)
, m_listeningToEditorManager(false)
, m_toolbar(0)
, m_crumblePath(0)
, m_objectTreeWidget(0)
, m_propertyInspector(0)
, m_settings(new InspectorSettings(this))
, m_clientProxy(0)
, m_qmlEngine(0)
, m_debugQuery(0)
, m_lastSelectedDebugId(-1)
, m_debugProject(0)
, m_selectionCallbackExpected(false)
{
m_instance = this;
m_toolbar = new QmlInspectorToolbar(this);
......@@ -225,7 +225,7 @@ void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextE
if ((qmlNode->kind == QmlJS::AST::Node::Kind_IdentifierExpression) &&
(m_clientProxy->objectReferenceForId(refToLook).debugId() == -1)) {
query = doubleQuote + QString("local: ") + refToLook + doubleQuote;
foreach(QDeclarativeDebugPropertyReference property, ref.properties()) {
foreach (QDeclarativeDebugPropertyReference property, ref.properties()) {
if (property.name() == wordAtCursor
&& !property.valueTypeName().isEmpty()) {
query = doubleQuote + property.name() + QLatin1Char(':')
......@@ -239,7 +239,7 @@ void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextE
+ QLatin1Char('+') + refToLook;
} else {
// show properties
foreach(QDeclarativeDebugPropertyReference property, ref.properties()) {
foreach (QDeclarativeDebugPropertyReference property, ref.properties()) {
if (property.name() == wordAtCursor && !property.valueTypeName().isEmpty()) {
query = doubleQuote + property.name() + QLatin1Char(':')
+ doubleQuote + QLatin1Char('+') + property.name();
......@@ -250,7 +250,7 @@ void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextE
}
if (!query.isEmpty()) {
m_debugQuery = m_clientProxy->queryExpressionResult(ref.debugId(),query);
m_debugQuery = m_clientProxy->queryExpressionResult(ref.debugId(),query, this);
connect(m_debugQuery, SIGNAL(stateChanged(QDeclarativeDebugQuery::State)),
this, SLOT(debugQueryUpdated(QDeclarativeDebugQuery::State)));
}
......@@ -281,13 +281,23 @@ void InspectorUi::connected(ClientProxy *clientProxy)
{
m_clientProxy = clientProxy;
QmlJS::Snapshot snapshot = modelManager()->snapshot();
for (QHash<QString, QmlJSLiveTextPreview *>::const_iterator it = m_textPreviews.constBegin();
it != m_textPreviews.constEnd(); ++it) {
Document::Ptr doc = snapshot.document(it.key());
it.value()->resetInitialDoc(doc);
}
connect(m_clientProxy, SIGNAL(selectedItemsChanged(QList<QDeclarativeDebugObjectReference>)),
SLOT(gotoObjectReferenceDefinition(QList<QDeclarativeDebugObjectReference>)));
SLOT(selectItems(QList<QDeclarativeDebugObjectReference>)));
connect(m_clientProxy, SIGNAL(enginesChanged()), SLOT(updateEngineList()));
connect(m_clientProxy, SIGNAL(serverReloaded()), this, SLOT(serverReloaded()));
connect(m_clientProxy, SIGNAL(contextPathUpdated(QStringList)),
m_crumblePath, SLOT(updateContextPath(QStringList)));
connect(m_clientProxy, SIGNAL(propertyChanged(int, QByteArray,QVariant)),
m_propertyInspector, SLOT(propertyValueChanged(int, QByteArray,QVariant)));
connect(m_propertyInspector, SIGNAL(changePropertyValue(int,QString,QString)),
this, SLOT(changePropertyValue(int,QString,QString)));
connect(m_clientProxy,SIGNAL(objectTreeUpdated()),this,SLOT(objectTreeReady()));
m_debugProject = ProjectExplorer::ProjectExplorerPlugin::instance()->startupProject();
if (m_debugProject->activeTarget()
......@@ -307,22 +317,34 @@ void InspectorUi::connected(ClientProxy *clientProxy)
initializeDocuments();
QHashIterator<QString, QmlJSLiveTextPreview *> iter(m_textPreviews);
while(iter.hasNext()) {
while (iter.hasNext()) {
iter.next();
iter.value()->setClientProxy(m_clientProxy);
iter.value()->updateDebugIds();
}
}
void InspectorUi::objectTreeReady()
{
// Should only run once, after debugger startup
if (!m_clientProxy->rootObjectReference().isEmpty()) {