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

enum {
    debug = false
};

82
83
const int collapseButtonOffset = 114;

84
85
namespace QmlDesigner {

86
87
88
89
90
91
92
93
94
95
96
97
98
#ifdef Q_OS_MAC
#  define SHARE_PATH "/../Resources/qmldesigner"
#else
#  define SHARE_PATH "/../share/qtcreator/qmldesigner"
#endif

static inline QString sharedDirPath()
{
    QString appPath = QCoreApplication::applicationDirPath();

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

99
100
101
102
103
static inline QString propertyTemplatesPath()
{
    return sharedDirPath() + QLatin1String("/propertyeditor/PropertyTemplates/");
}

104
105
106
107
108
109
110
static QObject *variantToQObject(const QVariant &v)
{
    if (v.userType() == QMetaType::QObjectStar || v.userType() > QMetaType::User)
        return *(QObject **)v.constData();

    return 0;
}
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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;
}

138
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
139
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
140
        m_contextObject(new PropertyEditorContextObject())
141
142
143
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

144
    QDeclarativeContext *ctxt = m_view->rootContext();
145
    m_view->engine()->setOutputWarningsToStandardError(debug);
146
    m_view->engine()->addImportPath(sharedDirPath() + QLatin1String("/propertyeditor"));
147
148
    m_dummyPropertyEditorValue->setValue("#000000");
    ctxt->setContextProperty("dummyBackendValue", m_dummyPropertyEditorValue.data());
149
150
    m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
    ctxt->setContextObject(m_contextObject.data());
151

Robert Loehning's avatar
Robert Loehning committed
152
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)), propertyEditor, SLOT(changeValue(QString)));
153
154
155
156
157
158
}

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

159
void setupPropertyEditorValue(const QString &name, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor, const QString &type)
160
161
162
{
    QString propertyName(name);
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
163
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(propertyMap->value(propertyName)));
164
165
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
Robert Loehning's avatar
Robert Loehning committed
166
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), propertyMap, SIGNAL(valueChanged(QString,QVariant)));
167
168
169
170
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
    }
    valueObject->setName(propertyName);
171
172
173
174
175
    if (type == "QColor")
        valueObject->setValue(QVariant("#000000"));
    else
        valueObject->setValue(QVariant(1));

176
177
}

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

192
    if (fxObjectNode.propertyAffectedByCurrentState(name) && !(fxObjectNode.modelNode().property(name).isBindingProperty())) {
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
        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());
    }
}

208
void PropertyEditor::NodeType::setValue(const QmlObjectNode & fxObjectNode, const QString &name, const QVariant &value)
209
{
210
211
    QString propertyName = name;
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
212
    PropertyEditorValue *propertyValue = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(propertyName)));
213
    if (propertyValue) {
214
        propertyValue->setValue(value);
215
216
        if (!fxObjectNode.hasBindingProperty(name))
            propertyValue->setExpression(value.toString());
217
218
        else
            propertyValue->setExpression(fxObjectNode.expression(name));
219
    }
220
221
}

222
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
223
{
224
    if (!fxObjectNode.isValid()) {
225
        return;
226
    }
227

228
    QDeclarativeContext *ctxt = m_view->rootContext();
229
230

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
231
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
232
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
233
234

        // className
235
        PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
236
237
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
238
239
240
        valueObject->setName("className");
        valueObject->setModelNode(fxObjectNode.modelNode());
        valueObject->setValue(fxObjectNode.modelNode().simplifiedTypeName());
Robert Loehning's avatar
Robert Loehning committed
241
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
242
        m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));
243
244

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

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

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

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

260
        m_contextObject->setSpecificsUrl(qmlSpecificsFile);
Orgad Shaneh's avatar
Orgad Shaneh committed
261

262
        m_contextObject->setStateName(stateName);
263
264
        if (!fxObjectNode.isValid())
            return;
265
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
266
267
268
269
270

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

        m_contextObject->setSelectionChanged(false);
271
272
273
274
275
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

