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