propertyeditor.cpp 38.7 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
8
9
10
11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
28
29
30
31
32
33
**
**************************************************************************/

#include "propertyeditor.h"

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

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

40
41
42
#include <bindingproperty.h>

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

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

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
104
105
static QObject *variantToQObject(const QVariant &v)
{
    if (v.userType() == QMetaType::QObjectStar || v.userType() > QMetaType::User)
        return *(QObject **)v.constData();

    return 0;
}
106

107
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
108
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
109
        m_contextObject(new PropertyEditorContextObject())
110
111
112
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

113
    QDeclarativeContext *ctxt = m_view->rootContext();
114
    m_view->engine()->setOutputWarningsToStandardError(debug);
115
116
    m_dummyPropertyEditorValue->setValue("#000000");
    ctxt->setContextProperty("dummyBackendValue", m_dummyPropertyEditorValue.data());
117
118
    m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
    ctxt->setContextObject(m_contextObject.data());
119

Robert Loehning's avatar
Robert Loehning committed
120
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)), propertyEditor, SLOT(changeValue(QString)));
121
122
123
124
125
126
}

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

127
void setupPropertyEditorValue(const QString &name, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor, const QString &type)
128
129
130
{
    QString propertyName(name);
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
131
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(propertyMap->value(propertyName)));
132
133
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
Robert Loehning's avatar
Robert Loehning committed
134
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), propertyMap, SIGNAL(valueChanged(QString,QVariant)));
135
136
137
138
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
    }
    valueObject->setName(propertyName);
139
140
141
142
143
    if (type == "QColor")
        valueObject->setValue(QVariant("#000000"));
    else
        valueObject->setValue(QVariant(1));

144
145
}

146
void createPropertyEditorValue(const QmlObjectNode &fxObjectNode, const QString &name, const QVariant &value, QDeclarativePropertyMap *propertyMap, PropertyEditor *propertyEditor)
147
148
{
    QString propertyName(name);
149
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
150
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(propertyMap->value(propertyName)));
151
152
    if (!valueObject) {
        valueObject = new PropertyEditorValue(propertyMap);
Robert Loehning's avatar
Robert Loehning committed
153
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), propertyMap, SIGNAL(valueChanged(QString,QVariant)));
154
        QObject::connect(valueObject, SIGNAL(expressionChanged(QString)), propertyEditor, SLOT(changeExpression(QString)));
155
        propertyMap->insert(propertyName, QVariant::fromValue(valueObject));
156
    }
157
    valueObject->setName(name);
158
    valueObject->setModelNode(fxObjectNode);
159

160
    if (fxObjectNode.propertyAffectedByCurrentState(name) && !(fxObjectNode.modelNode().property(name).isBindingProperty())) {
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
        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());
    }
}

176
void PropertyEditor::NodeType::setValue(const QmlObjectNode & fxObjectNode, const QString &name, const QVariant &value)
177
{
178
179
    QString propertyName = name;
    propertyName.replace(QLatin1Char('.'), QLatin1Char('_'));
180
    PropertyEditorValue *propertyValue = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(propertyName)));
181
    if (propertyValue) {
182
        propertyValue->setValue(value);
183
184
        if (!fxObjectNode.hasBindingProperty(name))
            propertyValue->setExpression(value.toString());
185
186
        else
            propertyValue->setExpression(fxObjectNode.expression(name));
187
    }
188
189
}

190
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
191
{
192
    if (!fxObjectNode.isValid()) {
193
        return;
194
    }
195

196
    QDeclarativeContext *ctxt = m_view->rootContext();
197
198

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
199
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
200
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
201
202

        // className
203
        PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
204
205
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
206
207
208
        valueObject->setName("className");
        valueObject->setModelNode(fxObjectNode.modelNode());
        valueObject->setValue(fxObjectNode.modelNode().simplifiedTypeName());
Robert Loehning's avatar
Robert Loehning committed
209
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
210
        m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));
211
212

        // id
213
        valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("id")));
214
215
        if (!valueObject)
            valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
216
        valueObject->setName("id");
217
        valueObject->setValue(fxObjectNode.id());
Robert Loehning's avatar
Robert Loehning committed
218
        QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
219
        m_backendValuesPropertyMap.insert("id", QVariant::fromValue(valueObject));
220
221
222
223
224

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

        ctxt->setContextProperty("anchorBackend", &m_backendAnchorBinding);
225
        
226
        ctxt->setContextProperty("transaction", m_propertyEditorTransaction.data());
227
228
229
230
        
        m_contextObject->setSpecificsUrl(qmlSpecificsFile);
        
        m_contextObject->setStateName(stateName);