276
277
void PropertyEditor::NodeType::initialSetup(const QString &typeName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
{
278
    QDeclarativeContext *ctxt = m_view->rootContext();
279

280
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
281

Marco Bubke's avatar
Marco Bubke committed
282
    foreach (const QString &propertyName, metaInfo.propertyNames())
283
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
284

285
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
286
287
288
289
290
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

    valueObject->setValue(typeName);
Robert Loehning's avatar
Robert Loehning committed
291
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
292
293
294
    m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));

    // id
295
    valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("id")));
296
297
298
299
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("id");
    valueObject->setValue("id");
Robert Loehning's avatar
Robert Loehning committed
300
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
301
302
303
    m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));

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

306
307
308
309
310
311
312
313
314
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

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

    m_contextObject->setIsBaseState(true);

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

    m_contextObject->setGlobalBaseUrl(QUrl());
315
316
}

317
PropertyEditor::PropertyEditor(QWidget *parent) :
318
319
320
321
        QmlModelView(parent),
        m_parent(parent),
        m_updateShortcut(0),
        m_timerId(0),
322
        m_stackedWidget(new StackedWidget(parent)),
323
        m_currentType(0),
324
        m_locked(false),
325
326
        m_setupCompleted(false),
        m_singleShotTimer(new QTimer(this))
327
328
329
330
{
    m_updateShortcut = new QShortcut(QKeySequence("F5"), m_stackedWidget);
    connect(m_updateShortcut, SIGNAL(activated()), this, SLOT(reloadQml()));

331
332
    m_stackedWidget->setStyleSheet(
            QLatin1String(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")));
333
    m_stackedWidget->setMinimumWidth(320);
334
    m_stackedWidget->move(0, 0);
335
    connect(m_stackedWidget, SIGNAL(resized()), this, SLOT(updateSize()));
336
337
338

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

339
340
341
342
343
344
345
346

    static bool declarativeTypesRegistered = false;
    if (!declarativeTypesRegistered) {
        declarativeTypesRegistered = true;
        BasicWidgets::registerDeclarativeTypes();
        BasicLayouts::registerDeclarativeTypes();
        ResetWidget::registerDeclarativeType();
        QLayoutObject::registerDeclarativeType();
347
        QmlEditorWidgets::ColorWidgets::registerDeclarativeTypes();
348
349
350
351
        BehaviorDialog::registerDeclarativeType();
        QProxyLayoutItem::registerDeclarativeTypes();
        PropertyEditorValue::registerDeclarativeTypes();
        FontWidget::registerDeclarativeTypes();
Thomas Hartmann's avatar
Thomas Hartmann committed
352
        SiblingComboBox::registerDeclarativeTypes();
353
        OriginWidget::registerDeclarativeType();
354
        GradientLineQmlAdaptor::registerDeclarativeType();
355
    }
356
357
    setQmlDir(sharedDirPath() + QLatin1String("/propertyeditor"));
    m_stackedWidget->setWindowTitle(tr("Properties"));
358
359
360
361
362
363
364
}

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

365
366
367
static inline QString fixTypeNameForPanes(const QString &typeName)
{
    QString fixedTypeName = typeName;
368
    fixedTypeName.replace('.', '/');
369
370
371
    return fixedTypeName;
}

372
373
374
void PropertyEditor::setupPane(const QString &typeName)
{

375
    QUrl qmlFile = fileToUrl(locateQmlFile(QLatin1String("Qt/ItemPane.qml")));
376
377
    QUrl qmlSpecificsFile;

378
    qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(typeName) + "Specifics.qml"));
379
    NodeType *type = m_typeHash.value(qmlFile.toString());
380

381
    if (!type) {
382
        type = new NodeType(this);
383

384
        QDeclarativeContext *ctxt = type->m_view->rootContext();
385
386
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
        type->initialSetup(typeName, qmlSpecificsFile, this);
387
        type->m_view->setSource(qmlFile);
388
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
389

390
391
392
        m_stackedWidget->addWidget(type->m_view);
        m_typeHash.insert(qmlFile.toString(), type);
    } else {
393
        QDeclarativeContext *ctxt = type->m_view->rootContext();
394
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
Thomas Hartmann's avatar
Thomas Hartmann committed
395

396
        type->initialSetup(typeName, qmlSpecificsFile, this);
397
398
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
399
400
}

401
void PropertyEditor::changeValue(const QString &propertyName)
402
{
403
    if (propertyName.isNull())
404
        return;
405
406
407
408

    if (m_locked)
        return;

409
    if (propertyName == "type")
410
411
        return;

412
413
414
    if (!m_selectedNode.isValid())
        return;

415
    if (propertyName == "id") {
416
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
417
418
        const QString newId = value->value().toString();

419
420
421
422
        if (newId == m_selectedNode.id())
            return;

        if (m_selectedNode.isValidId(newId)  && !modelNodeForId(newId).isValid() ) {
423
424
425
426
            if (m_selectedNode.id().isEmpty() || newId.isEmpty()) { //no id
                try {
                    m_selectedNode.setId(newId);
                } catch (InvalidIdException &e) { //better save then sorry
427
                    m_locked = true;
428
                    value->setValue(m_selectedNode.id());
429
                    m_locked = false;
430
431
432
433
434
435
                    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);
            }
436
        } else {
437
            m_locked = true;
438
            value->setValue(m_selectedNode.id());
439
440
441
442
443
            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));
444
445
446
447
        }
        return;
    }

