propertyeditor.cpp 38.8 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
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
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.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
31
32
33
34
35
**
**************************************************************************/

#include "propertyeditor.h"

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

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

42
43
44
#include <bindingproperty.h>

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

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

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
75
#include <QDeclarativeEngine>
#include <private/qdeclarativemetatype_p.h>
76
#include <QMessageBox>
77
#include <QApplication>
78
79
#include <QGraphicsOpacityEffect>
#include <QToolBar>
80
81
82
83
84

enum {
    debug = false
};

85
86
const int collapseButtonOffset = 114;

87
88
namespace QmlDesigner {

89
90
91
92
93
94
95
96
97
98
99
100
101
102
#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();
}


103
PropertyEditor::NodeType::NodeType(PropertyEditor *propertyEditor) :
104
        m_view(new DeclarativeWidgetView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
105
        m_contextObject(new PropertyEditorContextObject())
106
107
108
{
    Q_ASSERT(QFileInfo(":/images/button_normal.png").exists());

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

Robert Loehning's avatar
Robert Loehning committed
116
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(QString,QVariant)), propertyEditor, SLOT(changeValue(QString)));
117
118
119
120
121
122
}

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

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

140
141
}

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

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

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

186
void PropertyEditor::NodeType::setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditor *propertyEditor)
187
{
188
    if (!fxObjectNode.isValid()) {
189
        return;
190
    }
191

192
    QDeclarativeContext *ctxt = m_view->rootContext();
193
194

    if (fxObjectNode.isValid()) {
Marco Bubke's avatar
Marco Bubke committed
195
        foreach (const QString &propertyName, fxObjectNode.modelNode().metaInfo().propertyNames())
196
            createPropertyEditorValue(fxObjectNode, propertyName, fxObjectNode.instanceValue(propertyName), &m_backendValuesPropertyMap, propertyEditor);
197
198

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

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

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

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

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

        m_contextObject->setSelectionChanged(false);
236
237
238
239
240
    } else {
        qWarning() << "PropertyEditor: invalid node for setup";
    }
}

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

245
    NodeMetaInfo metaInfo = propertyEditor->model()->metaInfo(typeName, 4, 7);
246

Marco Bubke's avatar
Marco Bubke committed
247
    foreach (const QString &propertyName, metaInfo.propertyNames())
248
        setupPropertyEditorValue(propertyName, &m_backendValuesPropertyMap, propertyEditor, metaInfo.propertyTypeName(propertyName));
249

250
    PropertyEditorValue *valueObject = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_backendValuesPropertyMap.value("className")));
251
252
253
254
255
    if (!valueObject)
        valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
    valueObject->setName("className");

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

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

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

271
272
273
274
275
276
277
278
279
    m_contextObject->setSpecificsUrl(qmlSpecificsFile);

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

    m_contextObject->setIsBaseState(true);

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

    m_contextObject->setGlobalBaseUrl(QUrl());
280
281
}

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

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

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

304
305
306
307
308
309
310
311

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

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

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

338
339
340
void PropertyEditor::setupPane(const QString &typeName)
{

341
    QUrl qmlFile = fileToUrl(locateQmlFile(QLatin1String("Qt/ItemPane.qml")));
342
343
    QUrl qmlSpecificsFile;

344
    qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(typeName) + "Specifics.qml"));
345
    NodeType *type = m_typeHash.value(qmlFile.toString());
346

347
    if (!type) {
348
        type = new NodeType(this);
349

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

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

362
        type->initialSetup(typeName, qmlSpecificsFile, this);
363
364
        ctxt->setContextProperty("finishedNotify", QVariant(true) );
    }
365
366
}

367
void PropertyEditor::changeValue(const QString &propertyName)
368
{
369
    if (propertyName.isNull())
370
        return;
371
372
373
374

    if (m_locked)
        return;

375
    if (propertyName == "type")
376
377
        return;

378
379
380
    if (!m_selectedNode.isValid())
        return;

381
    if (propertyName == "id") {
382
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_currentType->m_backendValuesPropertyMap.value(propertyName)));
383
384
        const QString newId = value->value().toString();

