propertyeditor.cpp 36.2 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
7
8
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11
12
13
14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15
16
17
18
19
20
21
22
23
24
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
con's avatar
con committed
25
26
27
28
29
30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
31
32
33
34
35
36
**
**************************************************************************/

#include "propertyeditor.h"

#include <nodemetainfo.h>
37
#include <metainfo.h>
38
39

#include <invalididexception.h>
40
#include <rewritingexception.h>
41
42
43
#include <invalidnodestateexception.h>
#include <variantproperty.h>

44
45
46
#include <bindingproperty.h>

#include <nodeabstractproperty.h>
47
#include <rewriterview.h>
48

49
#include "propertyeditorvalue.h"
50
51
52
53
#include "basiclayouts.h"
#include "basicwidgets.h"
#include "resetwidget.h"
#include "qlayoutobject.h"
54
55
#include <qmleditorwidgets/colorwidgets.h>
#include "gradientlineqmladaptor.h"
56
57
58
#include "behaviordialog.h"
#include "qproxylayoutitem.h"
#include "fontwidget.h"
Thomas Hartmann's avatar
Thomas Hartmann committed
59
#include "siblingcombobox.h"
60
#include "propertyeditortransaction.h"
61
#include "originwidget.h"
62
63
64
65
66
67

#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileSystemWatcher>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
Christian Kamm's avatar
Christian Kamm committed
68
#include <QtCore/QTimer>
69
70
#include <QtDeclarative/QDeclarativeView>
#include <QtDeclarative/QDeclarativeContext>
71
72
73
#include <QtGui/QVBoxLayout>
#include <QtGui/QShortcut>
#include <QtGui/QStackedWidget>
74
75
#include <QDeclarativeEngine>
#include <private/qdeclarativemetatype_p.h>
76
#include <QMessageBox>
77
#include <QApplication>
78
79
#include <QGraphicsOpacityEffect>
#include <QToolBar>
80
81
82
83
84

enum {
    debug = false
};

85
86
const int collapseButtonOffset = 114;

87
88
namespace QmlDesigner {

89
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
90
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
91
        m_contextObject(new PropertyEditorContextObject())
92
93
94
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

95
    QDeclarativeContext *ctxt = m_view->rootContext();
96
    m_view->engine()->setOutputWarningsToStandardError(debug);
97
98
    m_dummyPropertyEditorValue->setValue("#000000");
    ctxt->setContextProperty("dummyBackendValue", m_dummyPropertyEditorValue.data());
99
100
    m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
    ctxt->setContextObject(m_contextObject.data());
101

102
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(const QString&, const QVariant&)), propertyEditor, SLOT(changeValue(const QString&)));
103
104
105
106
107
108
}

PropertyEditor::NodeType::~NodeType()
{
}

109
void setupPropertyEditorValue(const QString &name, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor, const QString &type)
110
111
112
{
    QString propertyName(name);
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
113
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(propertyMap->value(propertyName)));
114
115
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
116
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), propertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
117
118
119
120
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
    }
    valueObject->setName(propertyName);
121
122
123
124
125
    if (type == "QColor")
        valueObject->setValue(QVariant("#000000"));
    else
        valueObject->setValue(QVariant(1));

126
127
}

128
void createPropertyEditorValue(const QmlObjectNode &fxObjectNode, const QString &name, const QVariant &value, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor)
129
130
{
    QString propertyName(name);
131
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
132
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(propertyMap->value(propertyName)));
133
134
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
135
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), propertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
136
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
137
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
138
    }
139
    valueObject->setName(name);
140
    valueObject->setModelNode(fxObjectNode);
141

142
    if (fxObjectNode.propertyAffectedByCurrentState(name) && !(fxObjectNode.modelNode().property(name).isBindingProperty())) {
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
        valueObject->setValue(fxObjectNode.modelValue(name));

    } else {
        valueObject->setValue(value);
    }

    if (propertyName != QLatin1String("id") &&
        fxObjectNode.currentState().isBaseState() &&
        fxObjectNode.modelNode().property(propertyName).isBindingProperty()) {
        valueObject->setExpression(fxObjectNode.modelNode().bindingProperty(propertyName).expression());
    } else {
        valueObject->setExpression(fxObjectNode.instanceValue(name).toString());
    }
}

