1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qt5informationnodeinstanceserver.h"
27 
28 #include <QQuickItem>
29 #include <QQuickView>
30 #include <QDropEvent>
31 #include <QMimeData>
32 
33 #include "servernodeinstance.h"
34 #include "childrenchangeeventfilter.h"
35 #include "propertyabstractcontainer.h"
36 #include "propertybindingcontainer.h"
37 #include "propertyvaluecontainer.h"
38 #include "instancecontainer.h"
39 #include "createinstancescommand.h"
40 #include "changefileurlcommand.h"
41 #include "clearscenecommand.h"
42 #include "reparentinstancescommand.h"
43 #include "update3dviewstatecommand.h"
44 #include "changevaluescommand.h"
45 #include "changebindingscommand.h"
46 #include "changeidscommand.h"
47 #include "removeinstancescommand.h"
48 #include "nodeinstanceclientinterface.h"
49 #include "removepropertiescommand.h"
50 #include "valueschangedcommand.h"
51 #include "informationchangedcommand.h"
52 #include "pixmapchangedcommand.h"
53 #include "commondefines.h"
54 #include "changestatecommand.h"
55 #include "childrenchangedcommand.h"
56 #include "completecomponentcommand.h"
57 #include "componentcompletedcommand.h"
58 #include "createscenecommand.h"
59 #include "tokencommand.h"
60 #include "removesharedmemorycommand.h"
61 #include "objectnodeinstance.h"
62 #include "puppettocreatorcommand.h"
63 #include "inputeventcommand.h"
64 #include "view3dactioncommand.h"
65 #include "requestmodelnodepreviewimagecommand.h"
66 #include "changeauxiliarycommand.h"
67 
68 #include "dummycontextobject.h"
69 #include "../editor3d/generalhelper.h"
70 #include "../editor3d/mousearea3d.h"
71 #include "../editor3d/camerageometry.h"
72 #include "../editor3d/lightgeometry.h"
73 #include "../editor3d/gridgeometry.h"
74 #include "../editor3d/selectionboxgeometry.h"
75 #include "../editor3d/linegeometry.h"
76 #include "../editor3d/icongizmoimageprovider.h"
77 
78 #include <designersupportdelegate.h>
79 #include <qmlprivategate.h>
80 #include <quickitemnodeinstance.h>
81 
82 #include <QVector3D>
83 #include <QQmlProperty>
84 #include <QOpenGLContext>
85 #include <QQuickView>
86 #include <QQmlContext>
87 #include <QQmlEngine>
88 #include <QtGui/qevent.h>
89 #include <QtGui/qguiapplication.h>
90 
91 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
92 #include <QtQuick/private/qquickrendercontrol_p.h>
93 #endif
94 
95 #ifdef QUICK3D_MODULE
96 #include <QtQuick3D/private/qquick3dnode_p.h>
97 #include <QtQuick3D/private/qquick3dcamera_p.h>
98 #include <QtQuick3D/private/qquick3dabstractlight_p.h>
99 #include <QtQuick3D/private/qquick3dviewport_p.h>
100 #include <QtQuick3D/private/qquick3dscenerootnode_p.h>
101 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
102 #include "../editor3d/qt5compat/qquick3darealight_p.h"
103 #endif
104 #endif
105 
106 #ifdef QUICK3D_PARTICLES_MODULE
107 #include <QtQuick3DParticles/private/qquick3dparticlesystem_p.h>
108 #endif
109 
110 #ifdef IMPORT_QUICK3D_ASSETS
111 #include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
112 #endif
113 
114 // Uncomment to display FPS counter on the lower left corner of edit 3D view
115 //#define FPS_COUNTER
116 #ifdef FPS_COUNTER
117 #include <QtCore/qelapsedtimer.h>
118 static QElapsedTimer *_fpsTimer = nullptr;
119 static int _frameCount = 0;
120 #endif
121 
122 namespace QmlDesigner {
123 
objectToVariant(QObject * object)124 static QVariant objectToVariant(QObject *object)
125 {
126     return QVariant::fromValue(object);
127 }
128 
nonVisualComponentPreviewImage()129 static QImage nonVisualComponentPreviewImage()
130 {
131     static double ratio = qgetenv("FORMEDITOR_DEVICE_PIXEL_RATIO").toDouble();
132     if (ratio == 1.) {
133         static const QImage image(":/qtquickplugin/images/non-visual-component.png");
134         return image;
135     } else {
136         static const QImage image(":/qtquickplugin/images/non-visual-component@2x.png");
137         return image;
138     }
139 }
140 
imageHasContent(const QImage & image)141 static bool imageHasContent(const QImage &image)
142 {
143     // Check if any image pixel contains non-zero data
144     const uchar *pData = image.constBits();
145     const qsizetype size = image.sizeInBytes();
146     for (qsizetype i = 0; i < size; ++i) {
147         if (*(pData++) != 0)
148             return true;
149     }
150     return false;
151 }
152 
isQuick3DMode()153 static bool isQuick3DMode()
154 {
155     static bool mode3D = qEnvironmentVariableIsSet("QMLDESIGNER_QUICK3D_MODE");
156     return mode3D;
157 }
158 
toObjectList(const QVariant & variantList)159 static QObjectList toObjectList(const QVariant &variantList)
160 {
161     QObjectList objList;
162     if (!variantList.isNull()) {
163         const auto varList = variantList.value<QVariantList>();
164         for (const auto &var : varList) {
165             QObject *obj = var.value<QObject *>();
166             if (obj)
167                 objList.append(obj);
168         }
169     }
170     return objList;
171 }
172 
toPropertyNameList(const QVariant & variantList)173 static QList<PropertyName> toPropertyNameList(const QVariant &variantList)
174 {
175     QList<PropertyName> propList;
176     if (!variantList.isNull()) {
177         const auto varList = variantList.value<QVariantList>();
178         for (const auto &var : varList) {
179             PropertyName prop = var.toByteArray();
180             if (!prop.isEmpty())
181                 propList.append(prop);
182         }
183     }
184     return propList;
185 }
186 
createAuxiliaryQuickView(const QUrl & url,RenderViewData & viewData)187 void Qt5InformationNodeInstanceServer::createAuxiliaryQuickView(const QUrl &url,
188                                                                 RenderViewData &viewData)
189 {
190 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
191     viewData.window = new QQuickView(quickView()->engine(), quickView());
192     viewData.window->setFormat(quickView()->format());
193     DesignerSupport::createOpenGLContext(static_cast<QQuickView *>(viewData.window.data()));
194 #else
195     viewData.renderControl = new QQuickRenderControl;
196     viewData.window = new QQuickWindow(viewData.renderControl);
197     viewData.renderControl->initialize();
198 #endif
199     QQmlComponent component(engine());
200     component.loadUrl(url);
201     viewData.rootItem = qobject_cast<QQuickItem *>(component.create());
202 
203     if (!viewData.rootItem) {
204         qWarning() << "Could not create view for: " << url.toString() << component.errors();
205         return;
206     }
207 
208 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
209     DesignerSupport::setRootItem(static_cast<QQuickView *>(viewData.window.data()), viewData.rootItem);
210 #else
211     viewData.window->contentItem()->setSize(viewData.rootItem->size());
212     viewData.window->setGeometry(0, 0, viewData.rootItem->width(), viewData.rootItem->height());
213     viewData.rootItem->setParentItem(viewData.window->contentItem());
214 #endif
215 }
216 
updateLockedAndHiddenStates(const QSet<ServerNodeInstance> & instances)217 void Qt5InformationNodeInstanceServer::updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances)
218 {
219     if (!isQuick3DMode())
220         return;
221 
222     // We only want to update the topmost parents in the set
223     for (const auto &instance : instances) {
224         if (instance.isValid()) {
225             const auto parentInst = instance.parent();
226             if (!parentInst.isValid() || !instances.contains(parentInst)) {
227                 handleInstanceHidden(instance, instance.internalInstance()->isHiddenInEditor(), true);
228                 handleInstanceLocked(instance, instance.internalInstance()->isLockedInEditor(), true);
229             }
230         }
231     }
232 }
233 
handleInputEvents()234 void Qt5InformationNodeInstanceServer::handleInputEvents()
235 {
236     if (m_editView3DData.window) {
237         int angleDelta = 0;
238         for (int i = 0; i < m_pendingInputEventCommands.size(); ++i) {
239             const InputEventCommand &command = m_pendingInputEventCommands[i];
240             if (command.type() == QEvent::Wheel) {
241                 if (i < m_pendingInputEventCommands.size() - 1) {
242                     // Peek at next command. If that is also a wheel with same button/modifiers
243                     // state, skip this event and add the angle delta to the next one.
244                     auto nextCommand = m_pendingInputEventCommands[i + 1];
245                     if (nextCommand.type() == QEvent::MouseMove
246                             && nextCommand.button() == command.button()
247                             && nextCommand.buttons() == command.buttons()
248                             && nextCommand.modifiers() == command.modifiers()) {
249                         angleDelta += command.angleDelta();
250                         continue;
251                     }
252                 }
253                 QWheelEvent *we
254 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
255                         = new QWheelEvent(command.pos(), command.pos(), {0, 0},
256                                           {0, angleDelta + command.angleDelta()},
257                                           command.buttons(), command.modifiers(), Qt::NoScrollPhase,
258                                           false);
259 #else
260                         = new QWheelEvent(command.pos(), command.pos(), {0, 0}, {0, command.angleDelta()},
261                                           0, Qt::Horizontal, command.buttons(), command.modifiers(),
262                                           Qt::NoScrollPhase, Qt::MouseEventNotSynthesized);
263 #endif
264                 angleDelta = 0;
265                 QGuiApplication::sendEvent(m_editView3DData.window, we);
266             } else {
267                 if (command.type() == QEvent::MouseMove && i < m_pendingInputEventCommands.size() - 1) {
268                     // Peek at next command. If that is also a move with only difference being
269                     // the position, skip this event as it is pointless
270                     auto nextCommand = m_pendingInputEventCommands[i + 1];
271                     if (nextCommand.type() == QEvent::MouseMove
272                             && nextCommand.button() == command.button()
273                             && nextCommand.buttons() == command.buttons()
274                             && nextCommand.modifiers() == command.modifiers()) {
275                         continue;
276                     }
277                 }
278                 auto me = new QMouseEvent(command.type(), command.pos(), command.button(),
279                                           command.buttons(), command.modifiers());
280                 // We must use sendEvent in Qt 6, as using postEvent allows the associated position
281                 // data stored internally in QMutableEventPoint to potentially be updated by system
282                 // before the event is delivered.
283                 QGuiApplication::sendEvent(m_editView3DData.window, me);
284             }
285         }
286 
287         m_pendingInputEventCommands.clear();
288 
289         render3DEditView();
290     }
291 }
292 
resolveImportSupport()293 void Qt5InformationNodeInstanceServer::resolveImportSupport()
294 {
295 #ifdef IMPORT_QUICK3D_ASSETS
296     QSSGAssetImportManager importManager;
297     const QHash<QString, QStringList> supportedExtensions = importManager.getSupportedExtensions();
298     const QHash<QString, QVariantMap> supportedOptions = importManager.getAllOptions();
299 
300     QVariantMap supportMap;
301 
302     QVariantMap extMap;
303     auto itExt = supportedExtensions.constBegin();
304     while (itExt != supportedExtensions.constEnd()) {
305         extMap.insert(itExt.key(), itExt.value());
306         ++itExt;
307     }
308 
309     QVariantMap optMap;
310     auto itOpt = supportedOptions.constBegin();
311     while (itOpt != supportedOptions.constEnd()) {
312         optMap.insert(itOpt.key(), itOpt.value());
313         ++itOpt;
314     }
315 
316     supportMap.insert("options", optMap);
317     supportMap.insert("extensions", extMap);
318     nodeInstanceClient()->handlePuppetToCreatorCommand(
319                 {PuppetToCreatorCommand::Import3DSupport, QVariant(supportMap)});
320 
321 #endif
322 }
323 
updateRotationBlocks(const QVector<PropertyValueContainer> & valueChanges)324 void Qt5InformationNodeInstanceServer::updateRotationBlocks(const QVector<PropertyValueContainer> &valueChanges)
325 {
326 #ifdef QUICK3D_MODULE
327     auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
328     if (helper) {
329         QSet<QQuick3DNode *> blockedNodes;
330         QSet<QQuick3DNode *> unblockedNodes;
331         const PropertyName propName = "rotBlocked@internal";
332         for (const auto &container : valueChanges) {
333             if (container.name() == propName) {
334                 ServerNodeInstance instance = instanceForId(container.instanceId());
335                 if (instance.isValid()) {
336                     auto node = qobject_cast<QQuick3DNode *>(instance.internalObject());
337                     if (node) {
338                         if (container.value().toBool())
339                             blockedNodes.insert(node);
340                         else
341                             unblockedNodes.insert(node);
342                     }
343                 }
344             }
345         }
346         helper->addRotationBlocks(blockedNodes);
347         helper->removeRotationBlocks(unblockedNodes);
348     }
349 #else
350     Q_UNUSED(valueChanges)
351 #endif
352 }
353 
removeRotationBlocks(const QVector<qint32> & instanceIds)354 void Qt5InformationNodeInstanceServer::removeRotationBlocks(const QVector<qint32> &instanceIds)
355 {
356 #ifdef QUICK3D_MODULE
357     auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
358     if (helper) {
359         QSet<QQuick3DNode *> unblockedNodes;
360         for (const auto &id : instanceIds) {
361             ServerNodeInstance instance = instanceForId(id);
362             if (instance.isValid()) {
363                 auto node = qobject_cast<QQuick3DNode *>(instance.internalObject());
364                 if (node)
365                     unblockedNodes.insert(node);
366             }
367         }
368         helper->removeRotationBlocks(unblockedNodes);
369     }
370 #else
371     Q_UNUSED(instanceIds)
372 #endif
373 }
374 
createEditView3D()375 void Qt5InformationNodeInstanceServer::createEditView3D()
376 {
377 #ifdef QUICK3D_MODULE
378     qmlRegisterRevision<QQuick3DNode, 1>("MouseArea3D", 1, 0);
379     qmlRegisterType<QmlDesigner::Internal::MouseArea3D>("MouseArea3D", 1, 0, "MouseArea3D");
380     qmlRegisterUncreatableType<QmlDesigner::Internal::GeometryBase>("GeometryBase", 1, 0, "GeometryBase",
381                                                                     "Abstract Base Class");
382     qmlRegisterType<QmlDesigner::Internal::CameraGeometry>("CameraGeometry", 1, 0, "CameraGeometry");
383     qmlRegisterType<QmlDesigner::Internal::LightGeometry>("LightUtils", 1, 0, "LightGeometry");
384     qmlRegisterType<QmlDesigner::Internal::GridGeometry>("GridGeometry", 1, 0, "GridGeometry");
385     qmlRegisterType<QmlDesigner::Internal::SelectionBoxGeometry>("SelectionBoxGeometry", 1, 0, "SelectionBoxGeometry");
386     qmlRegisterType<QmlDesigner::Internal::LineGeometry>("LineGeometry", 1, 0, "LineGeometry");
387 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
388     qmlRegisterType<QmlDesigner::Internal::QQuick3DAreaLight>("LightUtils", 1, 0, "AreaLight");
389 #endif
390 
391     auto helper = new QmlDesigner::Internal::GeneralHelper();
392     QObject::connect(helper, &QmlDesigner::Internal::GeneralHelper::toolStateChanged,
393                      this, &Qt5InformationNodeInstanceServer::handleToolStateChanged);
394     engine()->rootContext()->setContextProperty("_generalHelper", helper);
395     engine()->addImageProvider(QLatin1String("IconGizmoImageProvider"),
396                                new QmlDesigner::Internal::IconGizmoImageProvider);
397     m_3dHelper = helper;
398 
399 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
400     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/EditView3D.qml"), m_editView3DData);
401 #else
402     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/EditView3D.qml"), m_editView3DData);
403 #endif
404     if (m_editView3DData.rootItem)
405         helper->setParent(m_editView3DData.rootItem);
406 #endif
407 }
408 
409 // The selection has changed in the edit view 3D. Empty list indicates selection is cleared.
handleSelectionChanged(const QVariant & objs)410 void Qt5InformationNodeInstanceServer::handleSelectionChanged(const QVariant &objs)
411 {
412     QList<ServerNodeInstance> instanceList;
413     const QVariantList varObjs = objs.value<QVariantList>();
414     for (const auto &object : varObjs) {
415         auto obj = object.value<QObject *>();
416         if (obj) {
417             ServerNodeInstance instance = instanceForObject(obj);
418             instanceList << instance;
419         }
420     }
421     selectInstances(instanceList);
422     // Hold selection changes reflected back from designer for a bit
423     m_selectionChangeTimer.start(500);
424 }
425 
426 QVector<Qt5InformationNodeInstanceServer::InstancePropertyValueTriple>
propertyToPropertyValueTriples(const ServerNodeInstance & instance,const PropertyName & propertyName,const QVariant & variant)427 Qt5InformationNodeInstanceServer::propertyToPropertyValueTriples(
428     const ServerNodeInstance &instance,
429     const PropertyName &propertyName,
430     const QVariant &variant)
431 {
432     QVector<InstancePropertyValueTriple> result;
433     InstancePropertyValueTriple propTriple;
434 
435     if (variant.type() == QVariant::Vector3D) {
436         auto vector3d = variant.value<QVector3D>();
437 
438         if (vector3d.isNull())
439             return result;
440 
441         const PropertyName dot = propertyName.isEmpty() ? "" : ".";
442         propTriple.instance = instance;
443         propTriple.propertyName = propertyName + dot + PropertyName("x");
444         propTriple.propertyValue = vector3d.x();
445         result.append(propTriple);
446         propTriple.propertyName = propertyName + dot + PropertyName("y");
447         propTriple.propertyValue = vector3d.y();
448         result.append(propTriple);
449         propTriple.propertyName = propertyName + dot + PropertyName("z");
450         propTriple.propertyValue = vector3d.z();
451         result.append(propTriple);
452     } else {
453         propTriple.instance = instance;
454         propTriple.propertyName = propertyName;
455         propTriple.propertyValue = variant;
456         result.append(propTriple);
457     }
458 
459     return result;
460 }
461 
modifyVariantValue(const QObjectList & objects,const QList<PropertyName> & propNames,ValuesModifiedCommand::TransactionOption option)462 void Qt5InformationNodeInstanceServer::modifyVariantValue(const QObjectList &objects,
463     const QList<PropertyName> &propNames, ValuesModifiedCommand::TransactionOption option)
464 {
465     struct PropNamePair {
466         PropertyName origPropName;
467         PropertyName targetPropName;
468     };
469 
470     QList<PropNamePair> propNamePairs;
471 
472     // Position is a special case, because the position can be 'position.x 'or simply 'x'.
473     // We prefer 'x'.
474     for (const auto &propName : propNames) {
475         if (propName != "position")
476             propNamePairs.append({propName, propName});
477         else
478             propNamePairs.append({propName, PropertyName{}});
479     }
480 
481     if (!objects.isEmpty()) {
482         QVector<PropertyValueContainer> valueVector;
483         for (const auto listObj : objects) {
484             ServerNodeInstance instance = instanceForObject(listObj);
485             if (instance.isValid()) {
486                 const qint32 instId = instance.instanceId();
487                 if (option == ValuesModifiedCommand::TransactionOption::Start)
488                     instance.setModifiedFlag(true);
489                 else if (option == ValuesModifiedCommand::TransactionOption::End)
490                     instance.setModifiedFlag(false);
491                 for (const auto &propNamePair : propNamePairs) {
492                     // We do have to split vector3d props into foobar.x, foobar.y, foobar.z
493                     const QVector<InstancePropertyValueTriple> triple
494                             = propertyToPropertyValueTriples(instance, propNamePair.targetPropName,
495                                                              listObj->property(propNamePair.origPropName));
496                     for (const auto &property : triple) {
497                         const PropertyName propName = property.propertyName;
498                         const QVariant propValue = property.propertyValue;
499                         valueVector.append(PropertyValueContainer(instId, propName,
500                                                                   propValue, PropertyName()));
501                     }
502                 }
503             }
504         }
505 
506         if (!valueVector.isEmpty()) {
507             ValuesModifiedCommand command(valueVector);
508             command.transactionOption = option;
509             nodeInstanceClient()->valuesModified(command);
510         }
511     }
512 }
513 
handleObjectPropertyCommit(const QVariant & objects,const QVariant & propNames)514 void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &objects,
515                                                                   const QVariant &propNames)
516 {
517     modifyVariantValue(toObjectList(objects), toPropertyNameList(propNames),
518                        ValuesModifiedCommand::TransactionOption::End);
519     m_changedNodes.clear();
520     m_changedProperties.clear();
521     m_propertyChangeTimer.stop();
522 }
523 
handleObjectPropertyChange(const QVariant & objects,const QVariant & propNames)524 void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &objects,
525                                                                   const QVariant &propNames)
526 {
527     QObjectList objList = toObjectList(objects);
528     QList<PropertyName> propList = toPropertyNameList(propNames);
529 
530     bool nodeChanged = true;
531     if (objList.size() == m_changedNodes.size()) {
532         nodeChanged = false;
533         for (int i = 0; i < objList.size(); ++i) {
534             if (objList[i] != m_changedNodes[i]) {
535                 nodeChanged = true;
536                 break;
537             }
538         }
539     }
540     if (!nodeChanged && propList.size() == m_changedProperties.size()) {
541         for (int i = 0; i < propList.size(); ++i) {
542             if (propList[i] != m_changedProperties[i]) {
543                 nodeChanged = true;
544                 break;
545             }
546         }
547     }
548 
549     if (nodeChanged) {
550         if (!m_changedNodes.isEmpty()) {
551             // Nodes/properties changed, commit currently pending changes
552             modifyVariantValue(m_changedNodes, m_changedProperties,
553                                ValuesModifiedCommand::TransactionOption::End);
554             m_changedNodes.clear();
555             m_changedProperties.clear();
556             m_propertyChangeTimer.stop();
557         }
558         modifyVariantValue(objList, propList, ValuesModifiedCommand::TransactionOption::Start);
559     } else if (!m_propertyChangeTimer.isActive()) {
560         m_propertyChangeTimer.start();
561     }
562     m_changedNodes = objList;
563     m_changedProperties = propList;
564 }
565 
handleActiveSceneChange()566 void Qt5InformationNodeInstanceServer::handleActiveSceneChange()
567 {
568 #ifdef QUICK3D_MODULE
569     ServerNodeInstance sceneInstance = active3DSceneInstance();
570     const QString sceneId = sceneInstance.id();
571 
572     QVariantMap toolStates;
573     auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
574     if (helper)
575         toolStates = helper->getToolStates(sceneId);
576     toolStates.insert("sceneInstanceId", QVariant::fromValue(sceneInstance.instanceId()));
577 
578     nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ActiveSceneChanged,
579                                                         toolStates});
580     m_selectionChangeTimer.start(0);
581 #endif
582 }
583 
handleToolStateChanged(const QString & sceneId,const QString & tool,const QVariant & toolState)584 void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &sceneId,
585                                                               const QString &tool,
586                                                               const QVariant &toolState)
587 {
588     QVariantList data;
589     data << sceneId;
590     data << tool;
591     data << toolState;
592     nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Edit3DToolState,
593                                                         QVariant::fromValue(data)});
594 }
595 
handleView3DSizeChange()596 void Qt5InformationNodeInstanceServer::handleView3DSizeChange()
597 {
598     QObject *view3D = sender();
599     if (view3D == m_active3DView)
600         updateView3DRect(view3D);
601 }
602 
handleView3DDestroyed(QObject * obj)603 void Qt5InformationNodeInstanceServer::handleView3DDestroyed(QObject *obj)
604 {
605 #ifdef QUICK3D_MODULE
606     auto view = qobject_cast<QQuick3DViewport *>(obj);
607     m_view3Ds.remove(obj);
608     removeNode3D(view->scene());
609     if (view && view == m_active3DView)
610         m_active3DView = nullptr;
611 #else
612     Q_UNUSED(obj)
613 #endif
614 }
615 
handleNode3DDestroyed(QObject * obj)616 void Qt5InformationNodeInstanceServer::handleNode3DDestroyed(QObject *obj)
617 {
618 #ifdef QUICK3D_MODULE
619     if (qobject_cast<QQuick3DCamera *>(obj)) {
620         QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseCameraGizmo",
621                                   Q_ARG(QVariant, objectToVariant(obj)));
622     } else if (qobject_cast<QQuick3DAbstractLight *>(obj)) {
623         QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseLightGizmo",
624                                   Q_ARG(QVariant, objectToVariant(obj)));
625 #ifdef QUICK3D_PARTICLES_MODULE
626     } else if (qobject_cast<QQuick3DParticleSystem *>(obj)) {
627         QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseParticleSystemGizmo",
628                                   Q_ARG(QVariant, objectToVariant(obj)));
629 #endif
630     }
631     removeNode3D(obj);
632 #else
633     Q_UNUSED(obj)
634 #endif
635 }
636 
updateView3DRect(QObject * view3D)637 void Qt5InformationNodeInstanceServer::updateView3DRect(QObject *view3D)
638 {
639     QRectF viewPortrect(0., 0., 1000., 1000.);
640     if (view3D) {
641         viewPortrect = QRectF(0., 0., view3D->property("width").toDouble(),
642                               view3D->property("height").toDouble());
643     }
644     QQmlProperty viewPortProperty(m_editView3DData.rootItem, "viewPortRect", context());
645     viewPortProperty.write(viewPortrect);
646 }
647 
updateActiveSceneToEditView3D()648 void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D()
649 {
650 #ifdef QUICK3D_MODULE
651     if (!m_editView3DSetupDone)
652         return;
653 
654     QVariant activeSceneVar = objectToVariant(m_active3DScene);
655 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
656     // Active scene change handling on qml side is async, so a deleted importScene would crash
657     // editView when it updates next. Disable/enable edit view update synchronously to avoid this.
658     QMetaObject::invokeMethod(m_editView3DData.rootItem, "enableEditViewUpdate",
659                               Q_ARG(QVariant, activeSceneVar));
660 #endif
661     ServerNodeInstance sceneInstance = active3DSceneInstance();
662     const QString sceneId = sceneInstance.id();
663 
664     // QML item id is updated with separate call, so delay this update until we have it
665     if (m_active3DScene && sceneId.isEmpty()) {
666         m_active3DSceneUpdatePending = true;
667         return;
668     } else {
669         m_active3DSceneUpdatePending = false;
670     }
671 
672     QMetaObject::invokeMethod(m_editView3DData.rootItem, "setActiveScene", Qt::QueuedConnection,
673                               Q_ARG(QVariant, activeSceneVar),
674                               Q_ARG(QVariant, QVariant::fromValue(sceneId)));
675 
676     updateView3DRect(m_active3DView);
677 
678     auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
679     if (helper)
680         helper->storeToolState(helper->globalStateId(), helper->lastSceneIdKey(), QVariant(sceneId), 0);
681 #endif
682 }
683 
removeNode3D(QObject * node)684 void Qt5InformationNodeInstanceServer::removeNode3D(QObject *node)
685 {
686     m_3DSceneMap.remove(node);
687     const auto oldMap = m_3DSceneMap;
688     auto it = oldMap.constBegin();
689     while (it != oldMap.constEnd()) {
690         if (it.value() == node) {
691             m_3DSceneMap.remove(it.key(), node);
692             break;
693         }
694         ++it;
695     }
696     if (node == m_active3DScene) {
697         m_active3DScene = nullptr;
698         m_active3DView = nullptr;
699         updateActiveSceneToEditView3D();
700     }
701 }
702 
resolveSceneRoots()703 void Qt5InformationNodeInstanceServer::resolveSceneRoots()
704 {
705 #ifdef QUICK3D_MODULE
706     if (!m_editView3DSetupDone)
707         return;
708 
709     const auto oldMap = m_3DSceneMap;
710     m_3DSceneMap.clear();
711     auto it = oldMap.begin();
712     bool updateActiveScene = !m_active3DScene;
713     while (it != oldMap.end()) {
714         QObject *node = it.value();
715         QObject *newRoot = find3DSceneRoot(node);
716         QObject *oldRoot = it.key();
717         if (!m_active3DScene || (newRoot != oldRoot && m_active3DScene == oldRoot)) {
718             m_active3DScene = newRoot;
719             updateActiveScene = true;
720         }
721         m_3DSceneMap.insert(newRoot, node);
722 
723         if (newRoot != oldRoot) {
724             if (qobject_cast<QQuick3DCamera *>(node)) {
725                 QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateCameraGizmoScene",
726                                           Q_ARG(QVariant, objectToVariant(newRoot)),
727                                           Q_ARG(QVariant, objectToVariant(node)));
728             } else if (qobject_cast<QQuick3DAbstractLight *>(node)) {
729                 QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateLightGizmoScene",
730                                           Q_ARG(QVariant, objectToVariant(newRoot)),
731                                           Q_ARG(QVariant, objectToVariant(node)));
732 #ifdef QUICK3D_PARTICLES_MODULE
733             } else if (qobject_cast<QQuick3DParticleSystem *>(node)) {
734                 QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateParticleSystemGizmoScene",
735                                           Q_ARG(QVariant, objectToVariant(newRoot)),
736                                           Q_ARG(QVariant, objectToVariant(node)));
737 #endif
738             }
739         }
740         ++it;
741     }
742     if (updateActiveScene) {
743         m_active3DView = findView3DForSceneRoot(m_active3DScene);
744         updateActiveSceneToEditView3D();
745     }
746 #endif
747 }
748 
active3DSceneInstance() const749 ServerNodeInstance Qt5InformationNodeInstanceServer::active3DSceneInstance() const
750 {
751     ServerNodeInstance sceneInstance;
752     if (hasInstanceForObject(m_active3DScene))
753         sceneInstance = instanceForObject(m_active3DScene);
754     else if (hasInstanceForObject(m_active3DView))
755         sceneInstance = instanceForObject(m_active3DView);
756     return sceneInstance;
757 }
758 
updateNodesRecursive(QQuickItem * item)759 void Qt5InformationNodeInstanceServer::updateNodesRecursive(QQuickItem *item)
760 {
761     const auto childItems = item->childItems();
762     for (QQuickItem *childItem : childItems)
763         updateNodesRecursive(childItem);
764 
765     if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) {
766         if (item->flags() & QQuickItem::ItemHasContents)
767             item->update();
768     } else {
769         DesignerSupport::updateDirtyNode(item);
770     }
771 }
772 
getContentItemForRendering(QQuickItem * rootItem)773 QQuickItem *Qt5InformationNodeInstanceServer::getContentItemForRendering(QQuickItem *rootItem)
774 {
775     QQuickItem *contentItem = QQmlProperty::read(rootItem, "contentItem").value<QQuickItem *>();
776     if (contentItem) {
777         if (!Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6())
778             designerSupport()->refFromEffectItem(contentItem, false);
779         QmlDesigner::Internal::QmlPrivateGate::disableNativeTextRendering(contentItem);
780     }
781     return contentItem;
782 }
783 
render3DEditView(int count)784 void Qt5InformationNodeInstanceServer::render3DEditView(int count)
785 {
786     m_need3DEditViewRender = qMax(count, m_need3DEditViewRender);
787     if (!m_render3DEditViewTimer.isActive())
788         m_render3DEditViewTimer.start(0);
789 }
790 
791 // render the 3D edit view and send the result to creator process
doRender3DEditView()792 void Qt5InformationNodeInstanceServer::doRender3DEditView()
793 {
794     if (m_editView3DSetupDone) {
795         if (!m_editView3DData.contentItem)
796             m_editView3DData.contentItem = getContentItemForRendering(m_editView3DData.rootItem);
797 
798         QImage renderImage;
799 
800         updateNodesRecursive(m_editView3DData.contentItem);
801 
802 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
803         if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
804             renderImage = m_editView3DData.window->grabWindow();
805         } else {
806             // Fake render loop signaling to update things like QML items as 3D textures
807             m_editView3DData.window->beforeSynchronizing();
808             m_editView3DData.window->beforeRendering();
809 
810             QSizeF size = qobject_cast<QQuickItem *>(m_editView3DData.contentItem)->size();
811             QRectF renderRect(QPointF(0., 0.), size);
812             renderImage = designerSupport()->renderImageForItem(m_editView3DData.contentItem,
813                                                                 renderRect, size.toSize());
814 
815             m_editView3DData.window->afterRendering();
816         }
817 #else
818         renderImage = grabRenderControl(m_editView3DData);
819 #endif
820 
821         // There's no instance related to image, so instance id is -1.
822         // Key number is selected so that it is unlikely to conflict other ImageContainer use.
823         auto imgContainer = ImageContainer(-1, renderImage, 2100000000);
824 
825         // send the rendered image to creator process
826         nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Render3DView,
827                                                             QVariant::fromValue(imgContainer)});
828         if (m_need3DEditViewRender > 0) {
829             m_render3DEditViewTimer.start(0);
830             --m_need3DEditViewRender;
831         }
832 #ifdef FPS_COUNTER
833         // Force constant rendering for accurate fps count
834         if (!m_render3DEditViewTimer.isActive())
835             m_render3DEditViewTimer.start(0);
836         ++_frameCount;
837         if (_fpsTimer->elapsed() > 1000) {
838             QQmlProperty fpsProperty(m_editView3DData.rootItem, "fps", context());
839             fpsProperty.write(_frameCount);
840             _frameCount = 0;
841             _fpsTimer->start();
842         }
843 #endif
844     }
845 }
846 
renderModelNodeImageView()847 void Qt5InformationNodeInstanceServer::renderModelNodeImageView()
848 {
849     if (!m_renderModelNodeImageViewTimer.isActive())
850         m_renderModelNodeImageViewTimer.start(0);
851 }
852 
doRenderModelNodeImageView()853 void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView()
854 {
855     // This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled
856 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
857     ServerNodeInstance instance;
858     if (m_modelNodePreviewImageCommand.renderItemId() >= 0)
859         instance = instanceForId(m_modelNodePreviewImageCommand.renderItemId());
860     else
861         instance = instanceForId(m_modelNodePreviewImageCommand.instanceId());
862 
863     if (instance.isSubclassOf("QQuick3DObject"))
864         doRenderModelNode3DImageView();
865     else if (instance.isSubclassOf("QQuickItem"))
866         doRenderModelNode2DImageView();
867 #endif
868 }
869 
doRenderModelNode3DImageView()870 void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
871 {
872 #ifdef QUICK3D_MODULE
873     if (m_modelNode3DImageViewData.rootItem) {
874         if (!m_modelNode3DImageViewData.contentItem)
875             m_modelNode3DImageViewData.contentItem = getContentItemForRendering(m_modelNode3DImageViewData.rootItem);
876 
877         // Key number is selected so that it is unlikely to conflict other ImageContainer use.
878         auto imgContainer = ImageContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001);
879         QImage renderImage;
880         if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) {
881             renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()];
882         } else {
883             QObject *instanceObj = nullptr;
884             bool createdFromComponent = false;
885             ServerNodeInstance instance = instanceForId(m_modelNodePreviewImageCommand.instanceId());
886             if (!m_modelNodePreviewImageCommand.componentPath().isEmpty()
887                     && instance.isSubclassOf("QQuick3DNode")) {
888                 // Create a new instance for Node components, as using Nodes in multiple
889                 // import scenes simultaneously isn't supported. And even if it was, we still
890                 // wouldn't want the children of the Node to appear in the preview.
891                 QQmlComponent component(engine());
892                 component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath()));
893                 instanceObj = qobject_cast<QQuick3DObject *>(component.create());
894                 if (!instanceObj) {
895                     qWarning() << "Could not create preview component: " << component.errors();
896                     return;
897                 }
898                 createdFromComponent = true;
899             } else {
900                 instanceObj = instance.internalObject();
901             }
902             QSize renderSize = m_modelNodePreviewImageCommand.size();
903             if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) {
904                 // Requested size is already adjusted for target pixel ratio, so we have to adjust
905                 // back if ratio is not default for our window.
906                 double ratio = m_modelNode3DImageViewData.window->devicePixelRatio();
907                 renderSize.setWidth(qRound(qreal(renderSize.width()) / ratio));
908                 renderSize.setHeight(qRound(qreal(renderSize.height()) / ratio));
909             }
910 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
911             m_modelNode3DImageViewData.bufferDirty = m_modelNode3DImageViewData.bufferDirty
912                     || m_modelNode3DImageViewData.rootItem->width() != renderSize.width()
913                     || m_modelNode3DImageViewData.rootItem->height() != renderSize.height();
914 #endif
915 
916             m_modelNode3DImageViewData.window->resize(renderSize);
917             m_modelNode3DImageViewData.rootItem->setSize(renderSize);
918 
919             QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "createViewForObject",
920                                       Q_ARG(QVariant, objectToVariant(instanceObj)));
921 
922             bool ready = false;
923             int count = 0; // Ensure we don't ever get stuck in an infinite loop
924             while (!ready && ++count < 10) {
925                 updateNodesRecursive(m_modelNode3DImageViewData.contentItem);
926 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
927                 if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
928                     renderImage = m_modelNode3DImageViewData.window->grabWindow();
929                 } else {
930                     // Fake render loop signaling to update things like QML items as 3D textures
931                     m_modelNode3DImageViewData.window->beforeSynchronizing();
932                     m_modelNode3DImageViewData.window->beforeRendering();
933 
934                     QSizeF size = qobject_cast<QQuickItem *>(m_modelNode3DImageViewData.contentItem)->size();
935                     QRectF renderRect(QPointF(0., 0.), size);
936                     renderImage = designerSupport()->renderImageForItem(m_modelNode3DImageViewData.contentItem,
937                                                                         renderRect, size.toSize());
938 
939                     m_modelNode3DImageViewData.window->afterRendering();
940                 }
941 #else
942                 renderImage = grabRenderControl(m_modelNode3DImageViewData);
943 #endif
944                 QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "afterRender");
945                 ready = QQmlProperty::read(m_modelNode3DImageViewData.rootItem, "ready").value<bool>();
946             }
947             QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "destroyView");
948             if (createdFromComponent) {
949                 // If component changes, puppet will need a reset anyway, so we can cache the image
950                 m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), renderImage);
951                 delete instanceObj;
952             }
953         }
954 
955         if (!renderImage.isNull()) {
956             imgContainer.setImage(renderImage);
957 
958             // send the rendered image to creator process
959             nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::RenderModelNodePreviewImage,
960                                                                 QVariant::fromValue(imgContainer)});
961         }
962     }
963 #endif
964 }
965 
itemBoundingRect(QQuickItem * item)966 static QRectF itemBoundingRect(QQuickItem *item)
967 {
968     QRectF itemRect;
969     if (item) {
970         itemRect = item->boundingRect();
971         if (item->clip()) {
972             return itemRect;
973         } else {
974             const auto childItems = item->childItems();
975             for (const auto &childItem : childItems) {
976                 QRectF mappedRect = childItem->mapRectToItem(item, itemBoundingRect(childItem));
977                 // Sanity check for size
978                 if (mappedRect.isValid() && (mappedRect.width() < 10000) && (mappedRect.height() < 10000))
979                     itemRect = itemRect.united(mappedRect);
980             }
981         }
982     }
983     return itemRect;
984 }
985 
doRenderModelNode2DImageView()986 void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView()
987 {
988     if (m_modelNode2DImageViewData.rootItem) {
989         if (!m_modelNode2DImageViewData.contentItem)
990             m_modelNode2DImageViewData.contentItem = getContentItemForRendering(m_modelNode2DImageViewData.rootItem);
991 
992         // Key number is the same as in 3D case as they produce image for same purpose
993         auto imgContainer = ImageContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001);
994         QImage renderImage;
995         if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) {
996             renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()];
997         } else {
998             QQuickItem *instanceItem = nullptr;
999 
1000             if (!m_modelNodePreviewImageCommand.componentPath().isEmpty()) {
1001                 QQmlComponent component(engine());
1002                 component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath()));
1003                 instanceItem = qobject_cast<QQuickItem *>(component.create());
1004                 if (!instanceItem) {
1005                     qWarning() << "Could not create preview component: " << component.errors();
1006                     return;
1007                 }
1008             } else {
1009                 qWarning() << "2D image preview is not supported for non-components.";
1010                 return;
1011             }
1012 
1013             instanceItem->setParentItem(m_modelNode2DImageViewData.contentItem);
1014 
1015             // Some component may expect to always be shown at certain size, so their layouts may
1016             // not support scaling, so let's always render at the default size if item has one and
1017             // scale the resulting image instead.
1018             QSize finalSize = m_modelNodePreviewImageCommand.size();
1019             QRectF renderRect = itemBoundingRect(instanceItem);
1020             QSize renderSize = renderRect.size().toSize();
1021             if (renderSize.isEmpty()) {
1022                 renderSize = finalSize;
1023                 renderRect = QRectF(QPointF(0., 0.), QSizeF(renderSize));
1024             }
1025 
1026 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1027             m_modelNode2DImageViewData.bufferDirty = m_modelNode2DImageViewData.bufferDirty
1028                     || m_modelNode2DImageViewData.rootItem->width() != renderSize.width()
1029                     || m_modelNode2DImageViewData.rootItem->height() != renderSize.height();
1030 #endif
1031 
1032             m_modelNode2DImageViewData.window->resize(renderSize);
1033             m_modelNode2DImageViewData.rootItem->setSize(renderSize);
1034             m_modelNode2DImageViewData.contentItem->setPosition(QPointF(-renderRect.x(), -renderRect.y()));
1035 
1036             updateNodesRecursive(m_modelNode2DImageViewData.contentItem);
1037 
1038 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1039                 if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
1040                     renderImage = m_modelNode2DImageViewData.window->grabWindow();
1041                 } else {
1042                     renderImage = designerSupport()->renderImageForItem(m_modelNode2DImageViewData.contentItem,
1043                                                                         renderRect, renderSize);
1044                 }
1045 #else
1046                 renderImage = grabRenderControl(m_modelNode2DImageViewData);
1047 #endif
1048 
1049             if (!imageHasContent(renderImage))
1050                 renderImage = nonVisualComponentPreviewImage();
1051 
1052             if (renderSize != finalSize)
1053                 renderImage = renderImage.scaled(finalSize, Qt::KeepAspectRatio);
1054 
1055             delete instanceItem;
1056 
1057             // If component changes, puppet will need a reset anyway, so we can cache the image
1058             m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), renderImage);
1059         }
1060 
1061         if (!renderImage.isNull()) {
1062             imgContainer.setImage(renderImage);
1063 
1064             // send the rendered image to creator process
1065             nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::RenderModelNodePreviewImage,
1066                                                                 QVariant::fromValue(imgContainer)});
1067         }
1068     }
1069 }
1070 
Qt5InformationNodeInstanceServer(NodeInstanceClientInterface * nodeInstanceClient)1071 Qt5InformationNodeInstanceServer::Qt5InformationNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) :
1072     Qt5NodeInstanceServer(nodeInstanceClient)
1073 {
1074     m_propertyChangeTimer.setInterval(100);
1075     m_propertyChangeTimer.setSingleShot(true);
1076     m_selectionChangeTimer.setSingleShot(true);
1077     m_render3DEditViewTimer.setSingleShot(true);
1078     m_inputEventTimer.setSingleShot(true);
1079     m_renderModelNodeImageViewTimer.setSingleShot(true);
1080 
1081 #ifdef FPS_COUNTER
1082     if (!_fpsTimer) {
1083         _fpsTimer = new QElapsedTimer;
1084         _fpsTimer->start();
1085     }
1086 #endif
1087 }
1088 
~Qt5InformationNodeInstanceServer()1089 Qt5InformationNodeInstanceServer::~Qt5InformationNodeInstanceServer()
1090 {
1091     m_editView3DSetupDone = false;
1092 
1093     m_propertyChangeTimer.stop();
1094     m_propertyChangeTimer.stop();
1095     m_selectionChangeTimer.stop();
1096     m_render3DEditViewTimer.stop();
1097     m_inputEventTimer.stop();
1098 
1099     if (m_editView3DData.rootItem)
1100         m_editView3DData.rootItem->disconnect(this);
1101 
1102     for (auto view : qAsConst(m_view3Ds))
1103         view->disconnect();
1104     for (auto node : qAsConst(m_3DSceneMap))
1105         node->disconnect();
1106 
1107     if (m_editView3DData.rootItem)
1108         QMetaObject::invokeMethod(m_editView3DData.rootItem, "aboutToShutDown", Qt::DirectConnection);
1109 
1110     if (!Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) {
1111         if (m_editView3DData.contentItem)
1112             designerSupport()->derefFromEffectItem(m_editView3DData.contentItem);
1113         if (m_modelNode3DImageViewData.contentItem)
1114             designerSupport()->derefFromEffectItem(m_modelNode3DImageViewData.contentItem);
1115         if (m_modelNode2DImageViewData.contentItem)
1116             designerSupport()->derefFromEffectItem(m_modelNode2DImageViewData.contentItem);
1117     }
1118 }
1119 
sendTokenBack()1120 void Qt5InformationNodeInstanceServer::sendTokenBack()
1121 {
1122     foreach (const TokenCommand &command, m_tokenList)
1123         nodeInstanceClient()->token(command);
1124 
1125     m_tokenList.clear();
1126 }
1127 
token(const TokenCommand & command)1128 void Qt5InformationNodeInstanceServer::token(const TokenCommand &command)
1129 {
1130     m_tokenList.append(command);
1131     startRenderTimer();
1132 }
1133 
isDirtyRecursiveForNonInstanceItems(QQuickItem * item) const1134 bool Qt5InformationNodeInstanceServer::isDirtyRecursiveForNonInstanceItems(QQuickItem *item) const
1135 {
1136     static DesignerSupport::DirtyType informationsDirty = DesignerSupport::DirtyType(DesignerSupport::TransformUpdateMask
1137                                                                               | DesignerSupport::ContentUpdateMask
1138                                                                               | DesignerSupport::Visible
1139                                                                               | DesignerSupport::ZValue
1140                                                                               | DesignerSupport::OpacityValue);
1141 
1142     if (DesignerSupport::isDirty(item, informationsDirty))
1143         return true;
1144 
1145     foreach (QQuickItem *childItem, item->childItems()) {
1146         if (!hasInstanceForObject(childItem)) {
1147             if (DesignerSupport::isDirty(childItem, informationsDirty))
1148                 return true;
1149             else if (isDirtyRecursiveForNonInstanceItems(childItem))
1150                 return true;
1151         }
1152     }
1153 
1154     return false;
1155 }
1156 
isDirtyRecursiveForParentInstances(QQuickItem * item) const1157 bool Qt5InformationNodeInstanceServer::isDirtyRecursiveForParentInstances(QQuickItem *item) const
1158 {
1159     if (DesignerSupport::isDirty(item,  DesignerSupport::TransformUpdateMask))
1160         return true;
1161 
1162     QQuickItem *parentItem = item->parentItem();
1163 
1164     if (parentItem) {
1165         if (hasInstanceForObject(parentItem))
1166             return false;
1167 
1168         return isDirtyRecursiveForParentInstances(parentItem);
1169     }
1170 
1171     return false;
1172 }
1173 
1174 /* This method allows changing the selection from the puppet */
selectInstances(const QList<ServerNodeInstance> & instanceList)1175 void Qt5InformationNodeInstanceServer::selectInstances(const QList<ServerNodeInstance> &instanceList)
1176 {
1177     nodeInstanceClient()->selectionChanged(createChangeSelectionCommand(instanceList));
1178 }
1179 
1180 /* This method allows changing property values from the puppet
1181  * For performance reasons (and the undo stack) properties should always be modifed in 'bulks'.
1182  */
modifyProperties(const QVector<NodeInstanceServer::InstancePropertyValueTriple> & properties)1183 void Qt5InformationNodeInstanceServer::modifyProperties(
1184     const QVector<NodeInstanceServer::InstancePropertyValueTriple> &properties)
1185 {
1186     nodeInstanceClient()->valuesModified(createValuesModifiedCommand(properties));
1187 }
1188 
createInstances(const QVector<InstanceContainer> & container)1189 QList<ServerNodeInstance> Qt5InformationNodeInstanceServer::createInstances(
1190         const QVector<InstanceContainer> &container)
1191 {
1192     const auto createdInstances = NodeInstanceServer::createInstances(container);
1193 
1194     if (m_editView3DSetupDone) {
1195         add3DViewPorts(createdInstances);
1196         add3DScenes(createdInstances);
1197         createCameraAndLightGizmos(createdInstances);
1198     }
1199 
1200     render3DEditView();
1201 
1202     return createdInstances;
1203 }
1204 
initializeAuxiliaryViews()1205 void Qt5InformationNodeInstanceServer::initializeAuxiliaryViews()
1206 {
1207 #ifdef QUICK3D_MODULE
1208     if (isQuick3DMode())
1209         createEditView3D();
1210 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1211     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode3DImageView.qml"),
1212                              m_modelNode3DImageViewData);
1213 #else
1214     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/ModelNode3DImageView.qml"),
1215                              m_modelNode3DImageViewData);
1216 #endif
1217 #endif
1218 
1219 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1220     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode2DImageView.qml"),
1221                              m_modelNode2DImageViewData);
1222 #else
1223     createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/ModelNode2DImageView.qml"),
1224                              m_modelNode2DImageViewData);
1225 #endif
1226     m_modelNode2DImageViewData.window->setDefaultAlphaBuffer(true);
1227     m_modelNode2DImageViewData.window->setColor(Qt::transparent);
1228 }
1229 
handleObjectPropertyChangeTimeout()1230 void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout()
1231 {
1232     modifyVariantValue(m_changedNodes, m_changedProperties,
1233                        ValuesModifiedCommand::TransactionOption::None);
1234 }
1235 
handleSelectionChangeTimeout()1236 void Qt5InformationNodeInstanceServer::handleSelectionChangeTimeout()
1237 {
1238     changeSelection(m_lastSelectionChangeCommand);
1239 }
1240 
createCameraAndLightGizmos(const QList<ServerNodeInstance> & instanceList) const1241 void Qt5InformationNodeInstanceServer::createCameraAndLightGizmos(
1242         const QList<ServerNodeInstance> &instanceList) const
1243 {
1244     QHash<QObject *, QObjectList> cameras;
1245     QHash<QObject *, QObjectList> lights;
1246     QHash<QObject *, QObjectList> particleSystems;
1247 
1248     for (const ServerNodeInstance &instance : instanceList) {
1249         if (instance.isSubclassOf("QQuick3DCamera"))
1250             cameras[find3DSceneRoot(instance)] << instance.internalObject();
1251         else if (instance.isSubclassOf("QQuick3DAbstractLight"))
1252             lights[find3DSceneRoot(instance)] << instance.internalObject();
1253         else if (instance.isSubclassOf("QQuick3DParticleSystem"))
1254             particleSystems[find3DSceneRoot(instance)] << instance.internalObject();
1255 
1256     }
1257 
1258     auto cameraIt = cameras.constBegin();
1259     while (cameraIt != cameras.constEnd()) {
1260         const auto cameraObjs = cameraIt.value();
1261         for (auto &obj : cameraObjs) {
1262             QMetaObject::invokeMethod(m_editView3DData.rootItem, "addCameraGizmo",
1263                                       Q_ARG(QVariant, objectToVariant(cameraIt.key())),
1264                                       Q_ARG(QVariant, objectToVariant(obj)));
1265         }
1266         ++cameraIt;
1267     }
1268     auto lightIt = lights.constBegin();
1269     while (lightIt != lights.constEnd()) {
1270         const auto lightObjs = lightIt.value();
1271         for (auto &obj : lightObjs) {
1272             QMetaObject::invokeMethod(m_editView3DData.rootItem, "addLightGizmo",
1273                                       Q_ARG(QVariant, objectToVariant(lightIt.key())),
1274                                       Q_ARG(QVariant, objectToVariant(obj)));
1275         }
1276         ++lightIt;
1277     }
1278     auto particleIt = particleSystems.constBegin();
1279     while (particleIt != particleSystems.constEnd()) {
1280         const auto particleObjs = particleIt.value();
1281         for (auto &obj : particleObjs) {
1282             QMetaObject::invokeMethod(m_editView3DData.rootItem, "addParticleSystemGizmo",
1283                                       Q_ARG(QVariant, objectToVariant(particleIt.key())),
1284                                       Q_ARG(QVariant, objectToVariant(obj)));
1285         }
1286         ++particleIt;
1287     }
1288 }
1289 
add3DViewPorts(const QList<ServerNodeInstance> & instanceList)1290 void Qt5InformationNodeInstanceServer::add3DViewPorts(const QList<ServerNodeInstance> &instanceList)
1291 {
1292     for (const ServerNodeInstance &instance : instanceList) {
1293         if (instance.isSubclassOf("QQuick3DViewport")) {
1294             QObject *obj = instance.internalObject();
1295             if (!m_view3Ds.contains(obj))  {
1296                 m_view3Ds << obj;
1297                 QObject::connect(obj, SIGNAL(widthChanged()), this, SLOT(handleView3DSizeChange()));
1298                 QObject::connect(obj, SIGNAL(heightChanged()), this, SLOT(handleView3DSizeChange()));
1299                 QObject::connect(obj, &QObject::destroyed,
1300                                  this, &Qt5InformationNodeInstanceServer::handleView3DDestroyed);
1301             }
1302         }
1303     }
1304 }
1305 
add3DScenes(const QList<ServerNodeInstance> & instanceList)1306 void Qt5InformationNodeInstanceServer::add3DScenes(const QList<ServerNodeInstance> &instanceList)
1307 {
1308     for (const ServerNodeInstance &instance : instanceList) {
1309         if (instance.isSubclassOf("QQuick3DNode")) {
1310             QObject *sceneRoot = find3DSceneRoot(instance);
1311             QObject *obj = instance.internalObject();
1312             if (!m_3DSceneMap.contains(sceneRoot, obj)) {
1313                 m_3DSceneMap.insert(sceneRoot, obj);
1314                 QObject::connect(obj, &QObject::destroyed,
1315                                  this, &Qt5InformationNodeInstanceServer::handleNode3DDestroyed);
1316             }
1317         }
1318     }
1319 }
1320 
findView3DForInstance(const ServerNodeInstance & instance) const1321 QObject *Qt5InformationNodeInstanceServer::findView3DForInstance(const ServerNodeInstance &instance) const
1322 {
1323 #ifdef QUICK3D_MODULE
1324     if (!instance.isValid())
1325         return {};
1326 
1327     // View3D of an instance is one of the following, in order of priority:
1328     // - Any direct ancestor View3D of the instance
1329     // - Any View3D that specifies the instance's scene as importScene
1330     ServerNodeInstance checkInstance = instance;
1331     while (checkInstance.isValid()) {
1332         if (checkInstance.isSubclassOf("QQuick3DViewport"))
1333             return checkInstance.internalObject();
1334         else
1335             checkInstance = checkInstance.parent();
1336     }
1337 
1338     // If no ancestor View3D was found, check if the scene root is specified as importScene in
1339     // some View3D.
1340     QObject *sceneRoot = find3DSceneRoot(instance);
1341     for (const auto &view3D : qAsConst(m_view3Ds)) {
1342         auto view = qobject_cast<QQuick3DViewport *>(view3D);
1343         if (view && sceneRoot == view->importScene())
1344             return view3D;
1345     }
1346 #else
1347     Q_UNUSED(instance)
1348 #endif
1349     return {};
1350 }
1351 
findView3DForSceneRoot(QObject * sceneRoot) const1352 QObject *Qt5InformationNodeInstanceServer::findView3DForSceneRoot(QObject *sceneRoot) const
1353 {
1354 #ifdef QUICK3D_MODULE
1355     if (!sceneRoot)
1356         return {};
1357 
1358     if (hasInstanceForObject(sceneRoot)) {
1359         return findView3DForInstance(instanceForObject(sceneRoot));
1360     } else {
1361         // No instance, so the scene root must be scene property of one of the views
1362         for (const auto &view3D : qAsConst(m_view3Ds)) {
1363             auto view = qobject_cast<QQuick3DViewport *>(view3D);
1364             if (view && sceneRoot == view->scene())
1365                 return view3D;
1366         }
1367     }
1368 #else
1369     Q_UNUSED(sceneRoot)
1370 #endif
1371     return {};
1372 }
1373 
find3DSceneRoot(const ServerNodeInstance & instance) const1374 QObject *Qt5InformationNodeInstanceServer::find3DSceneRoot(const ServerNodeInstance &instance) const
1375 {
1376 #ifdef QUICK3D_MODULE
1377     // The root of a 3D scene is any QQuick3DNode that doesn't have QQuick3DNode as parent.
1378     // One exception is QQuick3DSceneRootNode that has only a single child QQuick3DNode (not
1379     // a subclass of one, but exactly QQuick3DNode). In that case we consider the single child node
1380     // to be the scene root (as QQuick3DSceneRootNode is not visible in the navigator scene graph).
1381 
1382     if (!instance.isValid())
1383         return nullptr;
1384 
1385     QQuick3DNode *childNode = nullptr;
1386     auto countChildNodes = [&childNode](QQuick3DViewport *view) -> int {
1387         QQuick3DNode *sceneNode = view->scene();
1388         QList<QQuick3DObject *> children = sceneNode->childItems();
1389         int nodeCount = 0;
1390         for (const auto &child : children) {
1391             auto nodeChild = qobject_cast<QQuick3DNode *>(child);
1392             if (nodeChild) {
1393                 ++nodeCount;
1394                 childNode = nodeChild;
1395             }
1396         }
1397         return nodeCount;
1398     };
1399 
1400     // In case of View3D is selected, the root scene is whatever is contained in View3D, or
1401     // importScene, in case there is no content in View3D
1402     QObject *obj = instance.internalObject();
1403     auto view = qobject_cast<QQuick3DViewport *>(obj);
1404     if (view) {
1405         int nodeCount = countChildNodes(view);
1406         if (nodeCount == 0)
1407             return view->importScene();
1408         else if (nodeCount == 1)
1409             return childNode;
1410         else
1411             return view->scene();
1412     }
1413 
1414     ServerNodeInstance checkInstance = instance;
1415     bool foundNode = checkInstance.isSubclassOf("QQuick3DNode");
1416     while (checkInstance.isValid()) {
1417         ServerNodeInstance parentInstance = checkInstance.parent();
1418         if (parentInstance.isSubclassOf("QQuick3DViewport")) {
1419             view = qobject_cast<QQuick3DViewport *>(parentInstance.internalObject());
1420             int nodeCount = countChildNodes(view);
1421             if (nodeCount == 1)
1422                 return childNode;
1423             else
1424                 return view->scene();
1425         } else if (parentInstance.isSubclassOf("QQuick3DNode")) {
1426             foundNode = true;
1427             checkInstance = parentInstance;
1428         } else {
1429             if (!foundNode) {
1430                 // We haven't found any node yet, continue the search
1431                 checkInstance = parentInstance;
1432             } else {
1433                 return checkInstance.internalObject();
1434             }
1435         }
1436     }
1437 #else
1438     Q_UNUSED(instance)
1439 #endif
1440     return nullptr;
1441 }
1442 
find3DSceneRoot(QObject * obj) const1443 QObject *Qt5InformationNodeInstanceServer::find3DSceneRoot(QObject *obj) const
1444 {
1445 #ifdef QUICK3D_MODULE
1446     if (hasInstanceForObject(obj))
1447         return find3DSceneRoot(instanceForObject(obj));
1448 
1449     // If there is no instance, obj could be a scene in a View3D
1450     for (const auto &viewObj : qAsConst(m_view3Ds)) {
1451         const auto view = qobject_cast<QQuick3DViewport *>(viewObj);
1452         if (view && view->scene() == obj)
1453             return obj;
1454     }
1455 #else
1456     Q_UNUSED(obj)
1457 #endif
1458     // Some other non-instance object, assume it's not part of any scene
1459     return nullptr;
1460 }
1461 
setup3DEditView(const QList<ServerNodeInstance> & instanceList,const QHash<QString,QVariantMap> & toolStates)1462 void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeInstance> &instanceList,
1463                                                        const QHash<QString, QVariantMap> &toolStates)
1464 {
1465 #ifdef QUICK3D_MODULE
1466     if (!m_editView3DData.rootItem)
1467         return;
1468 
1469     ServerNodeInstance root = rootNodeInstance();
1470 
1471     add3DViewPorts(instanceList);
1472     add3DScenes(instanceList);
1473 
1474     QObject::connect(m_editView3DData.rootItem, SIGNAL(selectionChanged(QVariant)),
1475                      this, SLOT(handleSelectionChanged(QVariant)));
1476     QObject::connect(m_editView3DData.rootItem, SIGNAL(commitObjectProperty(QVariant, QVariant)),
1477                      this, SLOT(handleObjectPropertyCommit(QVariant, QVariant)));
1478     QObject::connect(m_editView3DData.rootItem, SIGNAL(changeObjectProperty(QVariant, QVariant)),
1479                      this, SLOT(handleObjectPropertyChange(QVariant, QVariant)));
1480     QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSceneChange()),
1481                      this, SLOT(handleActiveSceneChange()));
1482     QObject::connect(&m_propertyChangeTimer, &QTimer::timeout,
1483                      this, &Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout);
1484     QObject::connect(&m_selectionChangeTimer, &QTimer::timeout,
1485                      this, &Qt5InformationNodeInstanceServer::handleSelectionChangeTimeout);
1486     QObject::connect(&m_render3DEditViewTimer, &QTimer::timeout,
1487                      this, &Qt5InformationNodeInstanceServer::doRender3DEditView);
1488     QObject::connect(&m_inputEventTimer, &QTimer::timeout,
1489                      this, &Qt5InformationNodeInstanceServer::handleInputEvents);
1490 
1491     QString lastSceneId;
1492     auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
1493     if (helper) {
1494         auto it = toolStates.constBegin();
1495         while (it != toolStates.constEnd()) {
1496             helper->initToolStates(it.key(), it.value());
1497             ++it;
1498         }
1499         if (toolStates.contains(helper->globalStateId())) {
1500             if (toolStates[helper->globalStateId()].contains(helper->rootSizeKey())) {
1501                 QSize size = toolStates[helper->globalStateId()][helper->rootSizeKey()].value<QSize>();
1502                 m_editView3DData.rootItem->setSize(size);
1503                 m_editView3DData.window->setGeometry(0, 0, size.width(), size.height());
1504             }
1505             if (toolStates[helper->globalStateId()].contains(helper->lastSceneIdKey()))
1506                 lastSceneId = toolStates[helper->globalStateId()][helper->lastSceneIdKey()].toString();
1507         }
1508     }
1509 
1510     // Find a scene to show
1511     m_active3DScene = nullptr;
1512     m_active3DView = nullptr;
1513     if (!m_3DSceneMap.isEmpty()) {
1514         // Restore the previous scene if possible
1515         if (!lastSceneId.isEmpty()) {
1516             const auto keys = m_3DSceneMap.uniqueKeys();
1517             for (const auto key : keys) {
1518                 m_active3DScene = key;
1519                 m_active3DView = findView3DForSceneRoot(m_active3DScene);
1520                 ServerNodeInstance sceneInstance = active3DSceneInstance();
1521                 if (lastSceneId == sceneInstance.id())
1522                     break;
1523             }
1524         } else {
1525             m_active3DScene = m_3DSceneMap.begin().key();
1526             m_active3DView = findView3DForSceneRoot(m_active3DScene);
1527         }
1528     }
1529 
1530     m_editView3DSetupDone = true;
1531 
1532     if (toolStates.contains({})) {
1533         // Update tool state to an existing no-scene state before updating the active scene to
1534         // ensure the previous state is inherited properly in all cases.
1535         QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateToolStates", Qt::QueuedConnection,
1536                                   Q_ARG(QVariant, toolStates[{}]),
1537                                   Q_ARG(QVariant, QVariant::fromValue(false)));
1538     }
1539 
1540     updateActiveSceneToEditView3D();
1541 
1542     createCameraAndLightGizmos(instanceList);
1543 
1544     // Queue two renders to make sure icon gizmos update properly
1545     render3DEditView(2);
1546 #else
1547     Q_UNUSED(instanceList)
1548     Q_UNUSED(toolStates)
1549 #endif
1550 }
1551 
collectItemChangesAndSendChangeCommands()1552 void Qt5InformationNodeInstanceServer::collectItemChangesAndSendChangeCommands()
1553 {
1554     static bool inFunction = false;
1555     if (!inFunction) {
1556         inFunction = true;
1557 
1558         DesignerSupport::polishItems(quickWindow());
1559 
1560         QSet<ServerNodeInstance> informationChangedInstanceSet;
1561         QVector<InstancePropertyPair> propertyChangedList;
1562 
1563         if (quickWindow()) {
1564             foreach (QQuickItem *item, allItems()) {
1565                 if (item && hasInstanceForObject(item)) {
1566                     ServerNodeInstance instance = instanceForObject(item);
1567 
1568                     if (isDirtyRecursiveForNonInstanceItems(item))
1569                         informationChangedInstanceSet.insert(instance);
1570                     else if (isDirtyRecursiveForParentInstances(item))
1571                         informationChangedInstanceSet.insert(instance);
1572 
1573                     if (DesignerSupport::isDirty(item, DesignerSupport::ParentChanged)) {
1574                         m_parentChangedSet.insert(instance);
1575                         informationChangedInstanceSet.insert(instance);
1576                     }
1577                 }
1578             }
1579 
1580             foreach (const InstancePropertyPair& property, changedPropertyList()) {
1581                 const ServerNodeInstance instance = property.first;
1582                 if (instance.isValid()) {
1583                     if (property.second.contains("anchors"))
1584                         informationChangedInstanceSet.insert(instance);
1585 
1586                     propertyChangedList.append(property);
1587                 }
1588             }
1589 
1590             resetAllItems();
1591             clearChangedPropertyList();
1592 
1593             sendTokenBack();
1594 
1595             if (!informationChangedInstanceSet.isEmpty())
1596                 nodeInstanceClient()->informationChanged(
1597                     createAllInformationChangedCommand(QtHelpers::toList(informationChangedInstanceSet)));
1598 
1599             if (!propertyChangedList.isEmpty())
1600                 nodeInstanceClient()->valuesChanged(createValuesChangedCommand(propertyChangedList));
1601 
1602             if (!m_parentChangedSet.isEmpty()) {
1603                 sendChildrenChangedCommand(QtHelpers::toList(m_parentChangedSet));
1604                 updateLockedAndHiddenStates(m_parentChangedSet);
1605                 m_parentChangedSet.clear();
1606             }
1607 
1608             if (!m_completedComponentList.isEmpty()) {
1609                 nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(m_completedComponentList));
1610                 m_completedComponentList.clear();
1611             }
1612 
1613             slowDownRenderTimer();
1614             nodeInstanceClient()->flush();
1615             nodeInstanceClient()->synchronizeWithClientProcess();
1616         }
1617 
1618         inFunction = false;
1619     }
1620 }
1621 
reparentInstances(const ReparentInstancesCommand & command)1622 void Qt5InformationNodeInstanceServer::reparentInstances(const ReparentInstancesCommand &command)
1623 {
1624     foreach (const ReparentContainer &container, command.reparentInstances()) {
1625         if (hasInstanceForId(container.instanceId())) {
1626             ServerNodeInstance instance = instanceForId(container.instanceId());
1627             if (instance.isValid()) {
1628                 m_parentChangedSet.insert(instance);
1629             }
1630         }
1631     }
1632 
1633     Qt5NodeInstanceServer::reparentInstances(command);
1634 
1635     if (m_editView3DSetupDone)
1636         resolveSceneRoots();
1637 
1638     // Make sure selection is in sync after all reparentings are done
1639     m_selectionChangeTimer.start(0);
1640 }
1641 
clearScene(const ClearSceneCommand & command)1642 void Qt5InformationNodeInstanceServer::clearScene(const ClearSceneCommand &command)
1643 {
1644     Qt5NodeInstanceServer::clearScene(command);
1645 
1646     m_parentChangedSet.clear();
1647     m_completedComponentList.clear();
1648 }
1649 
createScene(const CreateSceneCommand & command)1650 void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &command)
1651 {
1652     Qt5NodeInstanceServer::createScene(command);
1653 
1654     QList<ServerNodeInstance> instanceList;
1655     for (const InstanceContainer &container : std::as_const(command.instances)) {
1656         if (hasInstanceForId(container.instanceId())) {
1657             ServerNodeInstance instance = instanceForId(container.instanceId());
1658             if (instance.isValid())
1659                 instanceList.append(instance);
1660         }
1661     }
1662 
1663     nodeInstanceClient()->informationChanged(createAllInformationChangedCommand(instanceList, true));
1664     nodeInstanceClient()->valuesChanged(createValuesChangedCommand(instanceList));
1665     sendChildrenChangedCommand(instanceList);
1666     nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList));
1667 
1668     if (isQuick3DMode()) {
1669         setup3DEditView(instanceList, command.edit3dToolStates);
1670         updateRotationBlocks(command.auxiliaryChanges);
1671     }
1672 
1673     QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout,
1674                      this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView);
1675 #ifdef IMPORT_QUICK3D_ASSETS
1676     QTimer::singleShot(0, this, &Qt5InformationNodeInstanceServer::resolveImportSupport);
1677 #endif
1678 }
1679 
sendChildrenChangedCommand(const QList<ServerNodeInstance> & childList)1680 void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList<ServerNodeInstance> &childList)
1681 {
1682     QSet<ServerNodeInstance> parentSet;
1683     QList<ServerNodeInstance> noParentList;
1684 
1685     foreach (const ServerNodeInstance &child, childList) {
1686         if (child.isValid()) {
1687             if (!child.hasParent()) {
1688                 noParentList.append(child);
1689             } else {
1690                 ServerNodeInstance parent = child.parent();
1691                 if (parent.isValid()) {
1692                     parentSet.insert(parent);
1693                 } else {
1694                     noParentList.append(child);
1695                 }
1696             }
1697         }
1698     }
1699 
1700     foreach (const ServerNodeInstance &parent, parentSet)
1701         nodeInstanceClient()->childrenChanged(createChildrenChangedCommand(parent, parent.childItems()));
1702 
1703     if (!noParentList.isEmpty())
1704         nodeInstanceClient()->childrenChanged(createChildrenChangedCommand(ServerNodeInstance(), noParentList));
1705 
1706 }
1707 
completeComponent(const CompleteComponentCommand & command)1708 void Qt5InformationNodeInstanceServer::completeComponent(const CompleteComponentCommand &command)
1709 {
1710     Qt5NodeInstanceServer::completeComponent(command);
1711 
1712     QList<ServerNodeInstance> instanceList;
1713     foreach (qint32 instanceId, command.instances()) {
1714         if (hasInstanceForId(instanceId)) {
1715             ServerNodeInstance instance = instanceForId(instanceId);
1716             if (instance.isValid()) {
1717                 instanceList.append(instance);
1718             }
1719         }
1720     }
1721 
1722     m_completedComponentList.append(instanceList);
1723 
1724     nodeInstanceClient()->valuesChanged(createValuesChangedCommand(instanceList));
1725     nodeInstanceClient()->informationChanged(createAllInformationChangedCommand(instanceList, true));
1726 }
1727 
removeSharedMemory(const QmlDesigner::RemoveSharedMemoryCommand & command)1728 void QmlDesigner::Qt5InformationNodeInstanceServer::removeSharedMemory(const QmlDesigner::RemoveSharedMemoryCommand &command)
1729 {
1730     if (command.typeName() == "Values")
1731         ValuesChangedCommand::removeSharedMemorys(command.keyNumbers());
1732 }
1733 
changeSelection(const ChangeSelectionCommand & command)1734 void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionCommand &command)
1735 {
1736     if (!m_editView3DSetupDone)
1737         return;
1738 
1739     m_lastSelectionChangeCommand = command;
1740     if (m_selectionChangeTimer.isActive()) {
1741         // If selection was recently changed by puppet, hold updating the selection for a bit to
1742         // avoid selection flicker, especially in multiselect cases.
1743         // Add additional time in case more commands are still coming through
1744         m_selectionChangeTimer.start(500);
1745         return;
1746     }
1747 
1748     // Find a scene root of the selection to update active scene shown
1749     const QVector<qint32> instanceIds = command.instanceIds();
1750     QVariantList selectedObjs;
1751     QObject *firstSceneRoot = nullptr;
1752     ServerNodeInstance firstInstance;
1753     for (qint32 id : instanceIds) {
1754         if (hasInstanceForId(id)) {
1755             ServerNodeInstance instance = instanceForId(id);
1756             QObject *sceneRoot = find3DSceneRoot(instance);
1757             if (!firstSceneRoot && sceneRoot) {
1758                 firstSceneRoot = sceneRoot;
1759                 firstInstance = instance;
1760             }
1761             QObject *object = nullptr;
1762             if (firstSceneRoot && sceneRoot == firstSceneRoot && instance.isSubclassOf("QQuick3DNode"))
1763                 object = instance.internalObject();
1764 
1765             auto isSelectableAsRoot = [&]() -> bool {
1766 #ifdef QUICK3D_MODULE
1767                 if (qobject_cast<QQuick3DModel *>(object)
1768                     || qobject_cast<QQuick3DCamera *>(object)
1769 #ifdef QUICK3D_PARTICLES_MODULE
1770                     || qobject_cast<QQuick3DParticleSystem *>(object)
1771 #endif
1772                     || qobject_cast<QQuick3DAbstractLight *>(object)) {
1773                     return true;
1774                 }
1775                 // Node is a component if it has node children that have no instances
1776                 auto node = qobject_cast<QQuick3DNode *>(object);
1777                 if (node) {
1778                     const auto childItems = node->childItems();
1779                     for (const auto &childItem : childItems) {
1780                         if (qobject_cast<QQuick3DNode *>(childItem) && !hasInstanceForObject(childItem))
1781                             return true;
1782                     }
1783                 }
1784 #endif
1785                 return false;
1786             };
1787             if (object && (firstSceneRoot != object || isSelectableAsRoot()))
1788                 selectedObjs << objectToVariant(object);
1789         }
1790     }
1791 
1792     if (firstSceneRoot && m_active3DScene != firstSceneRoot) {
1793         m_active3DScene = firstSceneRoot;
1794         m_active3DView = findView3DForInstance(firstInstance);
1795         updateActiveSceneToEditView3D();
1796     }
1797 
1798     // Ensure the UI has enough selection box items. If it doesn't yet have them, which can be the
1799     // case when the first selection processed is a multiselection, we wait a bit as
1800     // using the new boxes immediately leads to visual glitches.
1801     int boxCount = m_editView3DData.rootItem->property("selectionBoxes").value<QVariantList>().size();
1802     if (boxCount < selectedObjs.size()) {
1803         QMetaObject::invokeMethod(m_editView3DData.rootItem, "ensureSelectionBoxes",
1804                                   Q_ARG(QVariant, QVariant::fromValue(selectedObjs.size())));
1805         m_selectionChangeTimer.start(0);
1806     } else {
1807         QMetaObject::invokeMethod(m_editView3DData.rootItem, "selectObjects",
1808                                   Q_ARG(QVariant, QVariant::fromValue(selectedObjs)));
1809     }
1810 
1811     render3DEditView(2);
1812 }
1813 
changePropertyValues(const ChangeValuesCommand & command)1814 void Qt5InformationNodeInstanceServer::changePropertyValues(const ChangeValuesCommand &command)
1815 {
1816     bool hasDynamicProperties = false;
1817     const QVector<PropertyValueContainer> values = command.valueChanges();
1818     for (const PropertyValueContainer &container : values) {
1819         if (!container.isReflected()) {
1820             hasDynamicProperties |= container.isDynamic();
1821             setInstancePropertyVariant(container);
1822         }
1823     }
1824 
1825     if (hasDynamicProperties)
1826         refreshBindings();
1827 
1828     startRenderTimer();
1829 
1830     render3DEditView();
1831 }
1832 
removeInstances(const RemoveInstancesCommand & command)1833 void Qt5InformationNodeInstanceServer::removeInstances(const RemoveInstancesCommand &command)
1834 {
1835     int nodeCount = m_3DSceneMap.size();
1836 
1837     removeRotationBlocks(command.instanceIds());
1838 
1839     Qt5NodeInstanceServer::removeInstances(command);
1840 
1841     if (nodeCount != m_3DSceneMap.size()) {
1842         // Some nodes were removed, which can cause scene root to change for nodes under View3D
1843         // objects, so re-resolve scene roots.
1844         resolveSceneRoots();
1845     }
1846 
1847     if (m_editView3DSetupDone && (!m_active3DScene || !m_active3DView)) {
1848         if (!m_active3DScene && !m_3DSceneMap.isEmpty())
1849             m_active3DScene = m_3DSceneMap.begin().key();
1850         m_active3DView = findView3DForSceneRoot(m_active3DScene);
1851         updateActiveSceneToEditView3D();
1852     }
1853     render3DEditView();
1854 }
1855 
inputEvent(const InputEventCommand & command)1856 void Qt5InformationNodeInstanceServer::inputEvent(const InputEventCommand &command)
1857 {
1858     if (m_editView3DData.window) {
1859         m_pendingInputEventCommands.append(command);
1860         if (!m_inputEventTimer.isActive())
1861             m_inputEventTimer.start(0);
1862     }
1863 }
1864 
view3DAction(const View3DActionCommand & command)1865 void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &command)
1866 {
1867     if (!m_editView3DSetupDone)
1868         return;
1869 
1870     QVariantMap updatedState;
1871     int renderCount = 1;
1872 
1873     switch (command.type()) {
1874     case View3DActionCommand::MoveTool:
1875         updatedState.insert("transformMode", 0);
1876         break;
1877     case View3DActionCommand::RotateTool:
1878         updatedState.insert("transformMode", 1);
1879         break;
1880     case View3DActionCommand::ScaleTool:
1881         updatedState.insert("transformMode", 2);
1882         break;
1883     case View3DActionCommand::FitToView:
1884         QMetaObject::invokeMethod(m_editView3DData.rootItem, "fitToView");
1885         break;
1886     case View3DActionCommand::SelectionModeToggle:
1887         updatedState.insert("selectionMode", command.isEnabled() ? 1 : 0);
1888         break;
1889     case View3DActionCommand::CameraToggle:
1890         updatedState.insert("usePerspective", command.isEnabled());
1891         // It can take a couple frames to properly update icon gizmo positions, so render 3 frames
1892         renderCount = 3;
1893         break;
1894     case View3DActionCommand::OrientationToggle:
1895         updatedState.insert("globalOrientation", command.isEnabled());
1896         break;
1897     case View3DActionCommand::EditLightToggle:
1898         updatedState.insert("showEditLight", command.isEnabled());
1899         break;
1900     case View3DActionCommand::ShowGrid:
1901         updatedState.insert("showGrid", command.isEnabled());
1902         break;
1903     default:
1904         break;
1905     }
1906 
1907     if (!updatedState.isEmpty()) {
1908         QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateToolStates",
1909                                   Q_ARG(QVariant, updatedState),
1910                                   Q_ARG(QVariant, QVariant::fromValue(false)));
1911     }
1912 
1913     render3DEditView(renderCount);
1914 }
1915 
requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand & command)1916 void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command)
1917 {
1918     m_modelNodePreviewImageCommand = command;
1919     renderModelNodeImageView();
1920 }
1921 
changeAuxiliaryValues(const ChangeAuxiliaryCommand & command)1922 void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxiliaryCommand &command)
1923 {
1924     updateRotationBlocks(command.auxiliaryChanges);
1925     Qt5NodeInstanceServer::changeAuxiliaryValues(command);
1926     render3DEditView();
1927 }
1928 
changePropertyBindings(const ChangeBindingsCommand & command)1929 void Qt5InformationNodeInstanceServer::changePropertyBindings(const ChangeBindingsCommand &command)
1930 {
1931     Qt5NodeInstanceServer::changePropertyBindings(command);
1932     render3DEditView();
1933 }
1934 
changeIds(const ChangeIdsCommand & command)1935 void Qt5InformationNodeInstanceServer::changeIds(const ChangeIdsCommand &command)
1936 {
1937     Qt5NodeInstanceServer::changeIds(command);
1938 
1939 #ifdef QUICK3D_MODULE
1940     if (m_editView3DSetupDone) {
1941         ServerNodeInstance sceneInstance = active3DSceneInstance();
1942         if (m_active3DSceneUpdatePending) {
1943             const QString sceneId = sceneInstance.id();
1944             if (!sceneId.isEmpty())
1945                 updateActiveSceneToEditView3D();
1946         } else {
1947             qint32 sceneInstanceId = sceneInstance.instanceId();
1948             for (const auto &id : command.ids) {
1949                 if (sceneInstanceId == id.instanceId()) {
1950                     QMetaObject::invokeMethod(m_editView3DData.rootItem, "handleActiveSceneIdChange",
1951                                               Qt::QueuedConnection,
1952                                               Q_ARG(QVariant, QVariant(sceneInstance.id())));
1953                     render3DEditView();
1954                     break;
1955                 }
1956             }
1957         }
1958     }
1959 #endif
1960 }
1961 
changeState(const ChangeStateCommand & command)1962 void Qt5InformationNodeInstanceServer::changeState(const ChangeStateCommand &command)
1963 {
1964     Qt5NodeInstanceServer::changeState(command);
1965 
1966     render3DEditView();
1967 }
1968 
removeProperties(const RemovePropertiesCommand & command)1969 void Qt5InformationNodeInstanceServer::removeProperties(const RemovePropertiesCommand &command)
1970 {
1971     Qt5NodeInstanceServer::removeProperties(command);
1972 
1973     render3DEditView();
1974 }
1975 
handleInstanceLocked(const ServerNodeInstance & instance,bool enable,bool checkAncestors)1976 void Qt5InformationNodeInstanceServer::handleInstanceLocked(const ServerNodeInstance &instance,
1977                                                             bool enable, bool checkAncestors)
1978 {
1979 #ifdef QUICK3D_MODULE
1980     if (!isQuick3DMode())
1981         return;
1982 
1983     bool edit3dLocked = enable;
1984     if (!edit3dLocked || checkAncestors) {
1985         auto parentInst = instance.parent();
1986         while (!edit3dLocked && parentInst.isValid()) {
1987             edit3dLocked = parentInst.internalInstance()->isLockedInEditor();
1988             parentInst = parentInst.parent();
1989         }
1990     }
1991 
1992     QObject *obj = instance.internalObject();
1993     auto node = qobject_cast<QQuick3DNode *>(obj);
1994     if (node)
1995         node->setProperty("_edit3dLocked", edit3dLocked);
1996     const auto children = obj->children();
1997     for (auto child : children) {
1998         if (hasInstanceForObject(child)) {
1999             const ServerNodeInstance childInstance = instanceForObject(child);
2000             if (childInstance.isValid()) {
2001                 auto objInstance = childInstance.internalInstance();
2002                 // Don't override explicit lock on children
2003                 handleInstanceLocked(childInstance, edit3dLocked || objInstance->isLockedInEditor(), false);
2004             }
2005         }
2006     }
2007 #else
2008     Q_UNUSED(instance);
2009     Q_UNUSED(enable);
2010     Q_UNUSED(checkAncestors);
2011 #endif
2012 }
2013 
handleInstanceHidden(const ServerNodeInstance & instance,bool enable,bool checkAncestors)2014 void Qt5InformationNodeInstanceServer::handleInstanceHidden(const ServerNodeInstance &instance,
2015                                                             bool enable, bool checkAncestors)
2016 {
2017 #ifdef QUICK3D_MODULE
2018     if (!isQuick3DMode())
2019         return;
2020 
2021     bool edit3dHidden = enable;
2022     if (!edit3dHidden || checkAncestors) {
2023         // We do not care about hidden status of non-3D ancestor nodes, as the 3D scene
2024         // can be considered a separate visual entity in the whole scene.
2025         auto parentInst = instance.parent();
2026         while (!edit3dHidden && parentInst.isValid() && qobject_cast<QQuick3DNode *>(parentInst.internalObject())) {
2027             edit3dHidden = parentInst.internalInstance()->isHiddenInEditor();
2028             parentInst = parentInst.parent();
2029         }
2030     }
2031 
2032     auto node = qobject_cast<QQuick3DNode *>(instance.internalObject());
2033     if (node) {
2034         bool isInstanceHidden = false;
2035         auto getQuick3DInstanceAndHidden = [this, &isInstanceHidden](QQuick3DObject *obj) -> ServerNodeInstance {
2036             if (hasInstanceForObject(obj)) {
2037                 const ServerNodeInstance instance = instanceForObject(obj);
2038                 if (instance.isValid() && qobject_cast<QQuick3DNode *>(instance.internalObject())) {
2039                     auto objInstance = instance.internalInstance();
2040                     isInstanceHidden = objInstance->isHiddenInEditor();
2041                     return instance;
2042                 }
2043             }
2044             return {};
2045         };
2046         // Always make sure the hide status is correct on the node tree from this point on,
2047         // as changes in the node tree (reparenting, adding new nodes) can make the previously set
2048         // hide status based on ancestor unreliable.
2049         node->setProperty("_edit3dHidden", edit3dHidden);
2050 #if QT_VERSION < QT_VERSION_CHECK(6, 2, 1)
2051         if (auto model = qobject_cast<QQuick3DModel *>(node))
2052             model->setPickable(!edit3dHidden); // allow 3D objects to receive mouse clicks
2053 #endif
2054         const auto childItems = node->childItems();
2055         for (auto childItem : childItems) {
2056             const ServerNodeInstance quick3dInstance = getQuick3DInstanceAndHidden(childItem);
2057             if (quick3dInstance.isValid()) {
2058                 // Don't override explicit hide in children
2059                 handleInstanceHidden(quick3dInstance, edit3dHidden || isInstanceHidden, false);
2060             } else {
2061                 // Children of components do not have instances, but will still need to be pickable
2062                 std::function<void(QQuick3DNode *)> checkChildren;
2063                 checkChildren = [&](QQuick3DNode *checkNode) {
2064                     const auto childItems = checkNode->childItems();
2065                     for (auto child : childItems) {
2066                         if (auto childNode = qobject_cast<QQuick3DNode *>(child))
2067                             checkChildren(childNode);
2068                     }
2069                     if (auto checkModel = qobject_cast<QQuick3DModel *>(checkNode)) {
2070                         QVariant value;
2071                         if (!edit3dHidden)
2072                             value = QVariant::fromValue(node);
2073                         // Specify the actual pick target with dynamic property
2074                         checkModel->setProperty("_pickTarget", value);
2075 #if QT_VERSION < QT_VERSION_CHECK(6, 2, 1)
2076                         checkModel->setPickable(!edit3dHidden);
2077 #endif
2078                     }
2079                 };
2080                 if (auto childNode = qobject_cast<QQuick3DNode *>(childItem))
2081                     checkChildren(childNode);
2082             }
2083         }
2084     }
2085 #else
2086     Q_UNUSED(instance);
2087     Q_UNUSED(enable);
2088     Q_UNUSED(checkAncestors);
2089 #endif
2090 }
2091 
isInformationServer() const2092 bool Qt5InformationNodeInstanceServer::isInformationServer() const
2093 {
2094     return true;
2095 }
2096 
2097 // update 3D view size when it changes in creator side
update3DViewState(const Update3dViewStateCommand & command)2098 void Qt5InformationNodeInstanceServer::update3DViewState(const Update3dViewStateCommand &command)
2099 {
2100 #ifdef QUICK3D_MODULE
2101     if (command.type() == Update3dViewStateCommand::SizeChange) {
2102         if (m_editView3DSetupDone) {
2103             m_editView3DData.rootItem->setSize(command.size());
2104 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2105             m_editView3DData.window->contentItem()->setSize(m_editView3DData.rootItem->size());
2106             m_editView3DData.window->setGeometry(0, 0, m_editView3DData.rootItem->width(),
2107                                                  m_editView3DData.rootItem->height());
2108             m_editView3DData.bufferDirty = true;
2109 #endif
2110             auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
2111             if (helper)
2112                 helper->storeToolState(helper->globalStateId(), helper->rootSizeKey(), QVariant(command.size()), 0);
2113             // Queue three renders to make sure icon gizmos update properly
2114             render3DEditView(3);
2115         }
2116     }
2117 #else
2118     Q_UNUSED(command)
2119 #endif
2120 }
2121 
2122 } // namespace QmlDesigner
2123