propertyeditor.cpp 39.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
hjk's avatar
hjk committed
3
4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29
30
31
32

#include "propertyeditor.h"

#include <nodemetainfo.h>
33
#include <metainfo.h>
34
35

#include <invalididexception.h>
36
#include <rewritingexception.h>
37
38
#include <variantproperty.h>

39
40
41
#include <bindingproperty.h>

#include <nodeabstractproperty.h>
42
#include <rewriterview.h>
43

44
#include "propertyeditorvalue.h"
45
46
47
48
#include "basiclayouts.h"
#include "basicwidgets.h"
#include "resetwidget.h"
#include "qlayoutobject.h"
49
50
#include <qmleditorwidgets/colorwidgets.h>
#include "gradientlineqmladaptor.h"
51
52
53
#include "behaviordialog.h"
#include "qproxylayoutitem.h"
#include "fontwidget.h"
Thomas Hartmann's avatar
Thomas Hartmann committed
54
#include "siblingcombobox.h"
55
#include "propertyeditortransaction.h"
56
#include "originwidget.h"
57

58
#include <qmljs/qmljssimplereader.h>
59
60
#include <utils/fileutils.h>

61
62
63
64
65
66
67
68
69
70
71
#include <QCoreApplication>
#include <QDir>
#include <QFileSystemWatcher>
#include <QFileInfo>
#include <QDebug>
#include <QTimer>
#include <QDeclarativeView>
#include <QDeclarativeContext>
#include <QVBoxLayout>
#include <QShortcut>
#include <QStackedWidget>
72
#include <QDeclarativeEngine>
73
#include <QMessageBox>
74
#include <QApplication>
75
76
#include <QGraphicsOpacityEffect>
#include <QToolBar>
77

78
79
80
81
#ifdef Q_OS_WIN
#include <utils/winutils.h>
#endif

82
83
84
85
enum {
    debug = false
};

86
87
const int collapseButtonOffset = 114;

88
89
namespace QmlDesigner {

90
91
92
93
94
95
96
97
98
99
static QString applicationDirPath()
{
#ifdef Q_OS_WIN
    // normalize paths so QML doesn't freak out if it's wrongly capitalized on Windows
    return Utils::normalizePathName(QCoreApplication::applicationDirPath());
#else
    return QCoreApplication::applicationDirPath();
#endif
}

100
101
102
103
104
105
106
107
#ifdef Q_OS_MAC
#  define SHARE_PATH "/../Resources/qmldesigner"
#else
#  define SHARE_PATH "/../share/qtcreator/qmldesigner"
#endif

static inline QString sharedDirPath()
{
108
    QString appPath = applicationDirPath();
109
110
111
112

    return QFileInfo(appPath + SHARE_PATH).absoluteFilePath();
}

113
114
115
116
117
static inline QString propertyTemplatesPath()
{
    return sharedDirPath() + QLatin1String("/propertyeditor/PropertyTemplates/");
}

118
119
120
121
122
123
124
static QObject *variantToQObject(const QVariant &v)
{
    if (v.userType() == QMetaType::QObjectStar || v.userType() > QMetaType::User)
        return *(QObject **)v.constData();

    return 0;
}
125

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
static QmlJS::SimpleReaderNode::Ptr s_templateConfiguration;

QmlJS::SimpleReaderNode::Ptr templateConfiguration()
{
    if (!s_templateConfiguration) {
        QmlJS::SimpleReader reader;
        const QString fileName = propertyTemplatesPath() + QLatin1String("TemplateTypes.qml");
        s_templateConfiguration = reader.readFile(fileName);

        if (!s_templateConfiguration) {
            qWarning() << PropertyEditor::tr("template defitions:") << reader.errors();
        }
    }

    return s_templateConfiguration;
}

QStringList variantToStringList(const QVariant &variant) {
    QStringList stringList;

    foreach (const QVariant &singleValue, variant.toList())
        stringList << singleValue.toString();

    return stringList;
}

152
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
153
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
154
        m_contextObject(new PropertyEditorContextObject())
155
156
157
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

158
    QDeclarativeContext *ctxt = m_view->rootContext();
159
    m_view->engine()->setOutputWarningsToStandardError(debug);
160
    m_view->engine()->addImportPath(sharedDirPath() + QLatin1String("/propertyeditor"));
161
162
    m_dummyPropertyEditorValue->setValue("#000000");
    ctxt->setContextProperty("dummyBackendValue", m_dummyPropertyEditorValue.data());
163
164
    m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
    ctxt->setContextObject(m_contextObject.data());
165

Robert Loehning's avatar
Robert Loehning committed
166
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)), propertyEditor, SLOT(changeValue(QString)));
167
168
169
170
171
172
}

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