231
232
        if (!fxObjectNode.isValid())
            return;
233
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
234
235
236
237
238

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

        m_contextObject->setSelectionChanged(false);
239
240
241
242
243
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

244
245
void PropertyEditor::NodeType::initialSetup(const QString &typeName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
{
246
    QDeclarativeContext *ctxt = m_view->rootContext();
247

248
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
249

Marco Bubke's avatar
Marco Bubke committed
250
    foreach (const QString &propertyName, metaInfo.propertyNames())
251
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
252

253
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value("className")));
254
255
256
257
258
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

    valueObject->setValue(typeName);
Robert Loehning's avatar
Robert Loehning committed
259
    QObject::connect(valueObject, SIGNAL(valueChanged(QString,QVariant)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)));
260
261
262
    m_backendValuesPropertyMap.insert("className", QVariant::fromValue(valueObject));

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

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

274
275
276
277
278
279
280
281
282
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

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

    m_contextObject->setIsBaseState(true);

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

    m_contextObject->setGlobalBaseUrl(QUrl());
283
284
}

285
PropertyEditor::PropertyEditor(QWidget *parent) :
286
287
288
289
        QmlModelView(parent),
        m_parent(parent),
        m_updateShortcut(0),
        m_timerId(0),
290
        m_stackedWidget(new StackedWidget(parent)),
291
        m_currentType(0),
292
        m_locked(false),
293
294
        m_setupCompleted(false),
        m_singleShotTimer(new QTimer(this))
295
296
297
298
{
    m_updateShortcut = new QShortcut(QKeySequence("F5"), m_stackedWidget);
    connect(m_updateShortcut, SIGNAL(activated()), this, SLOT(reloadQml()));

299
300
    m_stackedWidget->setStyleSheet(
            QLatin1String(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")));
301
    m_stackedWidget->setMinimumWidth(320);
302
    m_stackedWidget->move(0, 0);
303
    connect(m_stackedWidget, SIGNAL(resized()), this, SLOT(updateSize()));
304
305
306

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

307
308
309
310
311
312
313
314

    static bool declarativeTypesRegistered = false;
    if (!declarativeTypesRegistered) {
        declarativeTypesRegistered = true;
        BasicWidgets::registerDeclarativeTypes();
        BasicLayouts::registerDeclarativeTypes();
        ResetWidget::registerDeclarativeType();
        QLayoutObject::registerDeclarativeType();
315
        QmlEditorWidgets::ColorWidgets::registerDeclarativeTypes();
316
317
318
319
        BehaviorDialog::registerDeclarativeType();
        QProxyLayoutItem::registerDeclarativeTypes();
        PropertyEditorValue::registerDeclarativeTypes();
        FontWidget::registerDeclarativeTypes();
Thomas Hartmann's avatar
Thomas Hartmann committed
320
        SiblingComboBox::registerDeclarativeTypes();
321
        OriginWidget::registerDeclarativeType();
322
        GradientLineQmlAdaptor::registerDeclarativeType();
323
    }
324
325
    setQmlDir(sharedDirPath() + QLatin1String("/propertyeditor"));
    m_stackedWidget->setWindowTitle(tr("Properties"));
326
327
328
329
330
331
332
}

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

333
334
335
static inline QString fixTypeNameForPanes(const QString &typeName)
{
    QString fixedTypeName = typeName;
336
    fixedTypeName.replace('.', '/');
337
338
339
340
    fixedTypeName.replace("QtQuick/", "Qt/");
    return fixedTypeName;
}

341
342
343
void PropertyEditor::setupPane(const QString &typeName)
{

344
    QUrl qmlFile = fileToUrl(locateQmlFile(QLatin1String("Qt/ItemPane.qml")));
345
346
    QUrl qmlSpecificsFile;

347
    qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(typeName) + "Specifics.qml"));
348
    NodeType *type = m_typeHash.value(qmlFile.toString());
349

350
    if (!type) {
351
        type = new NodeType(this);
352

353
        QDeclarativeContext *ctxt = type->m_view->rootContext();
354
355
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
        type->initialSetup(typeName, qmlSpecificsFile, this);
356
        type->m_view->setSource(qmlFile);
357
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
358

359
360
361
        m_stackedWidget->addWidget(type->m_view);
        m_typeHash.insert(qmlFile.toString(), type);
    } else {
362
        QDeclarativeContext *ctxt = type->m_view->rootContext();
363
        ctxt->setContextProperty("finishedNotify", QVariant(false) );
Thomas Hartmann's avatar
Thomas Hartmann committed
364

365
        type->initialSetup(typeName, qmlSpecificsFile, this);
366
367
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
368
369
}

370
void PropertyEditor::changeValue(const QString &propertyName)
371
{
372
    if (propertyName.isNull())
373
        return;
374
375
376
377

    if (m_locked)
        return;

378
    if (propertyName == "type")
379
380
        return;

381
382
383
    if (!m_selectedNode.isValid())
        return;

384
    if (propertyName == "id") {
385
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
386
387
        const QString newId = value->value().toString();

388
389
390
391
        if (newId == m_selectedNode.id())
            return;

        if (m_selectedNode.isValidId(newId)  && !modelNodeForId(newId).isValid() ) {
392
393
394
395
            if (m_selectedNode.id().isEmpty() || newId.isEmpty()) { //no id
                try {
                    m_selectedNode.setId(newId);
                } catch (InvalidIdException &e) { //better save then sorry
396
                    m_locked = true;
397
                    value->setValue(m_selectedNode.id());
398
                    m_locked = false;
399
400
401
402
403
404
                    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);
            }
405
        } else {
406
            m_locked = true;
407
            value->setValue(m_selectedNode.id());
408
409
410
411
412
            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));
413
414
415
416
        }
        return;
    }

