propertyeditor.cpp 39.9 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

#include "propertyeditor.h"

32
33
#include <qmldesignerconstants.h>

34
#include <nodemetainfo.h>
35
#include <metainfo.h>
36
37

#include <invalididexception.h>
38
#include <rewritingexception.h>
39
40
#include <variantproperty.h>

41
42
43
#include <bindingproperty.h>

#include <nodeabstractproperty.h>
44
#include <rewriterview.h>
45

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

60
#include <qmljs/qmljssimplereader.h>
61
62
#include <utils/fileutils.h>

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

80
81
82
83
#ifdef Q_OS_WIN
#include <utils/winutils.h>
#endif

84
85
86
87
enum {
    debug = false
};

88
89
const int collapseButtonOffset = 114;

90
91
namespace QmlDesigner {

92
93
const char resourcePropertyEditorPath[] = ":/propertyeditor";

94
95
96
97
98
99
100
101
102
103
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
}

104
105
106
107
108
109
110
111
#ifdef Q_OS_MAC
#  define SHARE_PATH "/../Resources/qmldesigner"
#else
#  define SHARE_PATH "/../share/qtcreator/qmldesigner"
#endif

static inline QString sharedDirPath()
{
112
    QString appPath = applicationDirPath();
113
114
115
116

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

117
118
119
120
121
static inline QString propertyTemplatesPath()
{
    return sharedDirPath() + QLatin1String("/propertyeditor/PropertyTemplates/");
}

122
123
124
125
126
127
128
static QObject *variantToQObject(const QVariant &v)
{
    if (v.userType() == QMetaType::QObjectStar || v.userType() > QMetaType::User)
        return *(QObject **)v.constData();

    return 0;
}
129

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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;
}

156
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
157
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
158
        m_contextObject(new PropertyEditorContextObject())
159
160
161
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

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

Robert Loehning's avatar
Robert Loehning committed
170
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)), propertyEditor, SLOT(changeValue(QString)));
171
172
173
174
175
176
}

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

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

194
195
}

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

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

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

240
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
241
{
242
    if (!fxObjectNode.isValid()) {
243
        return;
244
    }
245

246
    QDeclarativeContext *ctxt = m_view->rootContext();
247
248

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
249
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
250
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
251
252

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

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

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

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

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

278
        m_contextObject->setSpecificsUrl(qmlSpecificsFile);
Orgad Shaneh's avatar
Orgad Shaneh committed
279

280
        m_contextObject->setStateName(stateName);
281
282
        if (!fxObjectNode.isValid())
            return;
283
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
284
285
286
287
288

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

        m_contextObject->setSelectionChanged(false);
289
290
291
292
293
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

294
295
void PropertyEditor::NodeType::initialSetup(const QString &typeName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
{
296
    QDeclarativeContext *ctxt = m_view->rootContext();
297

298
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
299

Marco Bubke's avatar
Marco Bubke committed
300
    foreach (const QString &propertyName, metaInfo.propertyNames())
301
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
302

303
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
304
305
306
307
308
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

    valueObject->setValue(typeName);
Robert Loehning's avatar
Robert Loehning committed
309
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
310
311
312
    m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));

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

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

324
325
326
327
328
329
330
331
332
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

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

    m_contextObject->setIsBaseState(true);

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

    m_contextObject->setGlobalBaseUrl(QUrl());
333
334
}

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

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

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

357
358
359
360
361
362
363
364

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

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

383
384
385
static inline QString fixTypeNameForPanes(const QString &typeName)
{
    QString fixedTypeName = typeName;
386
    fixedTypeName.replace('.', '/');
387
388
389
    return fixedTypeName;
}