173
void setupPropertyEditorValue(const QString &name, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor, const QString &type)
174
175
176
{
    QString propertyName(name);
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
177
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(propertyMap->value(propertyName)));
178
179
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
Robert Loehning's avatar
Robert Loehning committed
180
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), propertyMap, SIGNAL(valueChanged(QString,QVariant)));
181
182
183
184
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
    }
    valueObject->setName(propertyName);
185
186
187
188
189
    if (type == "QColor")
        valueObject->setValue(QVariant("#000000"));
    else
        valueObject->setValue(QVariant(1));

190
191
}

192
void createPropertyEditorValue(const QmlObjectNode &fxObjectNode, const QString &name, const QVariant &value, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor)
193
194
{
    QString propertyName(name);
195
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
196
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(propertyMap->value(propertyName)));
197
198
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
Robert Loehning's avatar
Robert Loehning committed
199
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), propertyMap, SIGNAL(valueChanged(QString,QVariant)));
200
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
201
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
202
    }
203
    valueObject->setName(name);
204
    valueObject->setModelNode(fxObjectNode);
205

206
    if (fxObjectNode.propertyAffectedByCurrentState(name) && !(fxObjectNode.modelNode().property(name).isBindingProperty())) {
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        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());
    }
}

222
void PropertyEditor::NodeType::setValue(const QmlObjectNode & fxObjectNode, const QString &name, const QVariant &value)
223
{
224
225
    QString propertyName = name;
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
226
    PropertyEditorValue *propertyValue = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(propertyName)));
227
    if (propertyValue) {
228
        propertyValue->setValue(value);
229
230
        if (!fxObjectNode.hasBindingProperty(name))
            propertyValue->setExpression(value.toString());
231
232
        else
            propertyValue->setExpression(fxObjectNode.expression(name));
233
    }
234
235
}

236
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
237
{
238
    if (!fxObjectNode.isValid()) {
239
        return;
240
    }
241

242
    QDeclarativeContext *ctxt = m_view->rootContext();
243
244

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
245
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
246
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
247
248

        // className
249
        PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
250
251
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
252
253
254
        valueObject->setName("className");
        valueObject->setModelNode(fxObjectNode.modelNode());
        valueObject->setValue(fxObjectNode.modelNode().simplifiedTypeName());
Robert Loehning's avatar
Robert Loehning committed
255
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
256
        m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));
257
258

        // id
259
        valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("id")));
260
261
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
262
        valueObject->setName("id");
263
        valueObject->setValue(fxObjectNode.id());
Robert Loehning's avatar
Robert Loehning committed
264
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
265
        m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));
266
267
268
269
270

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

        ctxt->setContextProperty("anchorBackend", &m_backendAnchorBinding);
Orgad Shaneh's avatar
Orgad Shaneh committed
271

272
        ctxt->setContextProperty("transaction", m_propertyEditorTransaction.data());
Orgad Shaneh's avatar
Orgad Shaneh committed
273

274
        m_contextObject->setSpecificsUrl(qmlSpecificsFile);