448
449
450
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
451
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
452
453
454
455
456
457
458
459
460

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

Marco Bubke's avatar
Marco Bubke committed
461
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName)) {
462
        castedValue = fxObjectNode.modelNode().metaInfo().propertyCastedValue(propertyName, value->value());
463
    } else {
464
        qWarning() << "PropertyEditor:" <<propertyName << "cannot be casted (metainfo)";
465
466
467
468
        return ;
    }

    if (value->value().isValid() && !castedValue.isValid()) {
469
        qWarning() << "PropertyEditor:" << propertyName << "not properly casted (metainfo)";
470
471
472
        return ;
    }

Marco Bubke's avatar
Marco Bubke committed
473
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName))
474
        if (fxObjectNode.modelNode().metaInfo().propertyTypeName(propertyName) == QLatin1String("QUrl")) { //turn absolute local file paths into relative paths
475
476
477
478
479
480
481
        QString filePath = castedValue.toUrl().toString();
        if (QFileInfo(filePath).exists() && QFileInfo(filePath).isAbsolute()) {
            QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
            castedValue = QUrl(fileDir.relativeFilePath(filePath));
        }
    }

482
483
484
485
486
487
488
        if (castedValue.type() == QVariant::Color) {
            QColor color = castedValue.value<QColor>();
            QColor newColor = QColor(color.name());
            newColor.setAlpha(color.alpha());
            castedValue = QVariant(newColor);
        }

489
490
491
492
        try {
            if (!value->value().isValid()) { //reset
                fxObjectNode.removeVariantProperty(propertyName);
            } else {
493
                if (castedValue.isValid() && !castedValue.isNull()) {
494
                    m_locked = true;
495
496
497
                    fxObjectNode.setVariantProperty(propertyName, castedValue);
                    m_locked = false;
                }
498
499
500
501
            }
        }
        catch (RewritingException &e) {
            QMessageBox::warning(0, "Error", e.description());
502
503
504
505
506
        }
}

void PropertyEditor::changeExpression(const QString &name)
{
507
508
509
510
511
512
    if (name.isNull())
        return;

    if (m_locked)
        return;

513
514
    RewriterTransaction transaction = beginRewriterTransaction();

515
516
517
518
519
    try {
        QString underscoreName(name);
        underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

        QmlObjectNode fxObjectNode(m_selectedNode);
520
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553

        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;
                }
554
            }
555
556
        }

557
558
559
560
        if (!value) {
            qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
            return;
        }
561

562
563
564
        if (value->expression().isEmpty())
            return;

565
566
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
567
568

        transaction.commit(); //committing in the try block
569
570
    }

571
    catch (RewritingException &e) {
572
        QMessageBox::warning(0, "Error", e.description());
573
574
575
    }
}

576
void PropertyEditor::updateSize()
577
{
578
579
580
581
582
    if (!m_currentType)
        return;
    QWidget* frame = m_currentType->m_view->findChild<QWidget*>("propertyEditorFrame");
    if (frame)
        frame->resize(m_stackedWidget->size());
583
584
}

