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
228
        if (!fxObjectNode.isValid())
            return;
229
        ctxt->setContextProperty("propertyCount", QVariant(fxObjectNode.modelNode().properties().count()));
230
231
232
233
234

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

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

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

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

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

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

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

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

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

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

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

    m_contextObject->setIsBaseState(true);

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

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

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

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

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

303
304
305
306
307
308
309
310

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

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

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

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

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

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

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

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

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

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

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

    if (m_locked)
        return;

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

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

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

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

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

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

    if (value ==0) {
        return;
    }

    QmlObjectNode fxObjectNode(m_selectedNode);

    QVariant castedValue;

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

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

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

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

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

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

    if (m_locked)
        return;

478
479
    RewriterTransaction transaction = beginRewriterTransaction();

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

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

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

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

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

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

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

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

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

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

567
568
    m_locked = true;

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

    m_locked = false;
580
581
}

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

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

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

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

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

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

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

633
634
    bool emptyTemplate = true;

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

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

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

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

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

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

687
688
689
    return qmlTemplate;
}

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

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

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

    if (m_timerId)
        killTimer(m_timerId);

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

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

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

728
729
    QString specificQmlData;

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

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

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

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

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

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

776

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

784
785
    m_currentType = type;

786
787
    m_locked = false;

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

    updateSize();
792
793
794
}

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

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

821
    resetView();
822
823
824
825
826
827
    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
828
829

    m_locked = false;
830
831
832
833
834
}

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

    resetView();
}


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

    if (!m_selectedNode.isValid())
        return;

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

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


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

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

    if (!m_selectedNode.isValid())
        return;

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

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

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

    if (!m_selectedNode.isValid())
        return;

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

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

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

909

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

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

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

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

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

932
    if (node == m_selectedNode) {
933

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