Orgad Shaneh's avatar
Orgad Shaneh committed
275

276
        m_contextObject->setStateName(stateName);
277
278
        if (!fxObjectNode.isValid())
            return;
279
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
280
281
282
283
284

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

        m_contextObject->setSelectionChanged(false);
285
286
287
288
289
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

290
291
void PropertyEditor::NodeType::initialSetup(const QString &typeName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
{
292
    QDeclarativeContext *ctxt = m_view->rootContext();
293

294
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
295

Marco Bubke's avatar
Marco Bubke committed
296
    foreach (const QString &propertyName, metaInfo.propertyNames())
297
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
298

299
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
300
301
302
303
304
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

    valueObject->setValue(typeName);
Robert Loehning's avatar
Robert Loehning committed
305
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
306
307
308
    m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));

    // id
309
    valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("id")));
310
311
312
313
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("id");
    valueObject->setValue("id");
Robert Loehning's avatar
Robert Loehning committed
314
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
315
316
317
    m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));

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

320
321
322
323
324
325
326
327
328
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

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

    m_contextObject->setIsBaseState(true);

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

    m_contextObject->setGlobalBaseUrl(QUrl());
329
330
}

331
PropertyEditor::PropertyEditor(QWidget *parent) :
332
333
334
335
        QmlModelView(parent),
        m_parent(parent),
        m_updateShortcut(0),
        m_timerId(0),
336
        m_stackedWidget(new StackedWidget(parent)),
337
        m_currentType(0),
338
        m_locked(false),
339
340
        m_setupCompleted(false),
        m_singleShotTimer(new QTimer(this))