385
386
387
388
        if (newId == m_selectedNode.id())
            return;

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

414
415
416
    //.replace(QLatin1Char('.'), QLatin1Char('_'))
    QString underscoreName(propertyName);
    underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));
417
    PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(QDeclarativeMetaType::toQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
418
419
420
421
422
423
424
425
426

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

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

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

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

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

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

void PropertyEditor::changeExpression(const QString &name)
{
473
474
475
476
477
478
    if (name.isNull())
        return;

    if (m_locked)
        return;

479
480
    RewriterTransaction transaction = beginRewriterTransaction();

481
482
483
484
485
486
487
488
489
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
    try {
        QString underscoreName(name);
        underscoreName.replace(QLatin1Char('.'), QLatin1Char('_'));

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

        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;
                }
520
            }
521
522
        }

523
524
525
526
        if (!value) {
            qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName;
            return;
        }
527

528
529
530
        if (value->expression().isEmpty())
            return;

531
532
        if (fxObjectNode.expression(name) != value->expression() || !fxObjectNode.propertyAffectedByCurrentState(name))
            fxObjectNode.setBindingProperty(name, value->expression());
533
534

        transaction.commit(); //committing in the try block
535
536
    }

537
    catch (RewritingException &e) {
538
        QMessageBox::warning(0, "Error", e.description());
539
540
541
    }
}

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

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

561
void PropertyEditor::otherPropertyChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
562
{
563
564
    QmlModelView::otherPropertyChanged(fxObjectNode, propertyName);

565
566
567
    if (!m_selectedNode.isValid())
        return;

568
569
    m_locked = true;

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

    m_locked = false;
581
582
}

583
void PropertyEditor::transformChanged(const QmlObjectNode &fxObjectNode, const QString &propertyName)
584
{
585
586
    QmlModelView::transformChanged(fxObjectNode, propertyName);

587
588
589
    if (!m_selectedNode.isValid())
        return;

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

601
602
603
604
605
606
607
608
609
610
611
612
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)
613
        m_timerId = startTimer(100);
614
615
616
617
618
619
620
621
622
}

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

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

630
    QList<QString> orderedList;
Marco Bubke's avatar
Marco Bubke committed
631
    orderedList = type.propertyNames();
632
633
    qSort(orderedList);

634
635
    bool emptyTemplate = true;

636
    foreach (const QString &name, orderedList) {
637
638
639

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

642
        properName.replace('.', '_');
643

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

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

685
686
687
    if (emptyTemplate)
        return QString();

688
689
690
    return qmlTemplate;
}

691
692
693
694
695
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
696
697
    m_locked = true;

698
699
700
701
702
703
704
705
706
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

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

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

729
730
    QString specificQmlData;

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

736
    NodeType *type = m_typeHash.value(qmlFile.toString());
737

738
    if (!type) {
739
        type = new NodeType(this);
740
741

        m_stackedWidget->addWidget(type->m_view);
742
        m_typeHash.insert(qmlFile.toString(), type);
743
744
745
746
747
748

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

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

777

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

785
786
    m_currentType = type;

787
788
    m_locked = false;

789
790
    if (m_timerId)
        m_timerId = 0;
791
792

    updateSize();
793
794
795
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
796
                                          const QList<ModelNode> &lastSelectedNodeList)
797
798
799
{
    Q_UNUSED(lastSelectedNodeList);

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

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

    m_locked = false;
831
832
833
834
835
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
836
    m_currentType->m_propertyEditorTransaction->end();
837
838
839
840
841

    resetView();
}


842
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
843
{
844
    QmlModelView::propertiesRemoved(propertyList);
845
846
847
848

    if (!m_selectedNode.isValid())
        return;

849
850
851
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

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


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

866
867
868
869
870
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

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

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

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

    if (!m_selectedNode.isValid())
        return;

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

896
897
898
899
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

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

910

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

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

923
924
925
926
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

927
    if (!m_selectedNode.isValid())
928
929
        return;

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

933
    if (node == m_selectedNode) {
934

935
        if (m_currentType) {
936
            setValue(node, "id", newId);
937
938