1 /*
2  * Stellarium
3  * Copyright (C) 2008 Fabien Chereau
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #include "StelSkyLayerMgr.hpp"
21 #include "StelApp.hpp"
22 #include "StelCore.hpp"
23 #include "StelFileMgr.hpp"
24 #include "StelProjector.hpp"
25 #include "StelSkyImageTile.hpp"
26 #include "StelModuleMgr.hpp"
27 #include "StelPainter.hpp"
28 #include "MilkyWay.hpp"
29 #include "StelGuiBase.hpp"
30 #include "StelSkyDrawer.hpp"
31 #include "StelTranslator.hpp"
32 #include "StelProgressController.hpp"
33 
34 #include <QNetworkAccessManager>
35 #include <stdexcept>
36 #include <QDebug>
37 #include <QString>
38 #include <QVariantMap>
39 #include <QVariantList>
40 #include <QDir>
41 #include <QSettings>
42 
StelSkyLayerMgr(void)43 StelSkyLayerMgr::StelSkyLayerMgr(void) : flagShow(true)
44 {
45 	setObjectName("StelSkyLayerMgr");
46 }
47 
~StelSkyLayerMgr()48 StelSkyLayerMgr::~StelSkyLayerMgr()
49 {
50 	for (auto* s : allSkyLayers)
51 		delete s;
52 }
53 
54 /*************************************************************************
55  Reimplementation of the getCallOrder method
56 *************************************************************************/
getCallOrder(StelModuleActionName actionName) const57 double StelSkyLayerMgr::getCallOrder(StelModuleActionName actionName) const
58 {
59 	if (actionName==StelModule::ActionDraw)
60 		return GETSTELMODULE(MilkyWay)->getCallOrder(actionName)+5;
61 	return 0;
62 }
63 
64 // read from stream
init()65 void StelSkyLayerMgr::init()
66 {
67 	loadCollection();
68 
69 	QSettings* conf = StelApp::getInstance().getSettings();
70 	conf->beginGroup("skylayers");
71 	for (const auto& key : conf->childKeys())
72 	{
73 		QString uri = conf->value(key, "").toString();
74 		if (!uri.isEmpty())
75 		{
76 			if (key=="clilayer")
77 				insertSkyImage(uri, "Command-line layer");
78 			else
79 				insertSkyImage(uri);
80 		}
81 	}
82 	conf->endGroup();
83 
84 	setFlagShow(!conf->value("astro/flag_nebula_display_no_texture", false).toBool());
85 	addAction("actionShow_DSO_Textures", N_("Display Options"), N_("Deep-sky objects background images"), "flagShow", "I");
86 	addAction("actionShow_DSO_Textures_Reload", N_("Display Options"), N_("Reload the deep-sky objects background images"), "loadCollection()", "Ctrl+I");
87 }
88 
loadCollection()89 void StelSkyLayerMgr::loadCollection()
90 {
91 	if (!allSkyLayers.isEmpty())
92 		allSkyLayers.clear();
93 
94 	QString path = StelFileMgr::findFile("nebulae/default/textures.json");
95 	if (path.isEmpty())
96 		qWarning() << "ERROR while loading nebula texture set default";
97 	else
98 		insertSkyImage(path);
99 }
100 
insertSkyLayer(StelSkyLayerP tile,const QString & keyHint,bool ashow)101 QString StelSkyLayerMgr::insertSkyLayer(StelSkyLayerP tile, const QString& keyHint, bool ashow)
102 {
103 	SkyLayerElem* bEl = new SkyLayerElem(tile, ashow);
104 	QString key = tile->getKeyHint();
105 	if (key.isEmpty() || key=="no name")
106 	{
107 		if (!keyHint.isEmpty())
108 		{
109 			key = keyHint;
110 		}
111 		else
112 		{
113 			key = "no name";
114 		}
115 	}
116 	if (allSkyLayers.contains(key))
117 	{
118 		QString suffix = "_01";
119 		int i=1;
120 		while (allSkyLayers.contains(key+suffix))
121 		{
122 			suffix=QString("_%1").arg(i);
123 			++i;
124 		}
125 		key+=suffix;
126 	}
127 	allSkyLayers.insert(key,bEl);
128 	connect(bEl->layer.data(), SIGNAL(loadingStateChanged(bool)), this, SLOT(loadingStateChanged(bool)));
129 	connect(bEl->layer.data(), SIGNAL(percentLoadedChanged(int)), this, SLOT(percentLoadedChanged(int)));
130 	return key;
131 }
132 
133 // Add a new image from its URI (URL or local file name)
insertSkyImage(const QString & uri,const QString & keyHint,bool ashow)134 QString StelSkyLayerMgr::insertSkyImage(const QString& uri, const QString& keyHint, bool ashow)
135 {
136 	return insertSkyLayer(StelSkyLayerP(new StelSkyImageTile(uri)), keyHint, ashow);
137 }
138 
139 // Remove a sky image tile from the list of background images
removeSkyLayer(const QString & key)140 void StelSkyLayerMgr::removeSkyLayer(const QString& key)
141 {
142 	//qDebug() << "StelSkyLayerMgr::removeSkyImage removing image:" << key;
143 	if (allSkyLayers.contains(key))
144 	{
145 		SkyLayerElem* bEl = allSkyLayers[key];
146 		disconnect(bEl->layer.data(), SIGNAL(loadingStateChanged(bool)), this, SLOT(loadingStateChanged(bool)));
147 		disconnect(bEl->layer.data(), SIGNAL(percentLoadedChanged(int)), this, SLOT(percentLoadedChanged(int)));
148 		delete bEl;
149 		allSkyLayers.remove(key);
150 	}
151 	else
152 	{
153 		qDebug() << "StelSkyLayerMgr::removeSkyLayer there is no such key" << key << "nothing is removed";
154 	}
155 }
156 
157 // Remove a sky image tile from the list of background images
removeSkyLayer(StelSkyLayerP l)158 void StelSkyLayerMgr::removeSkyLayer(StelSkyLayerP l)
159 {
160 	const QString k = keyForLayer(l.data());
161 	if (!k.isEmpty())
162 		removeSkyLayer(k);
163 }
164 
165 // Draw all the multi-res images collection
draw(StelCore * core)166 void StelSkyLayerMgr::draw(StelCore* core)
167 {
168 	if (!flagShow)
169 		return;
170 
171 	StelPainter sPainter(core->getProjection(StelCore::FrameJ2000));
172 	sPainter.setBlending(true, GL_ONE, GL_ONE); //additive blending
173 	for (auto* s : allSkyLayers)
174 	{
175 		if (s->show)
176 		{
177 			sPainter.setProjector(core->getProjection(s->layer->getFrameType()));
178 			s->layer->draw(core, sPainter, 1.);
179 		}
180 	}
181 }
182 
noDelete(StelSkyLayer *)183 void noDelete(StelSkyLayer*) {;}
184 
185 // Called when loading of data started or stopped for one collection
loadingStateChanged(bool b)186 void StelSkyLayerMgr::loadingStateChanged(bool b)
187 {
188 	StelSkyLayer* tile = qobject_cast<StelSkyLayer*>(QObject::sender());
189 	Q_ASSERT(tile!=Q_NULLPTR);
190 	SkyLayerElem* elem = skyLayerElemForLayer(tile);
191 	Q_ASSERT(elem!=Q_NULLPTR);
192 	if (b)
193 	{
194 		Q_ASSERT(elem->progressBar==Q_NULLPTR);
195 		elem->progressBar = StelApp::getInstance().addProgressBar();
196 		QString serverStr = elem->layer->getShortServerCredits();
197 		if (!serverStr.isEmpty())
198 			serverStr = " from "+serverStr;
199 		elem->progressBar->setFormat("Loading "+elem->layer->getShortName()+serverStr);
200 		elem->progressBar->setRange(0,100);
201 	}
202 	else
203 	{
204 		Q_ASSERT(elem->progressBar!=Q_NULLPTR);
205 		StelApp::getInstance().removeProgressBar(elem->progressBar);
206 		elem->progressBar = Q_NULLPTR;
207 	}
208 }
209 
210 // Called when the percentage of loading tiles/tiles to be displayed changed for one collection
percentLoadedChanged(int percentage)211 void StelSkyLayerMgr::percentLoadedChanged(int percentage)
212 {
213 	StelSkyLayer* tile = qobject_cast<StelSkyLayer*>(QObject::sender());
214 	Q_ASSERT(tile!=Q_NULLPTR);
215 	SkyLayerElem* elem = skyLayerElemForLayer(tile);
216 	Q_ASSERT(elem!=Q_NULLPTR);
217 	Q_ASSERT(elem->progressBar!=Q_NULLPTR);
218 	elem->progressBar->setValue(percentage);
219 }
220 
skyLayerElemForLayer(const StelSkyLayer * t)221 StelSkyLayerMgr::SkyLayerElem* StelSkyLayerMgr::skyLayerElemForLayer(const StelSkyLayer* t)
222 {
223 	for (auto* e : allSkyLayers)
224 	{
225 		if (e->layer==t)
226 		{
227 			return e;
228 		}
229 	}
230 	return Q_NULLPTR;
231 }
232 
keyForLayer(const StelSkyLayer * t)233 QString StelSkyLayerMgr::keyForLayer(const StelSkyLayer* t)
234 {
235 	return allSkyLayers.key(skyLayerElemForLayer(t));
236 }
237 
SkyLayerElem(StelSkyLayerP t,bool ashow)238 StelSkyLayerMgr::SkyLayerElem::SkyLayerElem(StelSkyLayerP t, bool ashow) : layer(t), progressBar(Q_NULLPTR), show(ashow)
239 {;}
240 
~SkyLayerElem()241 StelSkyLayerMgr::SkyLayerElem::~SkyLayerElem()
242 {
243 	if (progressBar)
244 		StelApp::getInstance().removeProgressBar(progressBar);
245 	progressBar = Q_NULLPTR;
246 }
247 
loadSkyImage(const QString & id,const QString & filename,double long0,double lat0,double long1,double lat1,double long2,double lat2,double long3,double lat3,double minRes,double maxBright,bool visible,StelCore::FrameType frameType,bool withAberration)248 bool StelSkyLayerMgr::loadSkyImage(const QString& id, const QString& filename,
249 								   double long0, double lat0,
250 								   double long1, double lat1,
251 								   double long2, double lat2,
252 								   double long3, double lat3,
253 								   double minRes, double maxBright, bool visible, StelCore::FrameType frameType, bool withAberration)
254 {
255 	if (allSkyLayers.contains(id))
256 	{
257 		qWarning() << "Image ID" << id << "already exists, removing old image before loading";
258 		removeSkyLayer(id);
259 	}
260 
261 	QString path = StelFileMgr::findFile(filename);
262 	if (path.isEmpty())
263 	{
264 		qWarning() << "Could not find image" << QDir::toNativeSeparators(filename);
265 		return false;
266 	}
267 	QVariantMap vm;
268 	QVariantList cl; // coordinates list for adding worldCoords and textureCoords
269 	QVariantList c;  // a list for a pair of coordinates
270 	QVariantList ol; // outer list - we want a structure 3 levels deep...
271 	vm["imageUrl"] = QVariant(path);
272 	vm["maxBrightness"] = QVariant(maxBright);
273 	vm["minResolution"] = QVariant(minRes);
274 	vm["shortName"] = QVariant(id);
275 	vm["withAberration"] = QVariant(withAberration);
276 
277 	// textureCoords (define the ordering of worldCoords)
278 	cl.clear();
279 	ol.clear();
280 	c.clear(); c.append(0); c.append(0); cl.append(QVariant(c));
281 	c.clear(); c.append(1); c.append(0); cl.append(QVariant(c));
282 	c.clear(); c.append(1); c.append(1); cl.append(QVariant(c));
283 	c.clear(); c.append(0); c.append(1); cl.append(QVariant(c));
284 	ol.append(QVariant(cl));
285 	vm["textureCoords"] = ol;
286 
287 	// world coordinates
288 	cl.clear();
289 	ol.clear();
290 	c.clear(); c.append(long0); c.append(lat0); cl.append(QVariant(c));
291 	c.clear(); c.append(long1); c.append(lat1); cl.append(QVariant(c));
292 	c.clear(); c.append(long2); c.append(lat2); cl.append(QVariant(c));
293 	c.clear(); c.append(long3); c.append(lat3); cl.append(QVariant(c));
294 	ol.append(QVariant(cl));
295 	vm["worldCoords"] = ol;
296 
297 	vm["alphaBlend"] = true; // new 2017-3: Make black correctly see-through.
298 
299 	StelSkyLayerP tile = StelSkyLayerP(new StelSkyImageTile(vm, Q_NULLPTR));
300 	tile->setFrameType(frameType);
301 
302 	try
303 	{
304 		QString key = insertSkyLayer(tile, filename, visible);
305 		if (key == id)
306 			return true;
307 		else
308 			return false;
309 	}
310 	catch (std::runtime_error& e)
311 	{
312 		qWarning() << e.what();
313 		return false;
314 	}
315 }
316 
showLayer(const QString & id,bool b)317 void StelSkyLayerMgr::showLayer(const QString& id, bool b)
318 {
319 	if (allSkyLayers.contains(id))
320 	{
321 		if (allSkyLayers[id]!=Q_NULLPTR)
322 			allSkyLayers[id]->show = b;
323 	}
324 }
325 
getShowLayer(const QString & id) const326 bool StelSkyLayerMgr::getShowLayer(const QString& id) const
327 {
328 	if (allSkyLayers.contains(id))
329 	{
330 		if (allSkyLayers[id]!=Q_NULLPTR)
331 			return allSkyLayers[id]->show;
332 	}
333 	return false;
334 }
335 
336 
337 //! Get the list of all the currently loaded layers.
getAllSkyLayers() const338 QMap<QString, StelSkyLayerP> StelSkyLayerMgr::getAllSkyLayers() const
339 {
340 	QMap<QString, StelSkyLayerP> res;
341 	for (QMap<QString, StelSkyLayerMgr::SkyLayerElem*>::ConstIterator iter=allSkyLayers.constBegin();iter!=allSkyLayers.constEnd();++iter)
342 	{
343 		//qDebug() << iter.key() << iter.value()->layer->getShortName();
344 		res.insert(iter.key(), iter.value()->layer);
345 	}
346 	return res;
347 }
348 
getSkyLayer(const QString & key) const349 StelSkyLayerP StelSkyLayerMgr::getSkyLayer(const QString& key) const
350 {
351 	if (allSkyLayers.contains(key))
352 		return allSkyLayers[key]->layer;
353 	return StelSkyLayerP();
354 }
355