341
342
343
344
{
    m_updateShortcut = new QShortcut(QKeySequence("F5"), m_stackedWidget);
    connect(m_updateShortcut, SIGNAL(activated()), this, SLOT(reloadQml()));

345
346
    m_stackedWidget->setStyleSheet(
            QLatin1String(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")));
347
    m_stackedWidget->setMinimumWidth(320);
348
    m_stackedWidget->move(0, 0);
349
    connect(m_stackedWidget, SIGNAL(resized()), this, SLOT(updateSize()));
350
351
352

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

353
354
355
356
357
358
359
360

    static bool declarativeTypesRegistered = false;
    if (!declarativeTypesRegistered) {
        declarativeTypesRegistered = true;
        BasicWidgets::registerDeclarativeTypes();
        BasicLayouts::registerDeclarativeTypes();
        ResetWidget::registerDeclarativeType();
        QLayoutObject::registerDeclarativeType();
361
        QmlEditorWidgets::ColorWidgets::registerDeclarativeTypes();
362
363
364
365
        BehaviorDialog::registerDeclarativeType();
        QProxyLayoutItem::registerDeclarativeTypes();
        PropertyEditorValue::registerDeclarativeTypes();
        FontWidget::registerDeclarativeTypes();
Thomas Hartmann's avatar
Thomas Hartmann committed
366
        SiblingComboBox::registerDeclarativeTypes();
367
        OriginWidget::registerDeclarativeType();
368
        GradientLineQmlAdaptor::registerDeclarativeType();
369
    }
370
371
    setQmlDir(sharedDirPath() + QLatin1String("/propertyeditor"));
    m_stackedWidget->setWindowTitle(tr("Properties"));
372
373
374
375
376
377
378
}

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

379
380
381
static inline QString fixTypeNameForPanes(const QString &typeName)
{
    QString fixedTypeName = typeName;
382
    fixedTypeName.replace('.', '/');
383
384
385
    return fixedTypeName;
}

386
387
388
void PropertyEditor::setupPane(const QString &typeName)
{

389
    QUrl qmlFile = fileToUrl(locateQmlFile(QLatin1String("Qt/ItemPane.qml")));
390
391
    QUrl qmlSpecificsFile;

392
    qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(typeName) + "Specifics.qml"));
393
    NodeType *type = m_typeHash.value(qmlFile.toString());
394

395
    if (!type) {
396
        type = new NodeType(this);
397

398
        QDeclarativeContext *ctxt = type->m_view->rootContext();
399
400
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
        type->initialSetup(typeName, qmlSpecificsFile, this);
401
        type->m_view->setSource(qmlFile);
402
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
403

404
405
406
        m_stackedWidget->addWidget(type->m_view);
        m_typeHash.insert(qmlFile.toString(), type);
    } else {
407
        QDeclarativeContext *ctxt = type->m_view->rootContext();
408
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
Thomas Hartmann's avatar
Thomas Hartmann committed
409

410
        type->initialSetup(typeName, qmlSpecificsFile, this);
411
412
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
413
414
}

415
void PropertyEditor::changeValue(const QString &propertyName)
416
{
417
    if (propertyName.isNull())
418
        return;
419
420
421
422

    if (m_locked)
        return;

423
    if (propertyName == "type")
424
425
        return;

426
427
428
    if (!m_selectedNode.isValid())
        return;

429
    if (propertyName == "id") {
430
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
431
432
        const QString newId = value->value().toString();

433
434
435
436
        if (newId == m_selectedNode.id())
            return;

        if (m_selectedNode.isValidId(newId)  && !modelNodeForId(newId).isValid() ) {
437
438
439
440
            if (m_selectedNode.id().isEmpty() || newId.isEmpty()) { //no id
                try {
                    m_selectedNode.setId(newId);
                } catch (InvalidIdException &e) { //better save then sorry
441
                    m_locked = true;
442
                    value->setValue(m_selectedNode.id());
443
                    m_locked = false;
444
445
446
447
448
449
                    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);
            }
450
        } else {
451
            m_locked = true;
452
            value->setValue(m_selectedNode.id());
453
454
455
456
457
            m_locked = false;
            if (!m_selectedNode.isValidId(newId))
                QMessageBox::warning(0, tr("Invalid Id"),  tr("%1 is an invalid id").arg(newId));
            else
                QMessageBox::warning(0, tr("Invalid Id"),  tr("%1 already exists").arg(newId));
458
459
460
461
        }
        return;
    }

462
463
464
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
465
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
466
467
468
469
470
471
472
473
474

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

Marco Bubke's avatar
Marco Bubke committed
475
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName)) {
476
        castedValue = fxObjectNode.modelNode().metaInfo().propertyCastedValue(propertyName, value->value());
477
    } else {
478
        qWarning() << "PropertyEditor:" <<propertyName << "cannot be casted (metainfo)";
479
480
481
482
        return ;
    }

    if (value->value().isValid() && !castedValue.isValid()) {
483
        qWarning() << "PropertyEditor:" << propertyName << "not properly casted (metainfo)";
484
485
486
        return ;
    }

Marco Bubke's avatar
Marco Bubke committed
487
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName))
488
489
490
        if (fxObjectNode.modelNode().metaInfo().propertyTypeName(propertyName) == QLatin1String("QUrl")
                || fxObjectNode.modelNode().metaInfo().propertyTypeName(propertyName) == QLatin1String("url")) { //turn absolute local file paths into relative paths
            QString filePath = castedValue.toUrl().toString();
491
492
493
494
495
496
        if (QFileInfo(filePath).exists() && QFileInfo(filePath).isAbsolute()) {
            QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
            castedValue = QUrl(fileDir.relativeFilePath(filePath));
        }
    }

497
498
499
500
501
502
503
        if (castedValue.type() == QVariant::Color) {
            QColor color = castedValue.value<QColor>();
            QColor newColor = QColor(color.name());
            newColor.setAlpha(color.alpha());
            castedValue = QVariant(newColor);
        }