158
void PropertyEditor::NodeType::setValue(const QmlObjectNode & fxObjectNode, const QString &name, const QVariant &value)
159
{
160
161
    QString propertyName = name;
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
162
    PropertyEditorValue *propertyValue = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value(propertyName)));
163
    if (propertyValue) {
164
        propertyValue->setValue(value);
165
166
        if (!fxObjectNode.hasBindingProperty(name))
            propertyValue->setExpression(value.toString());
167
168
        else
            propertyValue->setExpression(fxObjectNode.expression(name));
169
    }
170
171
}

172
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
173
{
174
    if (!fxObjectNode.isValid()) {
175
        return;
176
    }
177

178
    QDeclarativeContext *ctxt = m_view->rootContext();
179
180

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
181
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
182
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
183
184

        // className
185
        PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value("className")));
186
187
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
188
189
190
        valueObject->setName("className");
        valueObject->setModelNode(fxObjectNode.modelNode());
        valueObject->setValue(fxObjectNode.modelNode().simplifiedTypeName());
191
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
192
        m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));
193
194

        // id
195
        valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value("id")));
196
197
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
198
        valueObject->setName("id");
199
        valueObject->setValue(fxObjectNode.id());
200
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
201
        m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));
202
203
204
205
206

        // anchors
        m_backendAnchorBinding.setup(QmlItemNode(fxObjectNode.modelNode()));

        ctxt->setContextProperty("anchorBackend", &m_backendAnchorBinding);
207
        
208
        ctxt->setContextProperty("transaction", m_propertyEditorTransaction.data());
209
210
211
212
        
        m_contextObject->setSpecificsUrl(qmlSpecificsFile);
        
        m_contextObject->setStateName(stateName);
213
        QApplication::processEvents();
214
215
        if (!fxObjectNode.isValid())
            return;
216
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
217
218
219
220
221

        m_contextObject->setIsBaseState(fxObjectNode.isInBaseState());
        m_contextObject->setSelectionChanged(false);

        m_contextObject->setSelectionChanged(false);
222
223
224
225
226
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

227
228
void PropertyEditor::NodeType::initialSetup(const QString &typeName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
{
229
    QDeclarativeContext *ctxt = m_view->rootContext();
230

231
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
232

Marco Bubke's avatar
Marco Bubke committed
233
    foreach (const QString &propertyName, metaInfo.propertyNames())
234
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
235

236
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value("className")));
237
238
239
240
241
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

    valueObject->setValue(typeName);
242
    QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
243
244
245
    m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));

    // id
246
    valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value("id")));
247
248
249
250
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("id");
    valueObject->setValue("id");
251
    QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const QVariant&)));
252
253
254
    m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));

    ctxt->setContextProperty("anchorBackend", &m_backendAnchorBinding);
255
    ctxt->setContextProperty("transaction", m_propertyEditorTransaction.data());
256

257
258
259
260
261
262
263
264
265
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

    m_contextObject->setStateName(QLatin1String("basestate"));

    m_contextObject->setIsBaseState(true);

    m_contextObject->setSpecificQmlData(QLatin1String(""));

    m_contextObject->setGlobalBaseUrl(QUrl());
266
267
}

268
PropertyEditor::PropertyEditor(QWidget *parent) :
269
270
271
272
        QmlModelView(parent),
        m_parent(parent),
        m_updateShortcut(0),
        m_timerId(0),
273
        m_stackedWidget(new StackedWidget(parent)),
274
        m_currentType(0),
275
        m_locked(false),
276
277
        m_setupCompleted(false),
        m_singleShotTimer(new QTimer(this))