390
391
void PropertyEditor::setupPane(const QString &typeName)
{
392
    NodeMetaInfo metaInfo = model()->metaInfo(typeName);
393

394
    QUrl qmlFile = fileToUrl(locateQmlFile(metaInfo, QLatin1String("Qt/ItemPane.qml")));
395
396
    QUrl qmlSpecificsFile;

397
    qmlSpecificsFile = fileToUrl(locateQmlFile(metaInfo, fixTypeNameForPanes(typeName) + "Specifics.qml"));
398
    NodeType *type = m_typeHash.value(qmlFile.toString());
399

400
    if (!type) {
401
        type = new NodeType(this);
402

403
        QDeclarativeContext *ctxt = type->m_view->rootContext();
404
405
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
        type->initialSetup(typeName, qmlSpecificsFile, this);
406
        type->m_view->setSource(qmlFile);
407
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
408

409
410
411
        m_stackedWidget->addWidget(type->m_view);
        m_typeHash.insert(qmlFile.toString(), type);
    } else {
412
        QDeclarativeContext *ctxt = type->m_view->rootContext();
413
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
Thomas Hartmann's avatar
Thomas Hartmann committed
414

415
        type->initialSetup(typeName, qmlSpecificsFile, this);
416
417
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
418
419
}

420
void PropertyEditor::changeValue(const QString &propertyName)
421
{
422
    if (propertyName.isNull())
423
        return;
424
425
426
427

    if (m_locked)
        return;

428
    if (propertyName == "type")
429
430
        return;

431
432
433
    if (!m_selectedNode.isValid())
        return;

434
    if (propertyName == "id") {
435
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
436
437
        const QString newId = value->value().toString();

438
439
440
441
        if (newId == m_selectedNode.id())
            return;

        if (m_selectedNode.isValidId(newId)  && !modelNodeForId(newId).isValid() ) {
442
443
444
445
            if (m_selectedNode.id().isEmpty() || newId.isEmpty()) { //no id
                try {
                    m_selectedNode.setId(newId);
                } catch (InvalidIdException &e) { //better save then sorry
446
                    m_locked = true;
447
                    value->setValue(m_selectedNode.id());
448
                    m_locked = false;
449
450
451
452
453
454
                    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);
            }
455
        } else {
456
            m_locked = true;
457
            value->setValue(m_selectedNode.id());
458
459
460
461
462
            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));
463
464
465
466
        }
        return;
    }

467
468
469
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
470
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
471
472
473
474
475
476
477
478
479

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

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

    if (value->value().isValid() && !castedValue.isValid()) {
488
        qWarning() << "PropertyEditor:" << propertyName << "not properly casted (metainfo)";
489
490
491
        return ;
    }

Marco Bubke's avatar
Marco Bubke committed
492
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName))
493
494
495
        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();
496
497
498
499
500
501
        if (QFileInfo(filePath).exists() && QFileInfo(filePath).isAbsolute()) {
            QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
            castedValue = QUrl(fileDir.relativeFilePath(filePath));
        }
    }

502
503
504
505
506
507
508
        if (castedValue.type() == QVariant::Color) {
            QColor color = castedValue.value<QColor>();
            QColor newColor = QColor(color.name());
            newColor.setAlpha(color.alpha());
            castedValue = QVariant(newColor);
        }

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

void PropertyEditor::changeExpression(const QString &name)
{
527
528
529
530
531
532
    if (name.isNull())
        return;

    if (m_locked)
        return;

533
534
    RewriterTransaction transaction = beginRewriterTransaction();

535
536
537
538
539
    try {
        QString underscoreName(name);
        underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

        QmlObjectNode fxObjectNode(m_selectedNode);
540
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
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
569
570
571
572
573

        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;
                }
574
            }
575
576
        }

577
578
579
580
        if (!value) {
            qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
            return;
        }
581

582
583
584
        if (value->expression().isEmpty())
            return;

585
586
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
587
588

        transaction.commit(); //committing in the try block
589
590
    }

591
    catch (RewritingException &e) {
592
        QMessageBox::warning(0, "Error", e.description());
593
594
595
    }
}

596
void PropertyEditor::updateSize()
597
{
598
599
600
601
602
    if (!m_currentType)
        return;
    QWidget* frame = m_currentType->m_view->findChild<QWidget*>("propertyEditorFrame");
    if (frame)
        frame->resize(m_stackedWidget->size());
603
604
}

605
606
607
void PropertyEditor::setupPanes()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
608
609
    setupPane("QtQuick.Rectangle");
    setupPane("QtQuick.Text");
610
611
612
613
614
    resetView();
    m_setupCompleted = true;
    QApplication::restoreOverrideCursor();
}

615
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
616
{
617
618
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

619
620
621
    if (!m_selectedNode.isValid())
        return;

622
623
    m_locked = true;

624
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
625
626
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
627
            if ( !m_selectedNode.hasProperty(propertyName) || m_selectedNode.property(property.name()).isBindingProperty() )
628
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
629
            else
630
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
631
632
        }
    }
633
634

    m_locked = false;
635
636
}

637
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
638
{
639
640
    QmlModelView::transformChanged(fxObjectNode, propertyName);

641
642
643
    if (!m_selectedNode.isValid())
        return;

644
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
645
646
647
        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))
648
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
649
            else
650
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
651
652
653
654
        }
    }
}

655
656
657
658
void PropertyEditor::setQmlDir(const QString &qmlDir)
{
    m_qmlDir = qmlDir;

659

660
661
662
663
664
665
666
667
    QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
    watcher->addPath(m_qmlDir);
    connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(reloadQml()));
}

void PropertyEditor::delayedResetView()
{
    if (m_timerId == 0)
668
        m_timerId = startTimer(100);
669
670
671
672
673
674
675
676
677
}

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