504
505
506
507
        try {
            if (!value->value().isValid()) { //reset
                fxObjectNode.removeVariantProperty(propertyName);
            } else {
508
                if (castedValue.isValid() && !castedValue.isNull()) {
509
                    m_locked = true;
510
511
512
                    fxObjectNode.setVariantProperty(propertyName, castedValue);
                    m_locked = false;
                }
513
514
515
516
            }
        }
        catch (RewritingException &e) {
            QMessageBox::warning(0, "Error", e.description());
517
518
519
520
521
        }
}

void PropertyEditor::changeExpression(const QString &name)
{
522
523
524
525
526
527
    if (name.isNull())
        return;

    if (m_locked)
        return;

528
529
    RewriterTransaction transaction = beginRewriterTransaction();

530
531
532
533
534
    try {
        QString underscoreName(name);
        underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

        QmlObjectNode fxObjectNode(m_selectedNode);
535
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568

        if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(name)) {
            if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("QColor")) {
                if (QColor(value->expression().remove('"')).isValid()) {
                    fxObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
                    transaction.commit(); //committing in the try block
                    return;
                }
            } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("bool")) {
                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);
                    transaction.commit(); //committing in the try block
                    return;
                }
            } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("int")) {
                bool ok;
                int intValue = value->expression().toInt(&ok);
                if (ok) {
                    fxObjectNode.setVariantProperty(name, intValue);
                    transaction.commit(); //committing in the try block
                    return;
                }
            } else if (fxObjectNode.modelNode().metaInfo().propertyTypeName(name) == QLatin1String("qreal")) {
                bool ok;
                qreal realValue = value->expression().toFloat(&ok);
                if (ok) {
                    fxObjectNode.setVariantProperty(name, realValue);
                    transaction.commit(); //committing in the try block
                    return;
                }
569
            }
570
571
        }

572
573
574
575
        if (!value) {
            qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
            return;
        }
576

577
578
579
        if (value->expression().isEmpty())
            return;

580
581
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
582
583

        transaction.commit(); //committing in the try block
584
585
    }

586
    catch (RewritingException &e) {
587
        QMessageBox::warning(0, "Error", e.description());
588
589
590
    }
}

591
void PropertyEditor::updateSize()
592
{
593
594
595
596
597
    if (!m_currentType)
        return;
    QWidget* frame = m_currentType->m_view->findChild<QWidget*>("propertyEditorFrame");
    if (frame)
        frame->resize(m_stackedWidget->size());
598
599
}

600
601
602
void PropertyEditor::setupPanes()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
603
604
    setupPane("QtQuick.Rectangle");
    setupPane("QtQuick.Text");
605
606
607
608
609
    resetView();
    m_setupCompleted = true;
    QApplication::restoreOverrideCursor();
}

610
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
611
{
612
613
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

614
615
616
    if (!m_selectedNode.isValid())
        return;

617
618
    m_locked = true;

619
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
620
621
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
622
            if ( !m_selectedNode.hasProperty(propertyName) || m_selectedNode.property(property.name()).isBindingProperty() )
623
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
624
            else
625
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
626
627
        }
    }
628
629

    m_locked = false;
630
631
}

632
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
633
{
634
635
    QmlModelView::transformChanged(fxObjectNode, propertyName);

636
637
638
    if (!m_selectedNode.isValid())
        return;

639
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
640
641
642
        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))
643
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
644
            else
645
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
646
647
648
649
        }
    }
}

650
651
652
653
void PropertyEditor::setQmlDir(const QString &qmlDir)
{
    m_qmlDir = qmlDir;

654

655
656
657
658
659
660
661
662
    QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
    watcher->addPath(m_qmlDir);
    connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(reloadQml()));
}

void PropertyEditor::delayedResetView()
{
    if (m_timerId == 0)
663
        m_timerId = startTimer(100);
664
665
666
667
668
669
670
671
672
}

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

