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