propertyeditor.cpp 38.2 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.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
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.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
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileSystemWatcher>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
Christian Kamm's avatar
Christian Kamm committed
68
#include <QtCore/QTimer>
69
70
#include <QtDeclarative/QDeclarativeView>
#include <QtDeclarative/QDeclarativeContext>
71
72
73
#include <QtGui/QVBoxLayout>
#include <QtGui/QShortcut>
#include <QtGui/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

116
    connect(&m_backendValuesPropertyMap, SIGNAL(valueChanged(const QString&, const QVariant&)), propertyEditor, SLOT(changeValue(const 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);
130
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), propertyMap, SIGNAL(valueChanged(QString, const 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);
149
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), propertyMap, SIGNAL(valueChanged(QString, const 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());
205
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const 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());
214
        QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const 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);
256
    QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const 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");
265
    QObject::connect(valueObject, SIGNAL(valueChanged(QString, const QVariant&)), &m_backendValuesPropertyMap, SIGNAL(valueChanged(QString, const 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
634
    qSort(orderedList);

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

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

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

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

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

    return qmlTemplate;
}

681
682
683
684
685
void PropertyEditor::resetView()
{
    if (model() == 0)
        return;

Thomas Hartmann's avatar
Thomas Hartmann committed
686
687
    m_locked = true;

688
689
690
691
692
693
694
695
696
    if (debug)
        qDebug() << "________________ RELOADING PROPERTY EDITOR QML _______________________";

    if (m_timerId)
        killTimer(m_timerId);

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

697
698
    QString specificsClassName;
    QUrl qmlFile(qmlForNode(m_selectedNode, specificsClassName));
699
700
    QUrl qmlSpecificsFile;
    if (m_selectedNode.isValid())
701
        qmlSpecificsFile = fileToUrl(locateQmlFile(fixTypeNameForPanes(m_selectedNode.type()) + "Specifics.qml"));
702

703
704
    QString specificQmlData;

705
    if (m_selectedNode.isValid() && !QFileInfo(qmlSpecificsFile.toLocalFile()).exists() && m_selectedNode.metaInfo().isValid()) {
706
        //do magic !!
707
        specificQmlData = templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(specificsClassName), m_selectedNode);
708
709
    }

710
    NodeType *type = m_typeHash.value(qmlFile.toString());
711

712
    if (!type) {
713
        type = new NodeType(this);
714
715

        m_stackedWidget->addWidget(type->m_view);
716
        m_typeHash.insert(qmlFile.toString(), type);
717
718
719
720
721
722

        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
            Q_ASSERT(fxObjectNode.isValid());
        }
723
        QDeclarativeContext *ctxt = type->m_view->rootContext();
724
        type->setup(fxObjectNode, currentState().name(), qmlSpecificsFile, this);
725
        ctxt->setContextProperty("finishedNotify", QVariant(false));
726
        if (specificQmlData.isEmpty())
727
728
729
730
            type->m_contextObject->setSpecificQmlData(specificQmlData);
            
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
731
        type->m_view->setSource(qmlFile);
732
733
734
735
736
737
        ctxt->setContextProperty("finishedNotify", QVariant(true));
    } else {
        QmlObjectNode fxObjectNode;
        if (m_selectedNode.isValid()) {
            fxObjectNode = QmlObjectNode(m_selectedNode);
        }
Thomas Hartmann's avatar
Thomas Hartmann committed
738
        QDeclarativeContext *ctxt = type->m_view->rootContext();
739
        
740
        ctxt->setContextProperty("finishedNotify", QVariant(false));
741
        if (specificQmlData.isEmpty())
742
            type->m_contextObject->setSpecificQmlData(specificQmlData);
743
744
        QString currentStateName = currentState().isValid() ? currentState().name() : QLatin1String("invalid state");
        type->setup(fxObjectNode, currentStateName, qmlSpecificsFile, this);
745
746
        type->m_contextObject->setGlobalBaseUrl(qmlFile);
        type->m_contextObject->setSpecificQmlData(specificQmlData);
747
748
749
    }

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

751

Thomas Hartmann's avatar
Thomas Hartmann committed
752
    QDeclarativeContext *ctxt = type->m_view->rootContext();
753
    ctxt->setContextProperty("finishedNotify", QVariant(true));
754
    /*ctxt->setContextProperty("selectionChanged", QVariant(false));
755
    ctxt->setContextProperty("selectionChanged", QVariant(true));
756
757
    ctxt->setContextProperty("selectionChanged", QVariant(false));*/
    type->m_contextObject->triggerSelectionChanged();
758

759
760
    m_currentType = type;

761
762
    m_locked = false;

763
764
    if (m_timerId)
        m_timerId = 0;
765
766

    updateSize();
767
768
769
}