278
279
280
281
282
283
284
285
{
    m_updateShortcut = new QShortcut(QKeySequence("F5"), m_stackedWidget);
    connect(m_updateShortcut, SIGNAL(activated()), this, SLOT(reloadQml()));

    QFile file(":/qmldesigner/stylesheet.css");
    file.open(QFile::ReadOnly);
    QString styleSheet = QLatin1String(file.readAll());
    m_stackedWidget->setStyleSheet(styleSheet);
286
    m_stackedWidget->setMinimumWidth(300);
287
    connect(m_stackedWidget, SIGNAL(resized()), this, SLOT(updateSize()));
288
289
290

    m_stackedWidget->insertWidget(0, new QWidget(m_stackedWidget));

291
292
293
294
295
296
297
298

    static bool declarativeTypesRegistered = false;
    if (!declarativeTypesRegistered) {
        declarativeTypesRegistered = true;
        BasicWidgets::registerDeclarativeTypes();
        BasicLayouts::registerDeclarativeTypes();
        ResetWidget::registerDeclarativeType();
        QLayoutObject::registerDeclarativeType();
299
        QmlEditorWidgets::ColorWidgets::registerDeclarativeTypes();
300
301
302
303
        BehaviorDialog::registerDeclarativeType();
        QProxyLayoutItem::registerDeclarativeTypes();
        PropertyEditorValue::registerDeclarativeTypes();
        FontWidget::registerDeclarativeTypes();
Thomas Hartmann's avatar
Thomas Hartmann committed
304
        SiblingComboBox::registerDeclarativeTypes();
305
        OriginWidget::registerDeclarativeType();
306
        GradientLineQmlAdaptor::registerDeclarativeType();
307
    }
308
309
310
311
312
313
314
315
}

PropertyEditor::~PropertyEditor()
{
    delete m_stackedWidget;
    qDeleteAll(m_typeHash);
}

316
317
318
static inline QString fixTypeNameForPanes(const QString &typeName)
{
    QString fixedTypeName = typeName;
319
    fixedTypeName.replace('.', '/');
320
321
322
323
    fixedTypeName.replace("QtQuick/", "Qt/");
    return fixedTypeName;
}

324
325
326
void PropertyEditor::setupPane(const QString &typeName)
{

327
    QUrl qmlFile = fileToUrl(locateQmlFile(QLatin1String("Qt/ItemPane.qml")));
328
329
    QUrl qmlSpecificsFile;

330
    qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(typeName) + "Specifics.qml"));
331
    NodeType *type = m_typeHash.value(qmlFile.toString());
332

333
    if (!type) {
334
        type = new NodeType(this);
335

336
        QDeclarativeContext *ctxt = type->m_view->rootContext();
337
338
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
        type->initialSetup(typeName, qmlSpecificsFile, this);
339
        type->m_view->setSource(qmlFile);
340
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
341

342
343
344
        m_stackedWidget->addWidget(type->m_view);
        m_typeHash.insert(qmlFile.toString(), type);
    } else {
345
        QDeclarativeContext *ctxt = type->m_view->rootContext();
346
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
Thomas Hartmann's avatar
Thomas Hartmann committed
347

348
        type->initialSetup(typeName, qmlSpecificsFile, this);
349
350
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
351
352
}

353
void PropertyEditor::changeValue(const QString &propertyName)
354
{
355
    if (propertyName.isNull())
356
        return;
357
358
359
360

    if (m_locked)
        return;

361
    if (propertyName == "type")
362
363
        return;

364
365
366
    if (!m_selectedNode.isValid())
        return;

367
    if (propertyName == "id") {
368
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
369
370
        const QString newId = value->value().toString();

371
        if (m_selectedNode.isValidId(newId)) {
372
373
374
375
376
377
378
379
380
381
            if (m_selectedNode.id().isEmpty() || newId.isEmpty()) { //no id
                try {
                    m_selectedNode.setId(newId);
                } catch (InvalidIdException &e) { //better save then sorry
                    QMessageBox::warning(0, tr("Invalid Id"), e.description());
                }
            } else { //there is already an id, so we refactor
                if (rewriterView())
                    rewriterView()->renameId(m_selectedNode.id(), newId);
            }
382
        } else {
383
            value->setValue(m_selectedNode.id());
Friedemann Kleint's avatar
Friedemann Kleint committed
384
            QMessageBox::warning(0, tr("Invalid Id"),  tr("%1 is an invalid id").arg(newId));
385
386
387
388
        }
        return;
    }

389
390
391
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
392
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
393
394
395
396
397
398
399
400
401

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

Marco Bubke's avatar
Marco Bubke committed
402
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName)) {
403
        castedValue = fxObjectNode.modelNode().metaInfo().propertyCastedValue(propertyName, value->value());
404
    } else {
405
        qWarning() << "PropertyEditor:" <<propertyName << "cannot be casted (metainfo)";
406
407
408
409
        return ;
    }

    if (value->value().isValid() && !castedValue.isValid()) {
410
        qWarning() << "PropertyEditor:" << propertyName << "not properly casted (metainfo)";
411
412
413
        return ;
    }

