1 #include "ThumbnailImageItem.hxx"
2
3 #include <QSGSimpleTextureNode>
4 #include <QQuickWindow>
5 #include <QFileInfo>
6 #include <QDir>
7
8 #include <Main/globals.hxx>
9
10 #include <simgear/package/Root.hxx>
11 #include <simgear/package/Package.hxx>
12 #include <simgear/package/Delegate.hxx>
13 #include <simgear/package/Catalog.hxx>
14 #include <simgear/package/Install.hxx>
15
16 using namespace simgear;
17
18 const int STANDARD_THUMBNAIL_HEIGHT = 128;
19 const int STANDARD_THUMBNAIL_WIDTH = 172;
20
21 class ThumbnailImageItem::ThumbnailPackageDelegate : public pkg::Delegate
22 {
23 public:
ThumbnailPackageDelegate(ThumbnailImageItem * o)24 ThumbnailPackageDelegate(ThumbnailImageItem* o) : owner(o) {}
25
catalogRefreshed(pkg::CatalogRef,StatusCode)26 void catalogRefreshed(pkg::CatalogRef, StatusCode) override {}
startInstall(pkg::InstallRef)27 void startInstall(pkg::InstallRef) override {}
installProgress(pkg::InstallRef,unsigned int,unsigned int)28 void installProgress(pkg::InstallRef, unsigned int, unsigned int) override {}
finishInstall(pkg::InstallRef,StatusCode)29 void finishInstall(pkg::InstallRef, StatusCode ) override {}
30 void dataForThumbnail(const std::string& aThumbnailUrl,
31 size_t length, const uint8_t* bytes) override;
32
33 ThumbnailImageItem* owner;
34 };
35
dataForThumbnail(const std::string & aThumbnailUrl,size_t length,const uint8_t * bytes)36 void ThumbnailImageItem::ThumbnailPackageDelegate::dataForThumbnail(const std::string& aThumbnailUrl,
37 size_t length, const uint8_t* bytes)
38 {
39 if (aThumbnailUrl != owner->url().toString().toStdString()) {
40 return;
41 }
42
43 const auto iLength = static_cast<int>(length);
44 QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), iLength));
45 if (img.isNull()) {
46 if (length > 0) {
47 // warn if we had valid bytes but couldn't load it, i.e corrupted data or similar
48 qWarning() << "failed to load image data for URL:" << QString::fromStdString(aThumbnailUrl);
49 owner->clearImage();
50 }
51 return;
52 }
53
54 owner->setImage(img);
55 }
56
ThumbnailImageItem(QQuickItem * parent)57 ThumbnailImageItem::ThumbnailImageItem(QQuickItem* parent) :
58 QQuickItem(parent),
59 m_delegate(new ThumbnailPackageDelegate(this)),
60 m_maximumSize(9999, 9999)
61 {
62 globals->packageRoot()->addDelegate(m_delegate.get());
63 setFlag(ItemHasContents);
64 setImplicitWidth(STANDARD_THUMBNAIL_WIDTH);
65 setImplicitHeight(STANDARD_THUMBNAIL_HEIGHT);
66 }
67
~ThumbnailImageItem()68 ThumbnailImageItem::~ThumbnailImageItem()
69 {
70 globals->packageRoot()->removeDelegate(m_delegate.get());
71 }
72
updatePaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData *)73 QSGNode *ThumbnailImageItem::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData *)
74 {
75 if (m_image.isNull()) {
76 delete oldNode;
77 return nullptr;
78 }
79
80 QSGSimpleTextureNode* textureNode = static_cast<QSGSimpleTextureNode*>(oldNode);
81 if (m_imageDirty || !textureNode) {
82 if (!textureNode) {
83 textureNode = new QSGSimpleTextureNode;
84 textureNode->setOwnsTexture(true);
85 }
86 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
87 QSGTexture* tex = window()->createTextureFromImage(m_image, QQuickWindow::TextureIsOpaque);
88 #else
89 QSGTexture* tex = window()->createTextureFromImage(m_image);
90 #endif
91 textureNode->setTexture(tex);
92 textureNode->markDirty(QSGBasicGeometryNode::DirtyMaterial);
93 m_imageDirty = false;
94 }
95
96 textureNode->setRect(QRectF(0, 0, width(), height()));
97 return textureNode;
98 }
99
url() const100 QUrl ThumbnailImageItem::url() const
101 {
102 return m_imageUrl;
103 }
104
aircraftUri() const105 QString ThumbnailImageItem::aircraftUri() const
106 {
107 return m_aircraftUri;
108 }
109
sourceSize() const110 QSize ThumbnailImageItem::sourceSize() const
111 {
112 return m_image.size();
113 }
114
maximumSize() const115 QSize ThumbnailImageItem::maximumSize() const
116 {
117 return m_maximumSize;
118 }
119
setAircraftUri(QString uri)120 void ThumbnailImageItem::setAircraftUri(QString uri)
121 {
122 if (m_aircraftUri == uri)
123 return;
124
125 m_aircraftUri = uri;
126
127 if (uri.startsWith("package:")) {
128 const std::string packageId = m_aircraftUri.toStdString().substr(8);
129 pkg::Root* root = globals->packageRoot();
130 pkg::PackageRef package = root->getPackageById(packageId);
131 if (package) {
132 auto variant = package->indexOfVariant(packageId);
133 const auto thumbnail = package->thumbnailForVariant(variant);
134 m_imageUrl = QUrl(QString::fromStdString(thumbnail.url));
135 if (m_imageUrl.isValid()) {
136 globals->packageRoot()->requestThumbnailData(m_imageUrl.toString().toStdString());
137 } else {
138 clearImage();
139 }
140 }
141 } else {
142 QFileInfo aircraftSetPath(QUrl(uri).toLocalFile());
143 const QString thumbnailPath = aircraftSetPath.dir().filePath("thumbnail.jpg");
144 m_imageUrl = QUrl::fromLocalFile(thumbnailPath);
145
146 if (QFileInfo(thumbnailPath).exists()) {
147 QImage img;
148 if (img.load(thumbnailPath)) {
149 setImage(img);
150 } else {
151 qWarning() << Q_FUNC_INFO << "failed to load thumbnail from:" << thumbnailPath;
152 clearImage();
153 }
154 }
155 } // of local aircraft case
156
157 emit aircraftUriChanged();
158 }
159
setMaximumSize(QSize maximumSize)160 void ThumbnailImageItem::setMaximumSize(QSize maximumSize)
161 {
162 if (m_maximumSize == maximumSize)
163 return;
164
165 m_maximumSize = maximumSize;
166 emit maximumSizeChanged(m_maximumSize);
167
168 if (!m_image.isNull()) {
169 setImplicitSize(qMin(m_maximumSize.width(), m_image.width()),
170 qMin(m_maximumSize.height(), m_image.height()));
171 }
172 }
173
setImage(QImage image)174 void ThumbnailImageItem::setImage(QImage image)
175 {
176 m_image = image;
177 m_imageDirty = true;
178 setImplicitSize(qMin(m_maximumSize.width(), m_image.width()),
179 qMin(m_maximumSize.height(), m_image.height()));
180 emit sourceSizeChanged();
181 update();
182 }
183
clearImage()184 void ThumbnailImageItem::clearImage()
185 {
186 m_image = QImage{};
187 m_imageDirty = true;
188 setImplicitSize(m_maximumSize.width(), m_maximumSize.height());
189 emit sourceSizeChanged();
190 update();
191 }
192