678
QString templateGeneration(NodeMetaInfo type, NodeMetaInfo superType, const QmlObjectNode &objectNode)
679
{
680
681
682
683
684
685
    if (!templateConfiguration() && templateConfiguration()->isValid())
        return QString();

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

    QString qmlTemplate = imports.join(QLatin1String("\n")) + QLatin1Char('\n');
686
    qmlTemplate += QLatin1String("GroupBox {\n");
687
    qmlTemplate += QString(QLatin1String("caption: \"%1\"\n")).arg(objectNode.modelNode().simplifiedTypeName());
688
689
    qmlTemplate += QLatin1String("layout: VerticalLayout {\n");

690
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
691
    orderedList = type.propertyNames();
692
693
    qSort(orderedList);

694
695
    bool emptyTemplate = true;

696
    foreach (const QString &name, orderedList) {
697
698
699

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

702
        properName.replace('.', '_');
703

704
        QString typeName = type.propertyTypeName(name);
705
        //alias resolution only possible with instance
706
707
        if (typeName == QLatin1String("alias") && objectNode.isValid())
            typeName = objectNode.instanceType(name);
708

709
        if (!superType.hasProperty(name) && type.propertyIsWritable(name) && !name.contains(QLatin1String("."))) {
710
711
712
713
714
715
716
717
718
719
720
721
722
            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;
                    }
                }
723
724
725
726
727
        }
    }
    qmlTemplate += QLatin1String("}\n"); //VerticalLayout
    qmlTemplate += QLatin1String("}\n"); //GroupBox

728
729
730
    if (emptyTemplate)
        return QString();

731
732
733
    return qmlTemplate;
}

734
735
736
737
738
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
739
740
    m_locked = true;

741
742
743
744
745
746
747
748
749
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

750
751
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
752
    QUrl qmlSpecificsFile;
753
754
755
756
757
758
759
760
761
762
763

    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;
764
            qmlSpecificsFile = fileToUrl(locateQmlFile(info, fixTypeNameForPanes(info.typeName()) + "Specifics.qml"));
765
766
767
768
769
770
            diffClassName = info.typeName();
        }
    }

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

772
773
    QString specificQmlData;

774
    if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) {
775
        //do magic !!
776
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode);
777
778
    }

779
    NodeType *type = m_typeHash.value(qmlFile.toString());
780

781
    if (!type) {
782
        type = new NodeType(this);
783
784

        m_stackedWidget->addWidget(type->m_view);
785
        m_typeHash.insert(qmlFile.toString(), type);
786
787
788
789
790
791

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

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

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

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

820

Thomas Hartmann's avatar
Thomas Hartmann committed
821
    QDeclarativeContext *ctxt = type->m_view->rootContext();
822
    ctxt->setContextProperty("finishedNotify", QVariant(true));
823
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
824
    ctxt->setContextProperty("selectionChanged", QVariant(true));
825
826
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
827

828
829
    m_currentType = type;

830
831
    m_locked = false;

832
833
    if (m_timerId)
        m_timerId = 0;
834
835

    updateSize();
836
837
838
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
839
                                          const QList<ModelNode> &lastSelectedNodeList)
840
841
842
{
    Q_UNUSED(lastSelectedNodeList);

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

865
    resetView();
866
867
    if (!m_setupCompleted) {
        m_singleShotTimer->setSingleShot(true);
Thomas Hartmann's avatar
Thomas Hartmann committed
868
        m_singleShotTimer->setInterval(100);
869
870
871
        connect(m_singleShotTimer, SIGNAL(timeout()), this, SLOT(setupPanes()));
        m_singleShotTimer->start();
    }
Thomas Hartmann's avatar
Thomas Hartmann committed
872
873

    m_locked = false;
874
875
876
877
878
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
879
    m_currentType->m_propertyEditorTransaction->end();
880
881
882
883
884

    resetView();
}


885
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
886
{
887
    QmlModelView::propertiesRemoved(propertyList);
888
889
890
891

    if (!m_selectedNode.isValid())
        return;

892
893
894
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

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


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

909
910
911
912
913
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

914
915
916
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

917
918
919
920
921
    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())
922
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
923
            else
924
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
925
926
927
928
929
930
931
932
933
934
935
        }
    }
}

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

    if (!m_selectedNode.isValid())
        return;

936
937
938
       if (!QmlObjectNode(m_selectedNode).isValid())
        return;

939
940
941
942
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
943
944
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
945
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
946
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
947
            else
948
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
949
950
951
952
        }
    }
}

953

954
void PropertyEditor::instanceInformationsChange(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
955
956
957
958
{
    if (!m_selectedNode.isValid())
        return;

959
    m_locked = true;
960
961
    QList<InformationName> informationNameList = informationChangeHash.values(m_selectedNode);
    if (informationNameList.contains(Anchor))
962
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
963
    m_locked = false;