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

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

enum {
    debug = false
};

81
82
const int collapseButtonOffset = 114;

83
84
namespace QmlDesigner {

85
86
87
88
89
90
91
92
93
94
95
96
97
#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();
}

98
99
100
101
102
103
104
static QObject *variantToQObject(const QVariant &v)
{
    if (v.userType() == QMetaType::QObjectStar || v.userType() > QMetaType::User)
        return *(QObject **)v.constData();

    return 0;
}
105

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

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

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

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

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

143
144
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    m_contextObject->setIsBaseState(true);

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

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

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

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

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

306
307
308
309
310
311
312
313

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

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

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

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

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

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

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

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

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

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

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

    if (m_locked)
        return;

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

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

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

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

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

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

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

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

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

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

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

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

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

    if (m_locked)
        return;

481
482
    RewriterTransaction transaction = beginRewriterTransaction();

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

        QmlObjectNode fxObjectNode(m_selectedNode);
488
        PropertyEditorValue *value = qobject_cast<PropertyEditorValue*>(variantToQObject(m_currentType->m_backendValuesPropertyMap.value(underscoreName)));
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
520
521

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

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

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

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

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

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

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

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

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

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

570
571
    m_locked = true;

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

    m_locked = false;
583
584
}

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

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

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

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

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

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

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

636
637
    bool emptyTemplate = true;

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

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

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

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

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

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

690
691
692
    return qmlTemplate;
}

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

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

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

    if (m_timerId)
        killTimer(m_timerId);

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

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

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

731
732
    QString specificQmlData;

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

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

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

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

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

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

779

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

787
788
    m_currentType = type;

789
790
    m_locked = false;

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

    updateSize();
795
796
797
}

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

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

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

    m_locked = false;
833
834
835
836
837
}

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

    resetView();
}


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

    if (!m_selectedNode.isValid())
        return;

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

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


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

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

    if (!m_selectedNode.isValid())
        return;

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

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

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

    if (!m_selectedNode.isValid())
        return;

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

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

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

912

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

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

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

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

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

935
    if (node == m_selectedNode) {