417
418
419
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
420
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
421
422
423
424
425
426
427
428
429

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

Marco Bubke's avatar
Marco Bubke committed
430
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName)) {
431
        castedValue = fxObjectNode.modelNode().metaInfo().propertyCastedValue(propertyName, value->value());
432
    } else {
433
        qWarning() << "PropertyEditor:" <<propertyName << "cannot be casted (metainfo)";
434
435
436
437
        return ;
    }

    if (value->value().isValid() && !castedValue.isValid()) {
438
        qWarning() << "PropertyEditor:" << propertyName << "not properly casted (metainfo)";
439
440
441
        return ;
    }

Marco Bubke's avatar
Marco Bubke committed
442
    if (fxObjectNode.modelNode().metaInfo().isValid() && fxObjectNode.modelNode().metaInfo().hasProperty(propertyName))
443
        if (fxObjectNode.modelNode().metaInfo().propertyTypeName(propertyName) == QLatin1String("QUrl")) { //turn absolute local file paths into relative paths
444
445
446
447
448
449
450
        QString filePath = castedValue.toUrl().toString();
        if (QFileInfo(filePath).exists() && QFileInfo(filePath).isAbsolute()) {
            QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
            castedValue = QUrl(fileDir.relativeFilePath(filePath));
        }
    }

451
452
453
454
455
456
457
        if (castedValue.type() == QVariant::Color) {
            QColor color = castedValue.value<QColor>();
            QColor newColor = QColor(color.name());
            newColor.setAlpha(color.alpha());
            castedValue = QVariant(newColor);
        }

458
459
460
461
        try {
            if (!value->value().isValid()) { //reset
                fxObjectNode.removeVariantProperty(propertyName);
            } else {
462
                if (castedValue.isValid() && !castedValue.isNull()) {
463
                    m_locked = true;
464
465
466
                    fxObjectNode.setVariantProperty(propertyName, castedValue);
                    m_locked = false;
                }
467
468
469
470
            }
        }
        catch (RewritingException &e) {
            QMessageBox::warning(0, "Error", e.description());
471
472
473
474
475
        }
}

void PropertyEditor::changeExpression(const QString &name)
{
476
477
478
479
480
481
    if (name.isNull())
        return;

    if (m_locked)
        return;

482
483
    RewriterTransaction transaction = beginRewriterTransaction();

484
485
486
487
488
    try {
        QString underscoreName(name);
        underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

        QmlObjectNode fxObjectNode(m_selectedNode);
489
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522

        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;
                }
523
            }
524
525
        }

526
527
528
529
        if (!value) {
            qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
            return;
        }
530

531
532
533
        if (value->expression().isEmpty())
            return;

534
535
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
536
537

        transaction.commit(); //committing in the try block
538
539
    }

540
    catch (RewritingException &e) {
541
        QMessageBox::warning(0, "Error", e.description());
542
543
544
    }
}

545
void PropertyEditor::updateSize()
546
{
547
548
549
550
551
    if (!m_currentType)
        return;
    QWidget* frame = m_currentType->m_view->findChild<QWidget*>("propertyEditorFrame");
    if (frame)
        frame->resize(m_stackedWidget->size());
552
553
}

554
555
556
void PropertyEditor::setupPanes()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
557
558
    setupPane("QtQuick.Rectangle");
    setupPane("QtQuick.Text");
559
560
561
562
563
    resetView();
    m_setupCompleted = true;
    QApplication::restoreOverrideCursor();
}