Marco Bubke's avatar
Marco Bubke committed
414
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName))
415
        if (fxObjectNode.modelNode().metaInfo().propertyTypeName(propertyName) == QLatin1String("QUrl")) { //turn absolute local file paths into relative paths
416
417
418
419
420
421
422
        QString filePath = castedValue.toUrl().toString();
        if (QFileInfo(filePath).exists() && QFileInfo(filePath).isAbsolute()) {
            QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
            castedValue = QUrl(fileDir.relativeFilePath(filePath));
        }
    }

423
424
425
426
427
428
429
        if (castedValue.type() == QVariant::Color) {
            QColor color = castedValue.value<QColor>();
            QColor newColor = QColor(color.name());
            newColor.setAlpha(color.alpha());
            castedValue = QVariant(newColor);
        }

430
431
432
433
        try {
            if (!value->value().isValid()) { //reset
                fxObjectNode.removeVariantProperty(propertyName);
            } else {
434
                if (castedValue.isValid() && !castedValue.isNull()) {
435
                    m_locked = true;
436
437
438
                    fxObjectNode.setVariantProperty(propertyName, castedValue);
                    m_locked = false;
                }
439
440
441
442
            }
        }
        catch (RewritingException &e) {
            QMessageBox::warning(0, "Error", e.description());
443
444
445
446
447
        }
}

void PropertyEditor::changeExpression(const QString &name)
{
448
449
450
451
452
453
    if (name.isNull())
        return;

    if (m_locked)
        return;

454
455
    RewriterTransaction transaction = beginRewriterTransaction();

456
457
458
    QString underscoreName(name);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

459
    QmlObjectNode fxObjectNode(m_selectedNode);
460
461
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));

Marco Bubke's avatar
Marco Bubke committed
462
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(name)) {
463
        if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("QColor")) {
464
465
466
467
            if (QColor(value->expression().remove('"')).isValid()) {
                fxObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
                return;
            }
468
        } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("bool")) {
469
470
471
472
473
474
475
            if (value->expression().compare("false", Qt::CaseInsensitive) == 0 || value->expression().compare("true", Qt::CaseInsensitive) == 0) {
                if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
                    fxObjectNode.setVariantProperty(name, true);
                else
                    fxObjectNode.setVariantProperty(name, false);
                return;
            }
476
        } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("int")) {
477
478
479
480
481
482
            bool ok;
            int intValue = value->expression().toInt(&ok);
            if (ok) {
                fxObjectNode.setVariantProperty(name, intValue);
                return;
            }
483
        } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("qreal")) {
484
485
486
487
488
489
            bool ok;
            qreal realValue = value->expression().toFloat(&ok);
            if (ok) {
                fxObjectNode.setVariantProperty(name, realValue);
                return;
            }
490
        }
Kai Koehne's avatar
Kai Koehne committed
491
    }
492

493
494
495
496
497
    if (!value) {
        qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
        return;
    }

498
    try {
499
500
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
501
502
    }

503
    catch (RewritingException &e) {
504
        QMessageBox::warning(0, "Error", e.description());
505
506
507
    }
}

508
void PropertyEditor::updateSize()
509
{
510
511
512
513
514
    if (!m_currentType)
        return;
    QWidget* frame = m_currentType->m_view->findChild<QWidget*>("propertyEditorFrame");
    if (frame)
        frame->resize(m_stackedWidget->size());
515
516
}

517
518
519
void PropertyEditor::setupPanes()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
520
521
    setupPane("QtQuick.Rectangle");
    setupPane("QtQuick.Text");
522
523
524
525
526
    resetView();
    m_setupCompleted = true;
    QApplication::restoreOverrideCursor();
}

527
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
528
{
529
530
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

531
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
532
533
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
534
            if ( !m_selectedNode.hasProperty(propertyName) || m_selectedNode.property(property.name()).isBindingProperty() )
535
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
536
            else
537
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
538
539
540
541
        }
    }
}

542
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
543
{
544
545
    QmlModelView::transformChanged(fxObjectNode, propertyName);

546
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
547
548
549
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
            if ( m_selectedNode.property(property.name()).isBindingProperty() || !m_selectedNode.hasProperty(propertyName))
550
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
551
            else
552
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
553
554
555
556
        }
    }
}