585
586
587
void PropertyEditor::setupPanes()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
588
589
    setupPane("QtQuick.Rectangle");
    setupPane("QtQuick.Text");
590
591
592
593
594
    resetView();
    m_setupCompleted = true;
    QApplication::restoreOverrideCursor();
}

595
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
596
{
597
598
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

599
600
601
    if (!m_selectedNode.isValid())
        return;

602
603
    m_locked = true;

604
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
605
606
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
607
            if ( !m_selectedNode.hasProperty(propertyName) || m_selectedNode.property(property.name()).isBindingProperty() )
608
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
609
            else
610
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
611
612
        }
    }
613
614

    m_locked = false;
615
616
}

617
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
618
{
619
620
    QmlModelView::transformChanged(fxObjectNode, propertyName);

621
622
623
    if (!m_selectedNode.isValid())
        return;

624
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
625
626
627
        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))
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
        }
    }
}

635
636
637
638
void PropertyEditor::setQmlDir(const QString &qmlDir)
{
    m_qmlDir = qmlDir;

639

640
641
642
643
644
645
646
647
    QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
    watcher->addPath(m_qmlDir);
    connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(reloadQml()));
}

void PropertyEditor::delayedResetView()
{
    if (m_timerId == 0)
648
        m_timerId = startTimer(100);
649
650
651
652
653
654
655
656
657
}

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

658
QString templateGeneration(NodeMetaInfo type, NodeMetaInfo superType, const QmlObjectNode &objectNode)
659
{
660
661
662
663
664
665
    if (!templateConfiguration() && templateConfiguration()->isValid())
        return QString();

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

    QString qmlTemplate = imports.join(QLatin1String("\n")) + QLatin1Char('\n');
666
    qmlTemplate += QLatin1String("GroupBox {\n");
667
    qmlTemplate += QString(QLatin1String("caption: \"%1\"\n")).arg(objectNode.modelNode().simplifiedTypeName());
668
669
    qmlTemplate += QLatin1String("layout: VerticalLayout {\n");

670
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
671
    orderedList = type.propertyNames();
672
673
    qSort(orderedList);

674
675
    bool emptyTemplate = true;

676
    foreach (const QString &name, orderedList) {
677
678
679

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

682
        properName.replace('.', '_');
683

684
        QString typeName = type.propertyTypeName(name);
685
        //alias resolution only possible with instance
686
687
        if (typeName == QLatin1String("alias") && objectNode.isValid())
            typeName = objectNode.instanceType(name);
688

689
        if (!superType.hasProperty(name) && type.propertyIsWritable(name)) {
690
691
692
693
694
695
696
697
698
699
700
701
702
            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;
                    }
                }
703
704
705
706
707
        }
    }
    qmlTemplate += QLatin1String("}\n"); //VerticalLayout
    qmlTemplate += QLatin1String("}\n"); //GroupBox

708
709
710
    if (emptyTemplate)
        return QString();

711
712
713
    return qmlTemplate;
}

714
715
716
717
718
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
719
720
    m_locked = true;

721
722
723
724
725
726
727
728
729
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

730
731
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
732
    QUrl qmlSpecificsFile;
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750

    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;
751

752
753
    QString specificQmlData;

754
    if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) {
755
        //do magic !!
756
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode);
757
758
    }

759
    NodeType *type = m_typeHash.value(qmlFile.toString());
760

761
    if (!type) {
762
        type = new NodeType(this);
763
764

        m_stackedWidget->addWidget(type->m_view);
765
        m_typeHash.insert(qmlFile.toString(), type);
766
767
768
769
770
771

        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
            Q_ASSERT(fxObjectNode.isValid());
        }
772
        QDeclarativeContext *ctxt = type->m_view->rootContext();
773
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
774
        ctxt->setContextProperty("finishedNotify", QVariant(false));
775
        if (specificQmlData.isEmpty())
776
            type->m_contextObject->setSpecificQmlData(specificQmlData);
Orgad Shaneh's avatar
Orgad Shaneh committed
777