564
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
565
{
566
567
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

568
569
570
    if (!m_selectedNode.isValid())
        return;

571
572
    m_locked = true;

573
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
574
575
        AbstractProperty property = fxObjectNode.modelNode().property(propertyName);
        if (fxObjectNode == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == fxObjectNode) {
576
            if ( !m_selectedNode.hasProperty(propertyName) || m_selectedNode.property(property.name()).isBindingProperty() )
577
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
578
            else
579
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
580
581
        }
    }
582
583

    m_locked = false;
584
585
}

586
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
587
{
588
589
    QmlModelView::transformChanged(fxObjectNode, propertyName);

590
591
592
    if (!m_selectedNode.isValid())
        return;

593
    if (fxObjectNode.isValid() && m_currentType && fxObjectNode == m_selectedNode && fxObjectNode.currentState().isValid()) {
594
595
596
        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))
597
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
598
            else
599
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
600
601
602
603
        }
    }
}

604
605
606
607
608
609
610
611
612
613
614
615
void PropertyEditor::setQmlDir(const QString &qmlDir)
{
    m_qmlDir = qmlDir;

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

void PropertyEditor::delayedResetView()
{
    if (m_timerId == 0)
616
        m_timerId = startTimer(100);
617
618
619
620
621
622
623
624
625
}

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

626
QString templateGeneration(NodeMetaInfo type, NodeMetaInfo superType, const QmlObjectNode &objectNode)
627
{
628
    QString qmlTemplate = QLatin1String("import QtQuick 1.0\nimport Bauhaus 1.0\n");
629
    qmlTemplate += QLatin1String("GroupBox {\n");
630
    qmlTemplate += QString(QLatin1String("caption: \"%1\"\n")).arg(objectNode.modelNode().simplifiedTypeName());
631
632
    qmlTemplate += QLatin1String("layout: VerticalLayout {\n");

633
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
634
    orderedList = type.propertyNames();
635
636
    qSort(orderedList);

637
638
    bool emptyTemplate = true;

639
    foreach (const QString &name, orderedList) {
640
641
642

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

645
        properName.replace('.', '_');
646

647
        QString typeName = type.propertyTypeName(name);
648
649
650
651
        //alias resolution only possible with instance
            if (typeName == QLatin1String("alias") && objectNode.isValid())
                typeName = objectNode.instanceType(name);

652
        if (!superType.hasProperty(name) && type.propertyIsWritable(name)) {
653
            if (typeName == "int") {
654
                qmlTemplate +=  QString(QLatin1String(
655
656
                "IntEditor { backendValue: backendValues.%2\n caption: \"%1\"\nbaseStateFlag: isBaseState\nslider: false\n}"
                )).arg(name).arg(properName);
657
                emptyTemplate = false;
658
            }
659
            if (typeName == "real" || typeName == "double" || typeName == "qreal") {
660
                qmlTemplate +=  QString(QLatin1String(
661
662
                "DoubleSpinBoxAlternate {\ntext: \"%1\"\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\n}\n"
                )).arg(name).arg(properName);
663
                emptyTemplate = false;
664
            }
665
            if (typeName == "string" || typeName == "QString" || typeName == "QUrl" || typeName == "url") {
666
                 qmlTemplate +=  QString(QLatin1String(
667
668
                "QWidget {\nlayout: HorizontalLayout {\nLabel {\ntext: \"%1\"\ntoolTip: \"%1\"\n}\nLineEdit {\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\n}\n}\n}\n"
                )).arg(name).arg(properName);
669
                 emptyTemplate = false;
670
            }
671
            if (typeName == "bool") {
672
                 qmlTemplate +=  QString(QLatin1String(
673
674
                 "QWidget {\nlayout: HorizontalLayout {\nLabel {\ntext: \"%1\"\ntoolTip: \"%1\"\n}\nCheckBox {text: backendValues.%2.value\nbackendValue: backendValues.%2\nbaseStateFlag: isBaseState\ncheckable: true\n}\n}\n}\n"
                 )).arg(name).arg(properName);
675
                 emptyTemplate = false;
676
            }
677
            if (typeName == "color" || typeName == "QColor") {
678
                qmlTemplate +=  QString(QLatin1String(
679
680
                "ColorGroupBox {\ncaption: \"%1\"\nfinished: finishedNotify\nbackendColor: backendValues.%2\n}\n\n"
                )).arg(name).arg(properName);
681
                emptyTemplate = false;
682
            }
683
684
685
686
687
        }
    }
    qmlTemplate += QLatin1String("}\n"); //VerticalLayout
    qmlTemplate += QLatin1String("}\n"); //GroupBox

688
689
690
    if (emptyTemplate)
        return QString();

691
692
693
    return qmlTemplate;
}

694
695
696
697
698
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
699
700
    m_locked = true;

701
702
703
704
705
706
707
708
709
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

710
711
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
712
    QUrl qmlSpecificsFile;
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730

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

732
733
    QString specificQmlData;

734
    if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) {
735
        //do magic !!
736
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode);
737
738
    }