void PropertyEditor::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
770
                                          const QList<ModelNode> &lastSelectedNodeList)
771
772
773
{
    Q_UNUSED(lastSelectedNodeList);

774
    if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1)
775
        select(ModelNode());
776
    else if (m_selectedNode != selectedNodeList.first())
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
        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
794
795
    m_locked = true;

796
    resetView();
797
798
799
800
801
802
    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
803
804

    m_locked = false;
805
806
807
808
809
}

void PropertyEditor::modelAboutToBeDetached(Model *model)
{
    QmlModelView::modelAboutToBeDetached(model);
810
    m_currentType->m_propertyEditorTransaction->end();
811
812
813
814
815

    resetView();
}


816
void PropertyEditor::propertiesRemoved(const QList<AbstractProperty>& propertyList)
817
{
818
    QmlModelView::propertiesRemoved(propertyList);
819
820
821
822

    if (!m_selectedNode.isValid())
        return;

823
824
825
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

826
    foreach (const AbstractProperty &property, propertyList) {
827
828
829
        ModelNode node(property.parentModelNode());
        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
            setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
830
831
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
832
833
834
835
836
837
838
        }
    }
}


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

840
841
842
843
844
    QmlModelView::variantPropertiesChanged(propertyList, propertyChange);

    if (!m_selectedNode.isValid())
        return;

845
846
847
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

848
849
850
851
852
    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())
853
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
854
            else
855
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
856
857
858
859
860
861
862
863
864
865
866
        }
    }
}

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

    if (!m_selectedNode.isValid())
        return;

867
868
869
       if (!QmlObjectNode(m_selectedNode).isValid())
        return;

870
871
872
873
    foreach (const BindingProperty &property, propertyList) {
        ModelNode node(property.parentModelNode());

        if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) {
874
875
            if (property.name().contains("anchor", Qt::CaseInsensitive))
                m_currentType->m_backendAnchorBinding.invalidate(m_selectedNode);
876
            if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty())
877
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).instanceValue(property.name()));
878
            else
879
                setValue(m_selectedNode, property.name(), QmlObjectNode(m_selectedNode).modelValue(property.name()));
880
881
882
883
        }
    }
}

884

885
void PropertyEditor::instanceInformationsChange(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
886
887
888
889
{
    if (!m_selectedNode.isValid())
        return;

890
    m_locked = true;
891
892
    QList<InformationName> informationNameList = informationChangeHash.values(m_selectedNode);
    if (informationNameList.contains(Anchor))
893
        m_currentType->m_backendAnchorBinding.setup(QmlItemNode(m_selectedNode));
894
    m_locked = false;
895
896
}

897
898
899
900
void PropertyEditor::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
{
    QmlModelView::nodeIdChanged(node, newId, oldId);

901
    if (!m_selectedNode.isValid())
902
903
        return;

904
905
906
    if (!QmlObjectNode(m_selectedNode).isValid())
        return;

907
    if (node == m_selectedNode) {
908

909
        if (m_currentType) {
910
            setValue(node, "id", newId);
911
912
        }
    }
913
914
}

Marco Bubke's avatar
Marco Bubke committed
915
916
917
918
919
void PropertyEditor::scriptFunctionsChanged(const ModelNode &node, const QStringList &scriptFunctionList)
{
    QmlModelView::scriptFunctionsChanged(node, scriptFunctionList);
}

920
921
void PropertyEditor::select(const ModelNode &node)
{
922
    if (QmlItemNode(node).isValid())
923
924
925
926
927
928
929
        m_selectedNode = node;
    else
        m_selectedNode = ModelNode();

    delayedResetView();
}

930
QWidget *PropertyEditor::widget()
931
932
933
934
935
{
    delayedResetView();
    return m_stackedWidget;
}