673
QString templateGeneration(NodeMetaInfo type, NodeMetaInfo superType, const QmlObjectNode &objectNode)
674
{
675
676
677
678
679
680
    if (!templateConfiguration() && templateConfiguration()->isValid())
        return QString();

    QStringList imports = variantToStringList(templateConfiguration()->property(QLatin1String("imports")));

    QString qmlTemplate = imports.join(QLatin1String("\n")) + QLatin1Char('\n');
681
    qmlTemplate += QLatin1String("GroupBox {\n");
682
    qmlTemplate += QString(QLatin1String("caption: \"%1\"\n")).arg(objectNode.modelNode().simplifiedTypeName());
683
684
    qmlTemplate += QLatin1String("layout: VerticalLayout {\n");

685
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
686
    orderedList = type.propertyNames();
687
688
    qSort(orderedList);

689
690
    bool emptyTemplate = true;

691
    foreach (const QString &name, orderedList) {
692
693
694

        if (name.startsWith(QLatin1String("__")))
            continue; //private API
695
        QString properName = name;
696

697
        properName.replace('.', '_');
698

699
        QString typeName = type.propertyTypeName(name);
700
        //alias resolution only possible with instance
701
702
        if (typeName == QLatin1String("alias") && objectNode.isValid())
            typeName = objectNode.instanceType(name);
703

704
        if (!superType.hasProperty(name) && type.propertyIsWritable(name) && !name.contains(QLatin1String("."))) {
705
706
707
708
709
710
711
712
713
714
715
716
717
            foreach (const QmlJS::SimpleReaderNode::Ptr &node, templateConfiguration()->children())
                if (variantToStringList(node->property(QLatin1String("typeNames"))).contains(typeName)) {
                    const QString fileName = propertyTemplatesPath() + node->property(QLatin1String("sourceFile")).toString();
                    QFile file(fileName);
                    if (file.open(QIODevice::ReadOnly)) {
                        QString source = file.readAll();
                        file.close();
                        qmlTemplate += source.arg(name).arg(properName);
                        emptyTemplate = false;
                    } else {
                        qWarning() << PropertyEditor::tr("template defition source file not found:") << fileName;
                    }
                }
718
719
720
721
722
        }
    }
    qmlTemplate += QLatin1String("}\n"); //VerticalLayout
    qmlTemplate += QLatin1String("}\n"); //GroupBox

723
724
725
    if (emptyTemplate)
        return QString();

726
727
728
    return qmlTemplate;
}

729
730
731
732
733
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
734
735
    m_locked = true;

736
737
738
739
740
741
742
743
744
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

745
746
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
747
    QUrl qmlSpecificsFile;
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765

    QString diffClassName;
    if (m_selectedNode.isValid()) {
        diffClassName = m_selectedNode.metaInfo().typeName();
        QList<NodeMetaInfo> hierarchy;
        hierarchy << m_selectedNode.metaInfo();
        hierarchy << m_selectedNode.metaInfo().superClasses();

        foreach (const NodeMetaInfo &info, hierarchy) {
            if (QFileInfo(qmlSpecificsFile.toLocalFile()).exists())
                break;
            qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(info.typeName()) + "Specifics.qml"));
            diffClassName = info.typeName();
        }
    }

    if (!QFileInfo(qmlSpecificsFile.toLocalFile()).exists())
        diffClassName = specificsClassName;
766

767
768
    QString specificQmlData;

769
    if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) {
770
        //do magic !!
771
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode);
772
773
    }

774
    NodeType *type = m_typeHash.value(qmlFile.toString());
775

776
    if (!type) {
777
        type = new NodeType(this);
778
779

        m_stackedWidget->addWidget(type->m_view);
780
        m_typeHash.insert(qmlFile.toString(), type);
781
782
783
784
785
786

        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
            Q_ASSERT(fxObjectNode.isValid());
        }
787
        QDeclarativeContext *ctxt = type->m_view->rootContext();
788
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
789
        ctxt->setContextProperty("finishedNotify", QVariant(false));