739
    NodeType *type = m_typeHash.value(qmlFile.toString());
740

741
    if (!type) {
742
        type = new NodeType(this);
743
744

        m_stackedWidget->addWidget(type->m_view);
745
        m_typeHash.insert(qmlFile.toString(), type);
746
747
748
749
750
751

        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
            Q_ASSERT(fxObjectNode.isValid());
        }
752
        QDeclarativeContext *ctxt = type->m_view->rootContext();
753
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
754
        ctxt->setContextProperty("finishedNotify", QVariant(false));
755
        if (specificQmlData.isEmpty())
756
757
758
759
            type->m_contextObject->setSpecificQmlData(specificQmlData);
            
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
760
        type->m_view->setSource(qmlFile);
761
762
763
764
765
766
        ctxt->setContextProperty("finishedNotify", QVariant(true));
    } else {
        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
        }
Thomas Hartmann's avatar
Thomas Hartmann committed
767
        QDeclarativeContext *ctxt = type->m_view->rootContext();
768
        
769
        ctxt->setContextProperty("finishedNotify", QVariant(false));
770
        if (specificQmlData.isEmpty())
771
            type->m_contextObject->setSpecificQmlData(specificQmlData);
772
773
        QString currentStateName = currentState().isValid() ? currentState().name() : QLatin1String("invalid state");
        type->setup(fxObjectNode, currentStateName, qmlSpecificsFile, this);
774
775
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
776
777
778
    }

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

780

Thomas Hartmann's avatar
Thomas Hartmann committed
781
    QDeclarativeContext *ctxt = type->m_view->rootContext();
782
    ctxt->setContextProperty("finishedNotify", QVariant(true));
783
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
784
    ctxt->setContextProperty("selectionChanged", QVariant(true));
785
786
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
787

788
789
    m_currentType = type;

790
791
    m_locked = false;

792
793
    if (m_timerId)
        m_timerId = 0;
794
795

    updateSize();
796
797
798
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
799
                                          const QList<ModelNode> &lastSelectedNodeList)
800
801
802
{
    Q_UNUSED(lastSelectedNodeList);

803
    if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
804
        select(ModelNode());
805
    else if (m_selectedNode != selectedNodeList.first())
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
        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
823
824
    m_locked = true;

825
    resetView();
826
827
    if (!m_setupCompleted) {
        m_singleShotTimer->setSingleShot(true);
Thomas Hartmann's avatar
Thomas Hartmann committed
828
        m_singleShotTimer->setInterval(100);
829
830
831
        connect(m_singleShotTimer, SIGNAL(timeout()), this, SLOT(setupPanes()));
        m_singleShotTimer->start();
    }
Thomas Hartmann's avatar
Thomas Hartmann committed
832
833

    m_locked = false;
834
835
836
837
838
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
839
    m_currentType->m_propertyEditorTransaction->end();
840
841
842
843
844

    resetView();
}


845
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
846
{
847
    QmlModelView::propertiesRemoved(propertyList);
848
849
850
851

    if (!m_selectedNode.isValid())
        return;

852
853
854
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

855
    foreach (const AbstractProperty &property, propertyList) {
856
857
858
        ModelNode node(property.parentModelNode());
        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
859
860
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
861
862
863
864
865
866
867
        }
    }
}


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

869
870
871
872
873
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

874
875
876
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

877
878
879
880
881
    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())
882
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
883
            else
884
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
885
886
887
888
889
890
891
892
893
894
895
        }
    }
}

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

    if (!m_selectedNode.isValid())
        return;

896
897
898
       if (!QmlObjectNode(m_selectedNode).isValid())
        return;

899
900
901
902
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
903
904
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
905
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
906
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
907
            else
908
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
909
910
911
912
        }
    }
}

913

914
void PropertyEditor::instanceInformationsChange(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
915
916
917
918
{
    if (!m_selectedNode.isValid())
        return;

919
    m_locked = true;
920
921
    QList<InformationName> informationNameList = informationChangeHash.values(m_selectedNode);
    if (informationNameList.contains(Anchor))
922
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
923
    m_locked = false;
924
925
}

926
927
928
929
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

930
    if (!m_selectedNode.isValid())
931
932
        return;

933
934
935
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

936
    if (node == m_selectedNode) {