778
779
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
780
        type->m_view->setSource(qmlFile);
781
782
783
784
785
786
        ctxt->setContextProperty("finishedNotify", QVariant(true));
    } else {
        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
        }
Thomas Hartmann's avatar
Thomas Hartmann committed
787
        QDeclarativeContext *ctxt = type->m_view->rootContext();
Orgad Shaneh's avatar
Orgad Shaneh committed
788

789
        ctxt->setContextProperty("finishedNotify", QVariant(false));
790
        if (specificQmlData.isEmpty())
791
            type->m_contextObject->setSpecificQmlData(specificQmlData);
792
793
        QString currentStateName = currentState().isValid() ? currentState().name() : QLatin1String("invalid state");
        type->setup(fxObjectNode, currentStateName, qmlSpecificsFile, this);
794
795
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
796
797
798
    }

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

800

Thomas Hartmann's avatar
Thomas Hartmann committed
801
    QDeclarativeContext *ctxt = type->m_view->rootContext();
802
    ctxt->setContextProperty("finishedNotify", QVariant(true));
803
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
804
    ctxt->setContextProperty("selectionChanged", QVariant(true));
805
806
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
807

808
809
    m_currentType = type;

810
811
    m_locked = false;

812
813
    if (m_timerId)
        m_timerId = 0;
814
815

    updateSize();
816
817
818
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
819
                                          const QList<ModelNode> &lastSelectedNodeList)
820
821
822
{
    Q_UNUSED(lastSelectedNodeList);

823
    if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
824
        select(ModelNode());
825
    else if (m_selectedNode != selectedNodeList.first())
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
        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
843
844
    m_locked = true;

845
    resetView();
846
847
    if (!m_setupCompleted) {
        m_singleShotTimer->setSingleShot(true);
Thomas Hartmann's avatar
Thomas Hartmann committed
848
        m_singleShotTimer->setInterval(100);
849
850
851
        connect(m_singleShotTimer, SIGNAL(timeout()), this, SLOT(setupPanes()));
        m_singleShotTimer->start();
    }
Thomas Hartmann's avatar
Thomas Hartmann committed
852
853

    m_locked = false;
854
855
856
857
858
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
859
    m_currentType->m_propertyEditorTransaction->end();
860
861
862
863
864

    resetView();
}


865
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
866
{
867
    QmlModelView::propertiesRemoved(propertyList);
868
869
870
871

    if (!m_selectedNode.isValid())
        return;

872
873
874
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

875
    foreach (const AbstractProperty &property, propertyList) {
876
877
878
        ModelNode node(property.parentModelNode());
        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
879
880
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
881
882
883
884
885
886
887
        }
    }
}


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

889
890
891
892
893
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

894
895
896
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

897
898
899
900
901
    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())
902
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
903
            else
904
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
905
906
907
908
909
910
911
912
913
914
915
        }
    }
}

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

    if (!m_selectedNode.isValid())
        return;

916
917
918
       if (!QmlObjectNode(m_selectedNode).isValid())
        return;

919
920
921
922
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
923
924
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
925
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
926
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
927
            else
928
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
929
930
931
932
        }
    }
}

933

934
void PropertyEditor::instanceInformationsChange(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
935
936
937
938
{
    if (!m_selectedNode.isValid())
        return;

939
    m_locked = true;
940
941
    QList<InformationName> informationNameList = informationChangeHash.values(m_selectedNode);
    if (informationNameList.contains(Anchor))
942
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
943
    m_locked = false;
944
945
}

946
947
948
949
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

950
    if (!m_selectedNode.isValid())
951
952
        return;

953
954
955
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

956
    if (node == m_selectedNode) {
957

958
        if (m_currentType) {
959
            setValue(node, "id", newId);
960
961
        }
    }
962
963
}

Marco Bubke's avatar
Marco Bubke committed
964
965
966
967
968
void PropertyEditor::scriptFunctionsChanged(const ModelNode &node, const QStringList &scriptFunctionList)
{
    QmlModelView::scriptFunctionsChanged(node, scriptFunctionList);
}

969
970
void PropertyEditor