790
        if (specificQmlData.isEmpty())
791
            type->m_contextObject->setSpecificQmlData(specificQmlData);
Orgad Shaneh's avatar
Orgad Shaneh committed
792

793
794
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
795
        type->m_view->setSource(qmlFile);
796
797
798
799
800
801
        ctxt->setContextProperty("finishedNotify", QVariant(true));
    } else {
        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
        }
Thomas Hartmann's avatar
Thomas Hartmann committed
802
        QDeclarativeContext *ctxt = type->m_view->rootContext();
Orgad Shaneh's avatar
Orgad Shaneh committed
803

804
        ctxt->setContextProperty("finishedNotify", QVariant(false));
805
        if (specificQmlData.isEmpty())
806
            type->m_contextObject->setSpecificQmlData(specificQmlData);
807
808
        QString currentStateName = currentState().isValid() ? currentState().name() : QLatin1String("invalid state");
        type->setup(fxObjectNode, currentStateName, qmlSpecificsFile, this);
809
810
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
811
812
813
    }

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

815

Thomas Hartmann's avatar
Thomas Hartmann committed
816
    QDeclarativeContext *ctxt = type->m_view->rootContext();
817
    ctxt->setContextProperty("finishedNotify", QVariant(true));
818
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
819
    ctxt->setContextProperty("selectionChanged", QVariant(true));
820
821
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
822

823
824
    m_currentType = type;

825
826
    m_locked = false;

827
828
    if (m_timerId)
        m_timerId = 0;
829
830

    updateSize();
831
832
833
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
834
                                          const QList<ModelNode> &lastSelectedNodeList)
835
836
837
{
    Q_UNUSED(lastSelectedNodeList);

838
    if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
839
        select(ModelNode());
840
    else if (m_selectedNode != selectedNodeList.first())
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
        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
858
859
    m_locked = true;

860
    resetView();
861
862
    if (!m_setupCompleted) {
        m_singleShotTimer->setSingleShot(true);
Thomas Hartmann's avatar
Thomas Hartmann committed
863
        m_singleShotTimer->setInterval(100);
864
865
866
        connect(m_singleShotTimer, SIGNAL(timeout()), this, SLOT(setupPanes()));
        m_singleShotTimer->start();
    }
Thomas Hartmann's avatar
Thomas Hartmann committed
867
868

    m_locked = false;
869
870
871
872
873
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
874
    m_currentType->m_propertyEditorTransaction->end();
875
876
877
878
879

    resetView();
}


880
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
881
{
882
    QmlModelView::propertiesRemoved(propertyList);
883
884
885
886

    if (!m_selectedNode.isValid())
        return;

887
888
889
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

890
    foreach (const AbstractProperty &property, propertyList) {
891
892
893
        ModelNode node(property.parentModelNode());
        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
894
895
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
896
897
898
899
900
901
902
        }
    }
}


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

904
905
906
907
908
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

909
910
911
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

912
913
914
915
916
    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())
917
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
918
            else
919
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
920
921
922
923
924
925
926
927
928
929
930
        }
    }
}

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

    if (!m_selectedNode.isValid())
        return;

931
932
933
       if (!QmlObjectNode(m_selectedNode).isValid())
        return;

934
935
936
937
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
938
939
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
940
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
941
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
942
            else
943
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
944
945
946
947
        }
    }
}

948

949
void PropertyEditor::instanceInformationsChange(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
950
951
952
953
{
    if (!m_selectedNode.isValid())
        return;

954
    m_locked = true;
955
956
    QList<InformationName> informationNameList = informationChangeHash.values(m_selectedNode);
    if (informationNameList.contains(Anchor))
957
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
958
    m_locked = false;
959
960
}

961
962
963
964
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

965
    if (!m_selectedNode.isValid())
966
967
        return;

968
969
970
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

971
    if (node == m_selectedNode) {
972