557
558
559
560
561
562
563
564
565
566
567
568
void PropertyEditor::setQmlDir(const QString &qmlDir)
{
    m_qmlDir = qmlDir;

    QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
    watcher->addPath(m_qmlDir);
    connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(reloadQml()));
}

void PropertyEditor::delayedResetView()
{
    if (m_timerId == 0)
569
        m_timerId = startTimer(200);
570
571
572
573
574
575
576
577
578
}

void PropertyEditor::timerEvent(QTimerEvent *timerEvent)
{
    if (m_timerId == timerEvent->timerId()) {
        resetView();
    }
}

579
QString templateGeneration(NodeMetaInfo type, NodeMetaInfo superType, const QmlObjectNode &objectNode)
580
{
581
    QString qmlTemplate = QLatin1String("import Qt 4.7\nimport Bauhaus 1.0\n");
582
583
584
585
    qmlTemplate += QLatin1String("GroupBox {\n");
    qmlTemplate += QString(QLatin1String("caption: \"%1\"\n")).arg(type.typeName());
    qmlTemplate += QLatin1String("layout: VerticalLayout {\n");

586
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
587
    orderedList = type.propertyNames();
588
589
590
    qSort(orderedList);

    foreach (const QString &name, orderedList) {
591
        QString properName = name;
592
        properName.replace('.', '_');
593

594
        QString typeName = type.propertyTypeName(name);
595
596
597
598
        //alias resolution only possible with instance
            if (typeName == QLatin1String("alias") && objectNode.isValid())
                typeName = objectNode.instanceType(name);

599
        if (!superType.hasProperty(name) && type.propertyIsWritable(name)) {
600
            if (typeName == "int") {
601
                qmlTemplate +=  QString(QLatin1String(
602
603
                "IntEditor { backendValue: backendValues.%2\n caption: \"%1\"\nbaseStateFlag: isBaseState\nslider: false\n}"
                )).arg(name).arg(properName);
604
            }
605
            if (typeName == "real" || typeName == "double" || typeName == "qreal") {
606
                qmlTemplate +=  QString(QLatin1String(
607
608
                "DoubleSpinBoxAlternate {\ntext: \"%1\"\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\n}\n"
                )).arg(name).arg(properName);
609
            }
610
            if (typeName == "string" || typeName == "QString" || typeName == "QUrl" || typeName == "url") {
611
                 qmlTemplate +=  QString(QLatin1String(
612
613
                "QWidget {\nlayout: HorizontalLayout {\nLabel {\ntext: \"%1\"\ntoolTip: \"%1\"\n}\nLineEdit {\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\n}\n}\n}\n"
                )).arg(name).arg(properName);
614
            }
615
            if (typeName == "bool") {
616
                 qmlTemplate +=  QString(QLatin1String(
617
618
                 "QWidget {\nlayout: HorizontalLayout {\nLabel {\ntext: \"%1\"\ntoolTip: \"%1\"\n}\nCheckBox {text: backendValues.%2.value\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\ncheckable: true\n}\n}\n}\n"
                 )).arg(name).arg(properName);
619
            }
620
            if (typeName == "color" || typeName == "QColor") {
621
                qmlTemplate +=  QString(QLatin1String(
622
623
                "ColorGroupBox {\ncaption: \"%1\"\nfinished: finishedNotify\nbackendColor: backendValues.%2\n}\n\n"
                )).arg(name).arg(properName);
624
            }
625
626
627
628
629
630
631
632
        }
    }
    qmlTemplate += QLatin1String("}\n"); //VerticalLayout
    qmlTemplate += QLatin1String("}\n"); //GroupBox

    return qmlTemplate;
}

633
634
635
636
637
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
638
639
    m_locked = true;

640
641
642
643
644
645
646
647
648
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

    if (m_selectedNode.isValid() && model() != m_selectedNode.model())
        m_selectedNode = ModelNode();

649
650
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
651
652
    QUrl qmlSpecificsFile;
    if (m_selectedNode.isValid())
653
        qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(m_selectedNode.type()) + "Specifics.qml"));
654

655
656
    QString specificQmlData;

