forked from qt-creator/qt-creator
QmlDesigner: Add navigator tooltip for 3D Components with Node root
This enables showing preview tooltip for all imported 3D models. Also moved the preview image handling out of the model and into NodeInstanceView, where it fits more naturally. Change-Id: I48135d06aa8d9313525dae618e22692563da78fd Fixes: QDS-2807 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -207,42 +207,31 @@ QMultiHash<TypeName, ModelNodePreviewImageHandler> DesignerActionManager::modelN
|
||||
void DesignerActionManager::registerModelNodePreviewHandler(const ModelNodePreviewImageHandler &handler)
|
||||
{
|
||||
m_modelNodePreviewImageHandlers.insert(handler.type, handler);
|
||||
|
||||
// Registering a new handler potentially invalidates no-handler set
|
||||
m_noModelNodePreviewImageHandlers.clear();
|
||||
}
|
||||
|
||||
bool DesignerActionManager::hasModelNodePreviewHandler(const ModelNode &node) const
|
||||
{
|
||||
if (m_modelNodePreviewImageHandlers.contains(node.type()))
|
||||
return true;
|
||||
|
||||
if (m_noModelNodePreviewImageHandlers.contains(node.type()))
|
||||
return false;
|
||||
|
||||
// Node may be a subclass of a registered type
|
||||
const bool isComponent = node.isComponent();
|
||||
for (const auto &handler : qAsConst(m_modelNodePreviewImageHandlers)) {
|
||||
if (node.isSubclassOf(handler.type)) {
|
||||
if ((isComponent || !handler.componentOnly) && node.isSubclassOf(handler.type)) {
|
||||
ModelNodePreviewImageHandler subClassHandler = handler;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_noModelNodePreviewImageHandlers.insert(node.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
ModelNodePreviewImageOperation DesignerActionManager::modelNodePreviewOperation(const ModelNode &node) const
|
||||
{
|
||||
ModelNodePreviewImageOperation op = nullptr;
|
||||
if (!m_noModelNodePreviewImageHandlers.contains(node.type())) {
|
||||
int prio = -1;
|
||||
for (const auto &handler : qAsConst(m_modelNodePreviewImageHandlers)) {
|
||||
if (node.isSubclassOf(handler.type) && handler.priority > prio)
|
||||
op = handler.operation;
|
||||
int prio = -1;
|
||||
const bool isComponent = node.isComponent();
|
||||
for (const auto &handler : qAsConst(m_modelNodePreviewImageHandlers)) {
|
||||
if ((isComponent || !handler.componentOnly) && handler.priority > prio
|
||||
&& node.isSubclassOf(handler.type)) {
|
||||
op = handler.operation;
|
||||
prio = handler.priority;
|
||||
}
|
||||
if (!op)
|
||||
m_noModelNodePreviewImageHandlers.insert(node.type());
|
||||
}
|
||||
return op;
|
||||
}
|
||||
@@ -1415,6 +1404,10 @@ void DesignerActionManager::createDefaultModelNodePreviewImageHandlers()
|
||||
registerModelNodePreviewHandler(
|
||||
ModelNodePreviewImageHandler("QtQuick3D.Model",
|
||||
ModelNodeOperations::previewImageDataFor3DNode));
|
||||
registerModelNodePreviewHandler(
|
||||
ModelNodePreviewImageHandler("QtQuick3D.Node",
|
||||
ModelNodeOperations::previewImageDataFor3DNode,
|
||||
true));
|
||||
|
||||
// TODO - Disabled until QTBUG-86616 is fixed
|
||||
// registerModelNodePreviewHandler(
|
||||
|
||||
@@ -72,15 +72,18 @@ struct ModelNodePreviewImageHandler
|
||||
public:
|
||||
ModelNodePreviewImageHandler(const TypeName &t,
|
||||
ModelNodePreviewImageOperation op,
|
||||
bool compOnly = false,
|
||||
int prio = 0)
|
||||
: type(t)
|
||||
, operation(op)
|
||||
, componentOnly(compOnly)
|
||||
, priority(prio)
|
||||
{
|
||||
}
|
||||
|
||||
TypeName type;
|
||||
ModelNodePreviewImageOperation operation = nullptr;
|
||||
bool componentOnly = false;
|
||||
int priority = 0;
|
||||
};
|
||||
|
||||
@@ -137,7 +140,6 @@ private:
|
||||
DesignerActionManagerView *m_designerActionManagerView;
|
||||
QList<AddResourceHandler> m_addResourceHandler;
|
||||
QMultiHash<TypeName, ModelNodePreviewImageHandler> m_modelNodePreviewImageHandlers;
|
||||
mutable QSet<TypeName> m_noModelNodePreviewImageHandlers;
|
||||
};
|
||||
|
||||
} //QmlDesigner
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include <nodelistproperty.h>
|
||||
#include <nodeproperty.h>
|
||||
#include <signalhandlerproperty.h>
|
||||
#include <nodeinstanceview.h>
|
||||
|
||||
#include <componentcore_constants.h>
|
||||
#include <stylesheetmerger.h>
|
||||
@@ -1521,14 +1522,14 @@ void removeGroup(const SelectionContext &selectionContext)
|
||||
QVariant previewImageDataFor3DNode(const ModelNode &modelNode)
|
||||
{
|
||||
if (modelNode.isValid())
|
||||
return modelNode.model()->previewImageDataFor3DNode(modelNode);
|
||||
return modelNode.model()->nodeInstanceView()->previewImageDataFor3DNode(modelNode);
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant previewImageDataForImageNode(const ModelNode &modelNode)
|
||||
{
|
||||
if (modelNode.isValid())
|
||||
return modelNode.model()->previewImageDataForImageNode(modelNode);
|
||||
return modelNode.model()->nodeInstanceView()->previewImageDataForImageNode(modelNode);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -118,9 +118,6 @@ public:
|
||||
|
||||
QList<ModelNode> selectedNodes(AbstractView *view) const;
|
||||
|
||||
QVariant previewImageDataFor3DNode(const ModelNode &modelNode);
|
||||
QVariant previewImageDataForImageNode(const ModelNode &modelNode);
|
||||
|
||||
protected:
|
||||
Model();
|
||||
|
||||
|
||||
@@ -143,6 +143,9 @@ public:
|
||||
|
||||
void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override;
|
||||
|
||||
QVariant previewImageDataFor3DNode(const ModelNode &modelNode);
|
||||
QVariant previewImageDataForImageNode(const ModelNode &modelNode);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *event) override;
|
||||
|
||||
@@ -201,6 +204,8 @@ private: // functions
|
||||
// puppet to creator command handlers
|
||||
void handlePuppetKeyPress(int key, Qt::KeyboardModifiers modifiers);
|
||||
|
||||
void updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image);
|
||||
|
||||
private:
|
||||
NodeInstance m_rootNodeInstance;
|
||||
NodeInstance m_activeStateInstance;
|
||||
|
||||
@@ -1495,7 +1495,7 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
|
||||
auto node = modelNodeForInternalId(container.instanceId());
|
||||
if (node.isValid()) {
|
||||
image.setDevicePixelRatio(2.);
|
||||
emitModelNodelPreviewImageChanged(node, image);
|
||||
updatePreviewImageForNode(node, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1527,11 +1527,15 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node)
|
||||
if (node.isValid()) {
|
||||
auto instance = instanceForModelNode(node);
|
||||
if (instance.isValid()) {
|
||||
QString componentPath;
|
||||
if (node.isComponent())
|
||||
componentPath = node.metaInfo().componentFileName();
|
||||
m_nodeInstanceServer->requestModelNodePreviewImage(
|
||||
RequestModelNodePreviewImageCommand(
|
||||
instance.instanceId(),
|
||||
QSize(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS,
|
||||
Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS)));
|
||||
Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS),
|
||||
componentPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1547,4 +1551,113 @@ void NodeInstanceView::timerEvent(QTimerEvent *event)
|
||||
restartProcess();
|
||||
}
|
||||
|
||||
struct ImageData {
|
||||
QDateTime time;
|
||||
QImage image;
|
||||
QString type;
|
||||
QString id;
|
||||
QString info;
|
||||
};
|
||||
static QHash<QString, QHash<QString, ImageData>> imageDataMap;
|
||||
|
||||
static QVariant imageDataToVariant(const ImageData &imageData)
|
||||
{
|
||||
if (!imageData.image.isNull()) {
|
||||
QVariantMap map;
|
||||
map.insert("type", imageData.type);
|
||||
map.insert("image", QVariant::fromValue<QImage>(imageData.image));
|
||||
map.insert("id", imageData.id);
|
||||
map.insert("info", imageData.info);
|
||||
return map;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNode)
|
||||
{
|
||||
if (!modelNode.isValid())
|
||||
return {};
|
||||
|
||||
// Images on file system can be cached globally as they are found by absolute paths
|
||||
QHash<QString, ImageData> &localDataMap = imageDataMap[{}];
|
||||
|
||||
VariantProperty prop = modelNode.variantProperty("source");
|
||||
QString imageSource = prop.value().toString();
|
||||
QFileInfo imageFi(imageSource);
|
||||
if (imageFi.isRelative())
|
||||
imageSource = QFileInfo(modelNode.model()->fileUrl().toLocalFile()).dir().absoluteFilePath(imageSource);
|
||||
|
||||
imageFi = QFileInfo(imageSource);
|
||||
QDateTime modified = imageFi.lastModified();
|
||||
|
||||
ImageData imageData;
|
||||
bool reload = true;
|
||||
if (localDataMap.contains(imageSource)) {
|
||||
imageData = localDataMap[imageSource];
|
||||
if (modified == imageData.time)
|
||||
reload = false;
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
QImage originalImage;
|
||||
originalImage.load(imageSource);
|
||||
if (!originalImage.isNull()) {
|
||||
imageData.image = originalImage.scaled(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2,
|
||||
Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2,
|
||||
Qt::KeepAspectRatio);
|
||||
imageData.image.setDevicePixelRatio(2.);
|
||||
|
||||
double imgSize = double(imageFi.size());
|
||||
imageData.type = QStringLiteral("%1 (%2)").arg(QString::fromLatin1(modelNode.type())).arg(imageFi.suffix());
|
||||
imageData.id = modelNode.id();
|
||||
static QStringList units({QObject::tr("B"), QObject::tr("KB"), QObject::tr("MB"), QObject::tr("GB")});
|
||||
int unitIndex = 0;
|
||||
while (imgSize > 1024. && unitIndex < units.size() - 1) {
|
||||
++unitIndex;
|
||||
imgSize /= 1024.;
|
||||
}
|
||||
imageData.info = QStringLiteral("%1 x %2 (%3%4)").arg(originalImage.width()).arg(originalImage.height())
|
||||
.arg(QString::number(imgSize, 'g', 3)).arg(units[unitIndex]);
|
||||
localDataMap.insert(imageSource, imageData);
|
||||
}
|
||||
}
|
||||
|
||||
return imageDataToVariant(imageData);
|
||||
}
|
||||
|
||||
QVariant NodeInstanceView::previewImageDataFor3DNode(const ModelNode &modelNode)
|
||||
{
|
||||
QFileInfo docFi = QFileInfo(modelNode.model()->fileUrl().toLocalFile());
|
||||
QHash<QString, ImageData> &localDataMap = imageDataMap[docFi.absoluteFilePath()];
|
||||
ImageData imageData;
|
||||
static const QImage placeHolder(":/navigator/icon/tooltip_placeholder.png");
|
||||
|
||||
// We need puppet to generate the image, which needs to be asynchronous.
|
||||
// Until the image is ready, we show a placeholder
|
||||
const QString id = modelNode.id();
|
||||
if (localDataMap.contains(id)) {
|
||||
imageData = localDataMap[id];
|
||||
} else {
|
||||
imageData.type = QString::fromLatin1(modelNode.type());
|
||||
imageData.id = id;
|
||||
imageData.image = placeHolder;
|
||||
localDataMap.insert(id, imageData);
|
||||
}
|
||||
requestModelNodePreviewImage(modelNode);
|
||||
|
||||
return imageDataToVariant(imageData);
|
||||
}
|
||||
|
||||
void NodeInstanceView::updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image)
|
||||
{
|
||||
QFileInfo docFi = QFileInfo(modelNode.model()->fileUrl().toLocalFile());
|
||||
QString docPath = docFi.absoluteFilePath();
|
||||
if (imageDataMap.contains(docPath)) {
|
||||
QHash<QString, ImageData> &localDataMap = imageDataMap[docPath];
|
||||
if (localDataMap.contains(modelNode.id()))
|
||||
localDataMap[modelNode.id()].image = image;
|
||||
}
|
||||
emitModelNodelPreviewImageChanged(modelNode, image);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "model.h"
|
||||
#include "model_p.h"
|
||||
#include <modelnode.h>
|
||||
#include <qmldesignerconstants.h>
|
||||
#include "internalnode_p.h"
|
||||
#include "invalidpropertyexception.h"
|
||||
#include "invalidargumentexception.h"
|
||||
@@ -286,111 +285,6 @@ void ModelPrivate::removeNodeFromModel(const InternalNodePointer &internalNodePo
|
||||
m_internalIdNodeHash.remove(internalNodePointer->internalId());
|
||||
}
|
||||
|
||||
struct ImageData {
|
||||
QDateTime time;
|
||||
QImage image;
|
||||
QString type;
|
||||
QString id;
|
||||
QString info;
|
||||
};
|
||||
static QHash<QString, QHash<QString, ImageData>> imageDataMap;
|
||||
|
||||
static QVariant imageDataToVariant(const ImageData &imageData)
|
||||
{
|
||||
if (!imageData.image.isNull()) {
|
||||
QVariantMap map;
|
||||
map.insert("type", imageData.type);
|
||||
map.insert("image", QVariant::fromValue<QImage>(imageData.image));
|
||||
map.insert("id", imageData.id);
|
||||
map.insert("info", imageData.info);
|
||||
return map;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ModelPrivate::previewImageDataForImageNode(const ModelNode &modelNode)
|
||||
{
|
||||
// Images on file system can be cached globally as they are found by absolute paths
|
||||
QHash<QString, ImageData> &localDataMap = imageDataMap[{}];
|
||||
|
||||
VariantProperty prop = modelNode.variantProperty("source");
|
||||
QString imageSource = prop.value().toString();
|
||||
QFileInfo imageFi(imageSource);
|
||||
if (imageFi.isRelative())
|
||||
imageSource = QFileInfo(m_fileUrl.toLocalFile()).dir().absoluteFilePath(imageSource);
|
||||
|
||||
imageFi = QFileInfo(imageSource);
|
||||
QDateTime modified = imageFi.lastModified();
|
||||
|
||||
ImageData imageData;
|
||||
bool reload = true;
|
||||
if (localDataMap.contains(imageSource)) {
|
||||
imageData = localDataMap[imageSource];
|
||||
if (modified == imageData.time)
|
||||
reload = false;
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
QImage originalImage;
|
||||
originalImage.load(imageSource);
|
||||
if (!originalImage.isNull()) {
|
||||
imageData.image = originalImage.scaled(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2,
|
||||
Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2,
|
||||
Qt::KeepAspectRatio);
|
||||
imageData.image.setDevicePixelRatio(2.);
|
||||
|
||||
double imgSize = double(imageFi.size());
|
||||
imageData.type = QStringLiteral("%1 (%2)").arg(QString::fromLatin1(modelNode.type())).arg(imageFi.suffix());
|
||||
imageData.id = modelNode.id();
|
||||
static QStringList units({QObject::tr("B"), QObject::tr("KB"), QObject::tr("MB"), QObject::tr("GB")});
|
||||
int unitIndex = 0;
|
||||
while (imgSize > 1024. && unitIndex < units.size() - 1) {
|
||||
++unitIndex;
|
||||
imgSize /= 1024.;
|
||||
}
|
||||
imageData.info = QStringLiteral("%1 x %2 (%3%4)").arg(originalImage.width()).arg(originalImage.height())
|
||||
.arg(QString::number(imgSize, 'g', 3)).arg(units[unitIndex]);
|
||||
localDataMap.insert(imageSource, imageData);
|
||||
}
|
||||
}
|
||||
|
||||
return imageDataToVariant(imageData);
|
||||
}
|
||||
|
||||
QVariant ModelPrivate::previewImageDataFor3DNode(const ModelNode &modelNode)
|
||||
{
|
||||
QFileInfo docFi = QFileInfo(m_fileUrl.toLocalFile());
|
||||
QHash<QString, Internal::ImageData> &localDataMap = Internal::imageDataMap[docFi.absoluteFilePath()];
|
||||
Internal::ImageData imageData;
|
||||
static const QImage placeHolder(":/navigator/icon/tooltip_placeholder.png");
|
||||
|
||||
// We need puppet to generate the image, which needs to be asynchronous.
|
||||
// Until the image is ready, we show a placeholder
|
||||
const QString id = modelNode.id();
|
||||
if (localDataMap.contains(id)) {
|
||||
imageData = localDataMap[id];
|
||||
} else {
|
||||
imageData.type = QString::fromLatin1(modelNode.type());
|
||||
imageData.id = id;
|
||||
imageData.image = placeHolder;
|
||||
localDataMap.insert(id, imageData);
|
||||
}
|
||||
modelNode.model()->nodeInstanceView()->requestModelNodePreviewImage(modelNode);
|
||||
|
||||
return imageDataToVariant(imageData);
|
||||
}
|
||||
|
||||
void ModelPrivate::updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image)
|
||||
{
|
||||
QFileInfo docFi = QFileInfo(m_fileUrl.toLocalFile());
|
||||
QString docPath = docFi.absoluteFilePath();
|
||||
if (imageDataMap.contains(docPath)) {
|
||||
QHash<QString, ImageData> &localDataMap = imageDataMap[docPath];
|
||||
if (localDataMap.contains(modelNode.id()))
|
||||
localDataMap[modelNode.id()].image = image;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelPrivate::removeAllSubNodes(const InternalNode::Pointer &internalNodePointer)
|
||||
{
|
||||
foreach (const InternalNodePointer &subNode, internalNodePointer->allSubNodes()) {
|
||||
@@ -808,8 +702,6 @@ void ModelPrivate::notifyUpdateActiveScene3D(const QVariantMap &sceneState)
|
||||
|
||||
void ModelPrivate::notifyModelNodePreviewImageChanged(const ModelNode &node, const QImage &image)
|
||||
{
|
||||
updatePreviewImageForNode(node, image);
|
||||
|
||||
for (const QPointer<AbstractView> &view : qAsConst(m_viewList)) {
|
||||
Q_ASSERT(view != nullptr);
|
||||
view->modelNodePreviewImageChanged(node, image);
|
||||
@@ -2183,16 +2075,6 @@ QList<ModelNode> Model::selectedNodes(AbstractView *view) const
|
||||
return d->toModelNodeList(d->selectedNodes(), view);
|
||||
}
|
||||
|
||||
QVariant Model::previewImageDataFor3DNode(const ModelNode &modelNode)
|
||||
{
|
||||
return d->previewImageDataFor3DNode(modelNode);
|
||||
}
|
||||
|
||||
QVariant Model::previewImageDataForImageNode(const ModelNode &modelNode)
|
||||
{
|
||||
return d->previewImageDataForImageNode(modelNode);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Returns the URL against which relative URLs within the model should be resolved.
|
||||
\return The base URL.
|
||||
|
||||
@@ -243,10 +243,6 @@ private: //functions
|
||||
QVector<ModelNode> toModelNodeVector(const QVector<InternalNodePointer> &internalNodeVector, AbstractView *view) const;
|
||||
QVector<InternalNodePointer> toInternalNodeVector(const QVector<ModelNode> &internalNodeVector) const;
|
||||
|
||||
QVariant previewImageDataFor3DNode(const ModelNode &modelNode);
|
||||
QVariant previewImageDataForImageNode(const ModelNode &modelNode);
|
||||
void updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image);
|
||||
|
||||
private:
|
||||
Model *m_q;
|
||||
MetaInfo m_metaInfo;
|
||||
|
||||
Reference in New Issue
Block a user