657
    if (m_selectedNode.isValid() && !QFileInfo(qmlSpecificsFile.toLocalFile()).exists() && m_selectedNode.metaInfo().isValid()) {
658
        //do magic !!
659
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(specificsClassName), m_selectedNode);
660
661
    }

662
    NodeType *type = m_typeHash.value(qmlFile.toString());
663

664
    if (!type) {
665
        type = new NodeType(this);
666
667

        m_stackedWidget->addWidget(type->m_view);
668
        m_typeHash.insert(qmlFile.toString(), type);
669
670
671
672
673
674

        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
            Q_ASSERT(fxObjectNode.isValid());
        }
675
        QDeclarativeContext *ctxt = type->m_view->rootContext();
676
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
677
        ctxt->setContextProperty("finishedNotify", QVariant(false));
678
        if (specificQmlData.isEmpty())
679
680
681
682
            type->m_contextObject->setSpecificQmlData(specificQmlData);
            
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
683
        type->m_view->setSource(qmlFile);
684
685
686
687
688
689
        ctxt->setContextProperty("finishedNotify", QVariant(true));
    } else {
        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
        }
Thomas Hartmann's avatar
Thomas Hartmann committed
690
        QDeclarativeContext *ctxt = type->m_view->rootContext();
691
        
692
        ctxt->setContextProperty("finishedNotify", QVariant(false));
693
        if (specificQmlData.isEmpty())
694
695
            type->m_contextObject->setSpecificQmlData(specificQmlData);
        
696
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
697
698
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
699
700
701
    }

    m_stackedWidget->setCurrentWidget(type->m_view);
702

703

Thomas Hartmann's avatar
Thomas Hartmann committed
704
    QDeclarativeContext *ctxt = type->m_view->rootContext();
705
    ctxt->setContextProperty("finishedNotify", QVariant(true));
706
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
707
    ctxt->setContextProperty("selectionChanged", QVariant(true));
708
709
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
710

711
712
    m_currentType = type;

713
714
    m_locked = false;

715
716
    if (m_timerId)
        m_timerId = 0;
717
718

    updateSize();
719
720
721
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
722
                                          const QList<ModelNode> &lastSelectedNodeList)
723
724
725
{
    Q_UNUSED(lastSelectedNodeList);

726
    if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
727
        select(ModelNode());
728
    else if (m_selectedNode != selectedNodeList.first())
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
        select(selectedNodeList.first());
}

void PropertyEditor::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
    QmlModelView::nodeAboutToBeRemoved(removedNode);
    if (m_selectedNode.isValid() && removedNode.isValid() && m_selectedNode == removedNode)
        select(m_selectedNode.parentProperty().parentModelNode());
}

void PropertyEditor::modelAttached(Model *model)
{
    QmlModelView::modelAttached(model);

    if (debug)
        qDebug() << Q_FUNC_INFO;

Thomas Hartmann's avatar
Thomas Hartmann committed
746
747
    m_locked = true;

748
    resetView();
749
750
751
752
753
754
    if (!m_setupCompleted) {
        m_singleShotTimer->setSingleShot(true);
        m_singleShotTimer->setInterval(1000);
        connect(m_singleShotTimer, SIGNAL(timeout()), this, SLOT(setupPanes()));
        m_singleShotTimer->start();
    }
Thomas Hartmann's avatar
Thomas Hartmann committed
755
756

    m_locked = false;
757
758
759
760
761
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
762
    m_currentType->m_propertyEditorTransaction->end();
763
764
765
766
767

    resetView();
}


768
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
769
{
770
    QmlModelView::propertiesRemoved(propertyList);
771
772
773
774
775

    if (!m_selectedNode.isValid())
        return;

    foreach (const AbstractProperty &property, propertyList) {
776
777
778
        ModelNode node(property.parentModelNode());
        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
779
780
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
781
782
783
784
785
786
787
        }
    }
}


void PropertyEditor::variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange)
{
788

789
790
791
792
793
794
795
796
797
798
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

    foreach (const VariantProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
799
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
800
            else
801
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
        }
    }
}

void PropertyEditor::bindingPropertiesChanged(const QList<BindingProperty>& propertyList, PropertyChangeFlags propertyChange)
{
    QmlModelView::bindingPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
817
818
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
819
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
820
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
821
            else
822
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
823
824
825
826
        }
    }
}

827
828
829
830
831
832
833
834
835
836

void PropertyEditor::instanceInformationsChange(const QVector<ModelNode> &nodeList)
{
    if (!m_selectedNode.isValid())
        return;

    if (nodeList.contains(m_selectedNode))
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
}

837
838
839
840
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

841
    if (!m_selectedNode.isValid())
842
843
        return;

844
    if (node == m_selectedNode) {
845

846
        if (m_currentType) {
847
            setValue(node, "id", newId);
848
849
        }
    }
850
851
}

Marco Bubke's avatar
Marco Bubke committed
852
853
854
855
856
void PropertyEditor::scriptFunctionsChanged(const ModelNode &node, const QStringList &scriptFunctionList)
{
    QmlModelView::scriptFunctionsChanged(node, scriptFunctionList);
}

857
858
void PropertyEditor::select(const ModelNode &node)
{
859
    if (QmlItemNode(node).isValid())
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
        m_selectedNode = node;
    else
        m_selectedNode = ModelNode();

    delayedResetView();
}

QWidget *PropertyEditor::createPropertiesPage()
{
    delayedResetView();
    return m_stackedWidget;
}

void PropertyEditor::stateChanged(const QmlModelState &newQmlModelState, const QmlModelState &oldQmlModelState)
{
    QmlModelView::stateChanged(newQmlModelState, oldQmlModelState);
    Q_ASSERT(newQmlModelState.isValid());
    if (debug)
        qDebug() << Q_FUNC_INFO << newQmlModelState.name();
    delayedResetView();
}

882
883
884
885
886
887
888
void PropertyEditor::setValue(const QmlObjectNode &fxObjectNode, const QString &name, const QVariant &value)
{
    m_locked = true;
    m_currentType->setValue(fxObjectNode, name, value);
    m_locked = false;
}

889
890
891
892
893
894
895
896
897
898
899
900
901
902
void PropertyEditor::reloadQml()
{
    m_typeHash.clear();
    while (QWidget *widget = m_stackedWidget->widget(0)) {
        m_stackedWidget->removeWidget(widget);
        delete widget;
    }
    m_currentType = 0;

    delayedResetView();
}

QString PropertyEditor::qmlFileName(const NodeMetaInfo &nodeInfo) const
{
903
    if (nodeInfo.typeName().split('.').last() == "QDeclarativeItem")
904
        return "Qt/ItemPane.qml";
905
    const QString fixedTypeName = fixTypeNameForPanes(nodeInfo.typeName());
906
    return fixedTypeName + QLatin1String("Pane.qml");
907
908
909
910
911
912
913
914
}

QUrl PropertyEditor::fileToUrl(const QString &filePath) const {
    QUrl fileUrl;

    if (filePath.isEmpty())
        return fileUrl;

915
    if (filePath.startsWith(QLatin1Char(':'))) {
916
917
918
919
920
921
922
923
924
925
926
        fileUrl.setScheme("qrc");
        QString path = filePath;
        path.remove(0, 1); // remove trailing ':'
        fileUrl.setPath(path);
    } else {
        fileUrl = QUrl::fromLocalFile(filePath);
    }

    return fileUrl;
}

927
QUrl PropertyEditor::qmlForNode(const ModelNode &modelNode, QString &className) const
928
929
930
931
932
933
934
935
{
    if (modelNode.isValid()) {
        QList<NodeMetaInfo> hierarchy;
        hierarchy << modelNode.metaInfo();
        hierarchy << modelNode.metaInfo().superClasses();

        foreach (const NodeMetaInfo &info, hierarchy) {
            QUrl fileUrl = fileToUrl(locateQmlFile(qmlFileName(info)));
936
937
            if (fileUrl.isValid()) {
                className = info.typeName();
938
                return fileUrl;
939
            }
940
941
        }
    }
942
    return fileToUrl(QDir(m_qmlDir).filePath("Qt/emptyPane.qml"));
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
}

QString PropertyEditor::locateQmlFile(const QString &relativePath) const
{
    QDir fileSystemDir(m_qmlDir);
    static QDir resourcesDir(":/propertyeditor");

    if (fileSystemDir.exists(relativePath))
        return fileSystemDir.absoluteFilePath(relativePath);
    if (resourcesDir.exists(relativePath))
        return resourcesDir.absoluteFilePath(relativePath);

    return QString();
}

958

959
960
} //QmlDesigner