1 /*
2  * Stellarium
3  * Copyright (C) 2007 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 "StelMainView.hpp"
21 #include "StelApp.hpp"
22 #include "StelCore.hpp"
23 #include "StelFileMgr.hpp"
24 #include "StelProjector.hpp"
25 #include "StelModuleMgr.hpp"
26 #include "StelPainter.hpp"
27 #include "StelGui.hpp"
28 #include "SkyGui.hpp"
29 #include "StelTranslator.hpp"
30 #include "StelUtils.hpp"
31 #include "StelActionMgr.hpp"
32 #include "StelOpenGL.hpp"
33 #include "StelOpenGLArray.hpp"
34 #include "StelProjector.hpp"
35 #include "StelMovementMgr.hpp"
36 
37 #include <QDebug>
38 #include <QDir>
39 #include <QOpenGLWidget>
40 #include <QApplication>
41 #include <QDesktopWidget>
42 #include <QGuiApplication>
43 #include <QGraphicsSceneMouseEvent>
44 #include <QGraphicsAnchorLayout>
45 #include <QGraphicsWidget>
46 #include <QGraphicsEffect>
47 #include <QFileInfo>
48 #include <QIcon>
49 #include <QImageWriter>
50 #include <QMoveEvent>
51 #include <QPluginLoader>
52 #include <QScreen>
53 #include <QSettings>
54 #include <QRegularExpression>
55 #include <QtPlugin>
56 #include <QThread>
57 #include <QTimer>
58 #include <QWidget>
59 #include <QWindow>
60 #include <QMessageBox>
61 #include <QStandardPaths>
62 #ifdef Q_OS_WIN
63 	#include <QPinchGesture>
64 #endif
65 #include <QOpenGLShader>
66 #include <QOpenGLShaderProgram>
67 #include <QOpenGLFramebufferObject>
68 #include <QOpenGLPaintDevice>
69 #ifdef OPENGL_DEBUG_LOGGING
70 #include <QOpenGLDebugLogger>
71 #endif
72 #include <QLoggingCategory>
73 
74 Q_LOGGING_CATEGORY(mainview, "stel.MainView")
75 
76 #include <clocale>
77 
78 // Initialize static variables
79 StelMainView* StelMainView::singleton = Q_NULLPTR;
80 
81 class StelGLWidget : public QOpenGLWidget
82 {
83 public:
StelGLWidget(const QSurfaceFormat & fmt,StelMainView * parent)84 	StelGLWidget(const QSurfaceFormat& fmt, StelMainView* parent)
85 		:
86 		  QOpenGLWidget(parent),
87 		  parent(parent),
88 		  initialized(false)
89 	{
90 		qDebug()<<"StelGLWidget constructor";
91 		setFormat(fmt);
92 
93 		//because we always draw the full background,
94 		//lets skip drawing the system background
95 		setAttribute(Qt::WA_OpaquePaintEvent);
96 		setAttribute(Qt::WA_AcceptTouchEvents);
97 		setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents);
98 		setAutoFillBackground(false);
99 	}
100 
~StelGLWidget()101 	~StelGLWidget() Q_DECL_OVERRIDE
102 	{
103 		qDebug()<<"StelGLWidget destroyed";
104 	}
105 
initializeGL()106 	virtual void initializeGL() Q_DECL_OVERRIDE
107 	{
108 		if(initialized)
109 		{
110 			qWarning()<<"Double initialization, should not happen";
111 			Q_ASSERT(false);
112 			return;
113 		}
114 
115 		//This seems to be the correct place to initialize all
116 		//GL related stuff of the application
117 		//this includes all the init() calls of the modules
118 
119 		QOpenGLContext* ctx = context();
120 		Q_ASSERT(ctx == QOpenGLContext::currentContext());
121 		StelOpenGL::mainContext = ctx; //throw an error when StelOpenGL functions are executed in another context
122 
123 		qDebug()<<"initializeGL";
124 		qDebug() << "OpenGL supported version: " << QString(reinterpret_cast<const char*>(ctx->functions()->glGetString(GL_VERSION)));
125 		qDebug() << "Current Format: " << this->format();
126 
127 		if (qApp->property("onetime_compat33")==true)
128 		{
129 			// This may not return the version number set previously!
130 			qDebug() << "StelGLWidget context format version:" << ctx->format().majorVersion() << "." << context()->format().minorVersion();
131 			qDebug() << "StelGLWidget has CompatibilityProfile:" << (ctx->format().profile()==QSurfaceFormat::CompatibilityProfile ? "yes" : "no") << "(" <<context()->format().profile() << ")";
132 		}
133 
134 		parent->init();
135 		initialized = true;
136 	}
137 
138 protected:
paintGL()139 	virtual void paintGL() Q_DECL_OVERRIDE
140 	{
141 		//this is actually never called because the
142 		//QGraphicsView intercepts the paint event
143 		//we have to draw in the background of the scene
144 		//or as a QGraphicsItem
145 		qDebug()<<"paintGL";
146 	}
resizeGL(int w,int h)147 	virtual void resizeGL(int w, int h) Q_DECL_OVERRIDE
148 	{
149 		//we probably can ignore this method,
150 		//it seems it is also never called
151 		qDebug()<<"resizeGL"<<w<<h;
152 	}
153 
154 private:
155 	StelMainView* parent;
156 	bool initialized;
157 };
158 
159 // A custom QGraphicsEffect to apply the night mode on top of the screen.
160 class NightModeGraphicsEffect : public QGraphicsEffect
161 {
162 public:
NightModeGraphicsEffect(StelMainView * parent=Q_NULLPTR)163 	NightModeGraphicsEffect(StelMainView* parent = Q_NULLPTR)
164 		: QGraphicsEffect(parent),
165 		  parent(parent), fbo(Q_NULLPTR)
166 	{
167 		Q_ASSERT(parent->glContext() == QOpenGLContext::currentContext());
168 
169 		program = new QOpenGLShaderProgram(this);
170 		QString vertexCode =
171 				"attribute highp vec4 a_pos;\n"
172 				"attribute highp vec2 a_texCoord;\n"
173 				"varying highp   vec2 v_texCoord;\n"
174 				"void main(void)\n"
175 				"{\n"
176 				"v_texCoord = a_texCoord;\n"
177 				"gl_Position = a_pos;\n"
178 				"}\n";
179 		QString fragmentCode =
180 				"varying highp vec2 v_texCoord;\n"
181 				"uniform sampler2D  u_source;\n"
182 				"void main(void)\n"
183 				"{\n"
184 				"	mediump vec3 color = texture2D(u_source, v_texCoord).rgb;\n"
185 				"	mediump float luminance = max(max(color.r, color.g), color.b);\n"
186 				"	gl_FragColor = vec4(luminance, luminance * 0.3, 0.0, 1.0);\n"
187 				"}\n";
188 		program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexCode);
189 		program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentCode);
190 		program->link();
191 		vars.pos = program->attributeLocation("a_pos");
192 		vars.texCoord = program->attributeLocation("a_texCoord");
193 		vars.source = program->uniformLocation("u_source");
194 	}
195 
~NightModeGraphicsEffect()196 	virtual ~NightModeGraphicsEffect() Q_DECL_OVERRIDE
197 	{
198 		// NOTE: Why Q_ASSERT is here?
199 		//Q_ASSERT(parent->glContext() == QOpenGLContext::currentContext());
200 		//clean up fbo
201 		delete fbo;
202 	}
203 protected:
draw(QPainter * painter)204 	virtual void draw(QPainter* painter) Q_DECL_OVERRIDE
205 	{
206 		Q_ASSERT(parent->glContext() == QOpenGLContext::currentContext());
207 		QOpenGLFunctions* gl = QOpenGLContext::currentContext()->functions();
208 
209 		QPaintDevice* paintDevice = painter->device();
210 
211 		int mainFBO;
212 		gl->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mainFBO);
213 
214 		double pixelRatio = paintDevice->devicePixelRatioF();
215 		QSize size(static_cast<int>(paintDevice->width() * pixelRatio), static_cast<int>(paintDevice->height() * pixelRatio));
216 		if (fbo && fbo->size() != size)
217 		{
218 			delete fbo;
219 			fbo = Q_NULLPTR;
220 		}
221 		if (!fbo)
222 		{
223 			QOpenGLFramebufferObjectFormat format;
224 			format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
225 			format.setInternalTextureFormat(GL_RGBA);
226 			fbo = new QOpenGLFramebufferObject(size, format);
227 		}
228 
229 		// we have to use our own paint device
230 		// we need this because using the original paint device (QOpenGLWidgetPaintDevice when used with QOpenGLWidget) will rebind the default FBO randomly
231 		// but using 2 GL painters at the same time can mess up the GL state, so we should close the old one first
232 
233 		// stop drawing to the old paint device to make sure state is reset correctly
234 		painter->end();
235 
236 		// create our paint device
237 		QOpenGLPaintDevice fboPaintDevice(size);
238 		fboPaintDevice.setDevicePixelRatio(pixelRatio);
239 
240 		fbo->bind();
241 		painter->begin(&fboPaintDevice);
242 		drawSource(painter);
243 		painter->end();
244 
245 		painter->begin(paintDevice);
246 
247 		//painter->beginNativePainting();
248 		program->bind();
249 		const GLfloat pos[] = {-1, -1, +1, -1, -1, +1, +1, +1};
250 		const GLfloat texCoord[] = {0, 0, 1, 0, 0, 1, 1, 1};
251 		program->setUniformValue(vars.source, 0);
252 		program->setAttributeArray(vars.pos, pos, 2);
253 		program->setAttributeArray(vars.texCoord, texCoord, 2);
254 		program->enableAttributeArray(vars.pos);
255 		program->enableAttributeArray(vars.texCoord);
256 		gl->glBindTexture(GL_TEXTURE_2D, fbo->texture());
257 		gl->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
258 		program->release();
259 		//painter->endNativePainting();
260 	}
261 
262 private:
263 	StelMainView* parent;
264 	QOpenGLFramebufferObject* fbo;
265 	QOpenGLShaderProgram *program;
266 	struct {
267 		int pos;
268 		int texCoord;
269 		int source;
270 	} vars;
271 };
272 
273 class StelGraphicsScene : public QGraphicsScene
274 {
275 public:
StelGraphicsScene(StelMainView * parent)276 	StelGraphicsScene(StelMainView* parent)
277 		: QGraphicsScene(parent), parent(parent)
278 	{
279 		qDebug()<<"StelGraphicsScene constructor";
280 	}
281 
282 protected:
keyPressEvent(QKeyEvent * event)283 	void keyPressEvent(QKeyEvent* event) Q_DECL_OVERRIDE
284 	{
285 		// Try to trigger a global shortcut.
286 		StelActionMgr* actionMgr = StelApp::getInstance().getStelActionManager();
287 		if (actionMgr->pushKey(event->key() + event->modifiers(), true)) {
288 			event->setAccepted(true);
289 			parent->thereWasAnEvent(); // Refresh screen ASAP
290 			return;
291 		}
292 		//pass event on to items otherwise
293 		QGraphicsScene::keyPressEvent(event);
294 	}
295 
296 private:
297 	StelMainView* parent;
298 };
299 
300 class StelRootItem : public QGraphicsObject
301 {
302 public:
StelRootItem(StelMainView * mainView,QGraphicsItem * parent=Q_NULLPTR)303 	StelRootItem(StelMainView* mainView, QGraphicsItem* parent = Q_NULLPTR)
304 		: QGraphicsObject(parent),
305 		  mainView(mainView),
306 		  skyBackgroundColor(0.f,0.f,0.f)
307 	{
308 		setFlag(QGraphicsItem::ItemClipsToShape);
309 		setFlag(QGraphicsItem::ItemClipsChildrenToShape);
310 		setFlag(QGraphicsItem::ItemIsFocusable);
311 
312 		setAcceptHoverEvents(true);
313 
314 #ifdef Q_OS_WIN
315 		setAcceptTouchEvents(true);
316 		grabGesture(Qt::PinchGesture);
317 #endif
318 		setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton);
319 		previousPaintTime = StelApp::getTotalRunTime();
320 	}
321 
setSize(const QSize & size)322 	void setSize(const QSize& size)
323 	{
324 		prepareGeometryChange();
325 		rect.setSize(size);
326 	}
327 
328 	//! Set the sky background color. Everything else than black creates a work of art!
setSkyBackgroundColor(Vec3f color)329 	void setSkyBackgroundColor(Vec3f color) { skyBackgroundColor=color; }
330 
331 	//! Get the sky background color. Everything else than black creates a work of art!
getSkyBackgroundColor() const332 	Vec3f getSkyBackgroundColor() const { return skyBackgroundColor; }
333 
334 
335 protected:
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)336 	virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE
337 	{
338 		Q_UNUSED(option);
339 		Q_UNUSED(widget);
340 
341 		//a sanity check
342 		Q_ASSERT(mainView->glContext() == QOpenGLContext::currentContext());
343 
344 		const double now = StelApp::getTotalRunTime();
345 		double dt = now - previousPaintTime;
346 		//qDebug()<<"dt"<<dt;
347 		previousPaintTime = now;
348 
349 		//important to call this, or Qt may have invalid state after we have drawn (wrong textures, etc...)
350 		painter->beginNativePainting();
351 
352 		//fix for bug LP:1628072 caused by QTBUG-56798
353 #ifndef QT_NO_DEBUG
354 		StelOpenGL::clearGLErrors();
355 #endif
356 
357 		//update and draw
358 		StelApp& app = StelApp::getInstance();
359 		app.update(dt); // may also issue GL calls
360 		app.draw();
361 		painter->endNativePainting();
362 
363 		mainView->drawEnded();
364 	}
365 
boundingRect() const366 	virtual QRectF boundingRect() const Q_DECL_OVERRIDE
367 	{
368 		return rect;
369 	}
370 
371 	//*** Main event handlers to pass on to StelApp ***//
mousePressEvent(QGraphicsSceneMouseEvent * event)372 	void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE
373 	{
374 		QMouseEvent ev = convertMouseEvent(event);
375 		StelApp::getInstance().handleClick(&ev);
376 		event->setAccepted(ev.isAccepted());
377 		if(ev.isAccepted())
378 			mainView->thereWasAnEvent();
379 	}
380 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)381 	void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE
382 	{
383 		QMouseEvent ev = convertMouseEvent(event);
384 		StelApp::getInstance().handleClick(&ev);
385 		event->setAccepted(ev.isAccepted());
386 		if(ev.isAccepted())
387 			mainView->thereWasAnEvent();
388 	}
389 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)390 	void mouseMoveEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE
391 	{
392 		QMouseEvent ev = convertMouseEvent(event);
393 		QPointF pos = ev.pos();
394 		event->setAccepted(StelApp::getInstance().handleMove(pos.x(), pos.y(), ev.buttons()));
395 		if(event->isAccepted())
396 			mainView->thereWasAnEvent();
397 	}
398 
wheelEvent(QGraphicsSceneWheelEvent * event)399 	void wheelEvent(QGraphicsSceneWheelEvent *event) Q_DECL_OVERRIDE
400 	{
401 		QPointF pos = event->scenePos();
402 		pos.setY(rect.height() - 1 - pos.y());
403 		QWheelEvent newEvent(QPoint(static_cast<int>(pos.x()),static_cast<int>(pos.y())), event->delta(), event->buttons(), event->modifiers(), event->orientation());
404 		StelApp::getInstance().handleWheel(&newEvent);
405 		event->setAccepted(newEvent.isAccepted());
406 		if(newEvent.isAccepted())
407 			mainView->thereWasAnEvent();
408 	}
409 
keyPressEvent(QKeyEvent * event)410 	void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE
411 	{
412 		StelApp::getInstance().handleKeys(event);
413 		if(event->isAccepted())
414 			mainView->thereWasAnEvent();
415 	}
416 
keyReleaseEvent(QKeyEvent * event)417 	void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE
418 	{
419 		StelApp::getInstance().handleKeys(event);
420 		if(event->isAccepted())
421 			mainView->thereWasAnEvent();
422 	}
423 
424 	//*** Gesture and touch support, currently only for Windows
425 #ifdef Q_OS_WIN
event(QEvent * e)426 	bool event(QEvent * e) Q_DECL_OVERRIDE
427 	{
428 		bool r = false;
429 		switch (e->type()){
430 			case QEvent::TouchBegin:
431 			case QEvent::TouchUpdate:
432 			case QEvent::TouchEnd:
433 			{
434 				QTouchEvent *touchEvent = static_cast<QTouchEvent *>(e);
435 				QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints();
436 
437 				if (touchPoints.count() == 1)
438 					setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton);
439 
440 				r = true;
441 				break;
442 			}
443 			case QEvent::Gesture:
444 				setAcceptedMouseButtons(Q_NULLPTR);
445 				r = gestureEvent(static_cast<QGestureEvent*>(e));
446 				break;
447 			default:
448 				r = QGraphicsObject::event(e);
449 		}
450 		return r;
451 	}
452 
453 private:
gestureEvent(QGestureEvent * event)454 	bool gestureEvent(QGestureEvent *event)
455 	{
456 		if (QGesture *pinch = event->gesture(Qt::PinchGesture))
457 			pinchTriggered(static_cast<QPinchGesture *>(pinch));
458 
459 		return true;
460 	}
461 
pinchTriggered(QPinchGesture * gesture)462 	void pinchTriggered(QPinchGesture *gesture)
463 	{
464 		QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
465 		if (changeFlags & QPinchGesture::ScaleFactorChanged) {
466 			qreal zoom = gesture->scaleFactor();
467 
468 			if (zoom < 2 && zoom > 0.5){
469 				StelApp::getInstance().handlePinch(zoom, true);
470 			}
471 		}
472 	}
473 #endif
474 
475 private:
476 	//! Helper function to convert a QGraphicsSceneMouseEvent to a QMouseEvent suitable for StelApp consumption
convertMouseEvent(QGraphicsSceneMouseEvent * event) const477 	QMouseEvent convertMouseEvent(QGraphicsSceneMouseEvent *event) const
478 	{
479 		//convert graphics scene mouse event to widget style mouse event
480 		QEvent::Type t = QEvent::None;
481 		switch(event->type())
482 		{
483 			case QEvent::GraphicsSceneMousePress:
484 				t = QEvent::MouseButtonPress;
485 				break;
486 			case QEvent::GraphicsSceneMouseRelease:
487 				t = QEvent::MouseButtonRelease;
488 				break;
489 			case QEvent::GraphicsSceneMouseMove:
490 				t = QEvent::MouseMove;
491 				break;
492 			case QEvent::GraphicsSceneMouseDoubleClick:
493 				//note: the old code seems to have ignored double clicks
494 				// and handled them the same as normal mouse presses
495 				//if we ever want to handle double clicks, switch out these lines
496 				t = QEvent::MouseButtonDblClick;
497 				//t = QEvent::MouseButtonPress;
498 				break;
499 			default:
500 				//warn in release and assert in debug
501 				qWarning("Unhandled mouse event type %d",event->type());
502 				Q_ASSERT(false);
503 		}
504 
505 		QPointF pos = event->scenePos();
506 		//Y needs to be inverted
507 		pos.setY(rect.height() - 1 - pos.y());
508 		return QMouseEvent(t,pos,event->button(),event->buttons(),event->modifiers());
509 	}
510 
511 	QRectF rect;
512 	double previousPaintTime;
513 	StelMainView* mainView;
514 	Vec3f skyBackgroundColor;           //! color which is used to initialize the frame. Should be black, but for some applications e.g. dark blue may be preferred.
515 };
516 
517 //! Initialize and render Stellarium gui.
518 class StelGuiItem : public QGraphicsWidget
519 {
520 public:
StelGuiItem(QGraphicsItem * parent=Q_NULLPTR)521 	StelGuiItem(QGraphicsItem* parent = Q_NULLPTR)
522 		: QGraphicsWidget(parent)
523 	{
524 		StelApp::getInstance().getGui()->init(this);
525 	}
526 
527 protected:
resizeEvent(QGraphicsSceneResizeEvent * event)528 	void resizeEvent(QGraphicsSceneResizeEvent* event) Q_DECL_OVERRIDE
529 	{
530 		Q_UNUSED(event);
531 		//widget->setGeometry(0, 0, size().width(), size().height());
532 		StelApp::getInstance().getGui()->forceRefreshGui();
533 	}
534 private:
535 	//QGraphicsWidget *widget;
536 	// void onSizeChanged();
537 };
538 
StelMainView(QSettings * settings)539 StelMainView::StelMainView(QSettings* settings)
540 	: QGraphicsView(),
541 	  configuration(settings),
542 	  guiItem(Q_NULLPTR),
543 	  gui(Q_NULLPTR),
544 	  stelApp(Q_NULLPTR),
545 	  updateQueued(false),
546 	  flagInvertScreenShotColors(false),
547 	  flagOverwriteScreenshots(false),
548 	  flagUseCustomScreenshotSize(false),
549 	  customScreenshotWidth(1024),
550 	  customScreenshotHeight(768),
551 	  customScreenshotMagnification(1.0f),
552 	  screenShotPrefix("stellarium-"),
553 	  screenShotFormat("png"),
554 	  screenShotDir(""),
555 	  flagCursorTimeout(false),
556 	  lastEventTimeSec(0.0),
557 	  minfps(1.f),
558 	  maxfps(10000.f)
559 {
560 	setAttribute(Qt::WA_OpaquePaintEvent);
561 	setAttribute(Qt::WA_AcceptTouchEvents);
562 	setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents);
563 	setAutoFillBackground(false);
564 	setMouseTracking(true);
565 
566 	StelApp::initStatic();
567 
568 	fpsTimer = new QTimer(this);
569 	fpsTimer->setTimerType(Qt::PreciseTimer);
570 	fpsTimer->setInterval(qRound(1000.f/minfps));
571 	connect(fpsTimer,SIGNAL(timeout()),this,SLOT(fpsTimerUpdate()));
572 
573 	cursorTimeoutTimer = new QTimer(this);
574 	cursorTimeoutTimer->setSingleShot(true);
575 	connect(cursorTimeoutTimer, SIGNAL(timeout()), this, SLOT(hideCursor()));
576 
577 	// Can't create 2 StelMainView instances
578 	Q_ASSERT(!singleton);
579 	singleton = this;
580 
581 	qApp->installEventFilter(this);
582 
583 	setWindowIcon(QIcon(":/mainWindow/icon.bmp"));
584 	initTitleI18n();
585 	setObjectName("MainView");
586 
587 	setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
588 	setFrameShape(QFrame::NoFrame);
589 	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
590 	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
591 	//because we only want child elements to have focus, we turn it off here
592 	setFocusPolicy(Qt::NoFocus);
593 	connect(this, SIGNAL(screenshotRequested()), this, SLOT(doScreenshot()));
594 
595 #ifdef OPENGL_DEBUG_LOGGING
596 	if (QApplication::testAttribute(Qt::AA_UseOpenGLES))
597 	{
598 		// QOpenGLDebugLogger doesn't work with OpenGLES's GL_KHR_debug.
599 		// See Qt Bug 62070: https://bugreports.qt.io/browse/QTBUG-62070
600 
601 		glLogger = Q_NULLPTR;
602 	}
603 	else
604 	{
605 		glLogger = new QOpenGLDebugLogger(this);
606 		connect(glLogger, SIGNAL(messageLogged(QOpenGLDebugMessage)), this, SLOT(logGLMessage(QOpenGLDebugMessage)));
607 	}
608 #endif
609 
610 	//get the desired opengl format parameters
611 	QSurfaceFormat glFormat = getDesiredGLFormat();
612 	// VSync control
613 	#ifdef Q_OS_OSX
614 	// FIXME: workaround for bug LP:#1705832 (https://bugs.launchpad.net/stellarium/+bug/1705832)
615 	// Qt: https://bugreports.qt.io/browse/QTBUG-53273
616 	const bool vsdef = false; // use vsync=false by default on macOS
617 	#else
618 	const bool vsdef = true;
619 	#endif
620 	if (configuration->value("video/vsync", vsdef).toBool())
621 		glFormat.setSwapInterval(1);
622 	else
623 		glFormat.setSwapInterval(0);
624 
625 	qDebug()<<"Desired surface format: "<<glFormat;
626 
627 	//we set the default format to our required format, if possible
628 	//this only works with Qt 5.4+
629 	QSurfaceFormat defFmt = glFormat;
630 	//we don't need these buffers in the background
631 	defFmt.setAlphaBufferSize(0);
632 	defFmt.setStencilBufferSize(0);
633 	defFmt.setDepthBufferSize(0);
634 	QSurfaceFormat::setDefaultFormat(defFmt);
635 
636 	//QGLWidget should set the format in constructor to prevent creating an unnecessary temporary context
637 	glWidget = new StelGLWidget(glFormat, this);
638 	setViewport(glWidget);
639 
640 	stelScene = new StelGraphicsScene(this);
641 	setScene(stelScene);
642 	scene()->setItemIndexMethod(QGraphicsScene::NoIndex);
643 	rootItem = new StelRootItem(this);
644 
645 	// Workaround (see Bug #940638) Although we have already explicitly set
646 	// LC_NUMERIC to "C" in main.cpp there seems to be a bug in OpenGL where
647 	// it will silently reset LC_NUMERIC to the value of LC_ALL during OpenGL
648 	// initialization. This has been observed on Ubuntu 11.10 under certain
649 	// circumstances, so here we set it again just to be on the safe side.
650 	setlocale(LC_NUMERIC, "C");
651 	// End workaround
652 
653 	// We cannot use global mousetracking. Only if mouse is hidden!
654 	//setMouseTracking(true);
655 }
656 
resizeEvent(QResizeEvent * event)657 void StelMainView::resizeEvent(QResizeEvent* event)
658 {
659 	if(scene())
660 	{
661 		const QSize& sz = event->size();
662 		scene()->setSceneRect(QRect(QPoint(0, 0), sz));
663 		rootItem->setSize(sz);
664 		if(guiItem)
665 			guiItem->setGeometry(QRectF(0.0,0.0,sz.width(),sz.height()));
666 	}
667 	QGraphicsView::resizeEvent(event);
668 }
669 
eventFilter(QObject * obj,QEvent * event)670 bool StelMainView::eventFilter(QObject *obj, QEvent *event)
671 {
672 	if(event->type() == QEvent::FileOpen)
673 	{
674 		QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
675 		//qDebug() << "load script:" << openEvent->file();
676 		qApp->setProperty("onetime_startup_script", openEvent->file());
677 	}
678 	return QGraphicsView::eventFilter(obj, event);
679 }
680 
mouseMoveEvent(QMouseEvent * event)681 void StelMainView::mouseMoveEvent(QMouseEvent *event)
682 {
683 	if (flagCursorTimeout)
684 	{
685 		// Show the previous cursor and reset the timeout if the current is "hidden"
686 		if (QGuiApplication::overrideCursor() && (QGuiApplication::overrideCursor()->shape() == Qt::BlankCursor) )
687 		{
688 			QGuiApplication::restoreOverrideCursor();
689 		}
690 
691 		cursorTimeoutTimer->start();
692 	}
693 
694 	QGraphicsView::mouseMoveEvent(event);
695 }
696 
697 
focusSky()698 void StelMainView::focusSky() {
699 	//scene()->setActiveWindow(0);
700 	rootItem->setFocus();
701 }
702 
~StelMainView()703 StelMainView::~StelMainView()
704 {
705 	//delete the night view graphic effect here while GL context is still valid
706 	rootItem->setGraphicsEffect(Q_NULLPTR);
707 	StelApp::deinitStatic();
708 	delete guiItem;
709 	guiItem=Q_NULLPTR;
710 }
711 
getDesiredGLFormat() const712 QSurfaceFormat StelMainView::getDesiredGLFormat() const
713 {
714 	//use the default format as basis
715 	QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
716 	qDebug()<<"Default surface format: "<<fmt;
717 
718 	//if on an GLES build, do not set the format
719 	const QOpenGLContext::OpenGLModuleType openGLModuleType=QOpenGLContext::openGLModuleType();
720 	if (openGLModuleType==QOpenGLContext::LibGL)
721 	{
722 		// OGL 2.1 + FBOs should basically be the minimum required for Stellarium
723 		fmt.setRenderableType(QSurfaceFormat::OpenGL);
724 		fmt.setMajorVersion(2);
725 		fmt.setMinorVersion(1);
726 
727 		// The following is NOT needed (or even supported) when we request a 2.1 context
728 		// The implementation may give us a newer context,
729 		// but compatibility with 2.1 should be ensured automatically
730 		//fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
731 		//fmt.setOption(QSurfaceFormat::DeprecatedFunctions);
732 	}
733 
734 	// Note: this only works if --mesa-mode was given on the command line. Auto-switch to Mesa or the driver name apparently cannot be detected at this early stage.
735 	bool isMesa= qApp->property("onetime_mesa_mode").isValid() && (qApp->property("onetime_mesa_mode")==true);
736 
737 	//request some sane buffer formats
738 	fmt.setRedBufferSize(8);
739 	fmt.setGreenBufferSize(8);
740 	fmt.setBlueBufferSize(8);
741 	fmt.setAlphaBufferSize(8);
742 	fmt.setDepthBufferSize(24);
743 	//Stencil buffer seems necessary for GUI boxes
744 	fmt.setStencilBufferSize(8);
745 	const int multisamplingLevel = configuration->value("video/multisampling", 0).toInt();
746 	if(  multisamplingLevel  && (qApp->property("spout").toString() == "none") && (!isMesa) )
747 		fmt.setSamples(multisamplingLevel);
748 
749 #ifdef OPENGL_DEBUG_LOGGING
750 	//try to enable GL debugging using GL_KHR_debug
751 	fmt.setOption(QSurfaceFormat::DebugContext);
752 #endif
753 	//vsync needs to be set on the default format for it to work
754 	//fmt.setSwapInterval(0);
755 
756 	return fmt;
757 }
758 
init()759 void StelMainView::init()
760 {
761 #ifdef OPENGL_DEBUG_LOGGING
762 	if (glLogger)
763 	{
764 		if(!QOpenGLContext::currentContext()->hasExtension(QByteArrayLiteral("GL_KHR_debug")))
765 			qWarning()<<"GL_KHR_debug extension missing, OpenGL debug logger will likely not work";
766 		if(glLogger->initialize())
767 		{
768 			qDebug()<<"OpenGL debug logger initialized";
769 			QVector<GLuint> disabledMsgs;
770 			//if your GL implementation spams some output you are not interested in,
771 			//you can disable their message IDs here
772 			//disabledMsgs.append(100);
773 			glLogger->disableMessages(disabledMsgs);
774 			glLogger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
775 			//the internal log buffer may not be empty, so check it
776 			for (const auto& msg : glLogger->loggedMessages())
777 			{
778 				logGLMessage(msg);
779 			}
780 		}
781 		else
782 			qWarning()<<"Failed to initialize OpenGL debug logger";
783 
784 		connect(QOpenGLContext::currentContext(),SIGNAL(aboutToBeDestroyed()),this,SLOT(contextDestroyed()));
785 		//for easier debugging, print the adress of the main GL context
786 		qDebug()<<"CurCtxPtr:"<<QOpenGLContext::currentContext();
787 	}
788 #endif
789 
790 	qDebug()<<"StelMainView::init";
791 
792 	glInfo.mainContext = QOpenGLContext::currentContext();
793 	glInfo.functions = glInfo.mainContext->functions();
794 	glInfo.vendor = QString(reinterpret_cast<const char*>(glInfo.functions->glGetString(GL_VENDOR)));
795 	glInfo.renderer = QString(reinterpret_cast<const char*>(glInfo.functions->glGetString(GL_RENDERER)));
796 
797 	gui = new StelGui();
798 
799 	// Should be check of requirements disabled? -- NO! This is intentional here, and does no harm.
800 	if (configuration->value("main/check_requirements", true).toBool())
801 	{
802 		// Find out lots of debug info about supported version of OpenGL and vendor/renderer.
803 		processOpenGLdiagnosticsAndWarnings(configuration, QOpenGLContext::currentContext());
804 	}
805 
806 	//create and initialize main app
807 	stelApp = new StelApp(this);
808 	stelApp->setGui(gui);
809 	stelApp->init(configuration);
810 	//setup StelOpenGLArray global state
811 	StelOpenGLArray::initGL();
812 	//this makes sure the app knows how large the window is
813 	connect(stelScene,SIGNAL(sceneRectChanged(QRectF)),stelApp,SLOT(glWindowHasBeenResized(QRectF)));
814 	//also immediately set the current values
815 	stelApp->glWindowHasBeenResized(stelScene->sceneRect());
816 
817 	StelActionMgr *actionMgr = stelApp->getStelActionManager();
818 	actionMgr->addAction("actionSave_Screenshot_Global", N_("Miscellaneous"), N_("Save screenshot"), this, "saveScreenShot()", "Ctrl+S");
819 	actionMgr->addAction("actionReload_Shaders", N_("Miscellaneous"), N_("Reload shaders (for development)"), this, "reloadShaders()", "Ctrl+R, P");
820 	actionMgr->addAction("actionSet_Full_Screen_Global", N_("Display Options"), N_("Full-screen mode"), this, "fullScreen", "F11");
821 
822 	StelPainter::initGLShaders();
823 
824 	guiItem = new StelGuiItem(rootItem);
825 	scene()->addItem(rootItem);
826 	//set the default focus to the sky
827 	focusSky();
828 	nightModeEffect = new NightModeGraphicsEffect(this);
829 	updateNightModeProperty(StelApp::getInstance().getVisionModeNight());
830 	//install the effect on the whole view
831 	rootItem->setGraphicsEffect(nightModeEffect);
832 
833 	int screen = configuration->value("video/screen_number", 0).toInt();
834 	if (screen < 0 || screen >= qApp->screens().count())
835 	{
836 		qWarning() << "WARNING: screen" << screen << "not found";
837 		screen = 0;
838 	}
839 	QRect screenGeom = qApp->screens().at(screen)->geometry();
840 
841 	QSize size = QSize(configuration->value("video/screen_w", screenGeom.width()).toInt(),
842 		     configuration->value("video/screen_h", screenGeom.height()).toInt());
843 
844 	bool fullscreen = configuration->value("video/fullscreen", true).toBool();
845 
846 	// Without this, the screen is not shown on a Mac + we should use resize() for correct work of fullscreen/windowed mode switch. --AW WTF???
847 	resize(size);
848 
849 	if (fullscreen)
850 	{
851 		// The "+1" below is to work around Linux/Gnome problem with mouse focus.
852 		move(screenGeom.x()+1, screenGeom.y()+1);
853 		// The fullscreen window appears on screen where is the majority of
854 		// the normal window. Therefore we crop the normal window to the
855 		// screen area to ensure that the majority is not on another screen.
856 		setGeometry(geometry() & screenGeom);
857 		setFullScreen(true);
858 	}
859 	else
860 	{
861 		setFullScreen(false);
862 		int x = configuration->value("video/screen_x", 0).toInt();
863 		int y = configuration->value("video/screen_y", 0).toInt();
864 		move(x + screenGeom.x(), y + screenGeom.y());
865 	}
866 
867 	flagInvertScreenShotColors = configuration->value("main/invert_screenshots_colors", false).toBool();
868 	screenShotFormat = configuration->value("main/screenshot_format", "png").toString();
869 	flagUseCustomScreenshotSize=configuration->value("main/screenshot_custom_size", false).toBool();
870 	customScreenshotWidth=configuration->value("main/screenshot_custom_width", 1024).toInt();
871 	customScreenshotHeight=configuration->value("main/screenshot_custom_height", 768).toInt();
872 	setFlagCursorTimeout(configuration->value("gui/flag_mouse_cursor_timeout", false).toBool());
873 	setCursorTimeout(configuration->value("gui/mouse_cursor_timeout", 10.f).toDouble());
874 	setMaxFps(configuration->value("video/maximum_fps",10000.f).toFloat());
875 	setMinFps(configuration->value("video/minimum_fps",10000.f).toFloat());
876 	setSkyBackgroundColor(Vec3f(configuration->value("color/sky_background_color", "0,0,0").toString()));
877 
878 	// XXX: This should be done in StelApp::init(), unfortunately for the moment we need to init the gui before the
879 	// plugins, because the gui creates the QActions needed by some plugins.
880 	stelApp->initPlugIns();
881 
882 	// The script manager can only be fully initialized after the plugins have loaded.
883 	stelApp->initScriptMgr();
884 
885 	// Set the global stylesheet, this is only useful for the tooltips.
886 	StelGui* gui = dynamic_cast<StelGui*>(stelApp->getGui());
887 	if (gui!=Q_NULLPTR)
888 		setStyleSheet(gui->getStelStyle().qtStyleSheet);
889 	connect(stelApp, SIGNAL(visionNightModeChanged(bool)), this, SLOT(updateNightModeProperty(bool)));
890 
891 	// I doubt this will have any effect on framerate, but may cause problems elsewhere?
892 	QThread::currentThread()->setPriority(QThread::HighestPriority);
893 #ifndef NDEBUG
894 	// Get an overview of module callOrders
895 	if (qApp->property("verbose")==true)
896 	{
897 		StelApp::getInstance().dumpModuleActionPriorities(StelModule::ActionDraw);
898 		StelApp::getInstance().dumpModuleActionPriorities(StelModule::ActionUpdate);
899 		StelApp::getInstance().dumpModuleActionPriorities(StelModule::ActionHandleMouseClicks);
900 		StelApp::getInstance().dumpModuleActionPriorities(StelModule::ActionHandleMouseMoves);
901 		StelApp::getInstance().dumpModuleActionPriorities(StelModule::ActionHandleKeys);
902 	}
903 #endif
904 
905 	// check conflicts for keyboard shortcuts...
906 	if (configuration->childGroups().contains("shortcuts"))
907 	{
908 		QStringList defaultShortcuts =  actionMgr->getShortcutsList();
909 		QStringList conflicts;
910 		configuration->beginGroup("shortcuts");
911 		QStringList cstActionNames = configuration->allKeys();
912 		QMultiMap<QString, QString> cstActionsMap; // It is possible we have a very messed-up setup with duplicates
913 		for (QStringList::const_iterator cstActionName = cstActionNames.constBegin(); cstActionName != cstActionNames.constEnd(); ++cstActionName)
914 		{
915 			#if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
916 			QStringList singleCustomActionShortcuts = configuration->value((*cstActionName).toLocal8Bit().constData()).toString().split(" ", Qt::SkipEmptyParts);
917 			#else
918 			QStringList singleCustomActionShortcuts = configuration->value((*cstActionName).toLocal8Bit().constData()).toString().split(" ", QString::SkipEmptyParts);
919 			#endif
920 			singleCustomActionShortcuts.removeAll("\"\"");
921 
922 			// Add 1-2 entries per action
923 			for (QStringList::const_iterator cstActionShortcut = singleCustomActionShortcuts.constBegin(); cstActionShortcut != singleCustomActionShortcuts.constEnd(); ++cstActionShortcut)
924 				if (strcmp( (*cstActionShortcut).toLocal8Bit().constData(), "") )
925 					cstActionsMap.insert((*cstActionShortcut), (*cstActionName));
926 		}
927 		// Now we have a QMultiMap with (customShortcut, actionName). It may contain multiple keys!
928 		QStringList allMapKeys=cstActionsMap.keys();
929 		QStringList uniqueMapKeys=cstActionsMap.uniqueKeys();
930 		for (auto key : uniqueMapKeys)
931 			allMapKeys.removeOne(key);
932 		conflicts << allMapKeys; // Add the remaining (duplicate) keys
933 
934 		// Check every shortcut from the Map that it is not assigned to its own correct action
935 		for (QMultiMap<QString, QString>::const_iterator it=cstActionsMap.constBegin(); it != cstActionsMap.constEnd(); ++it)
936 		{
937 			QString customKey(it.key());
938 			QString actionName=cstActionsMap.value(it.key());
939 			StelAction *action = actionMgr->findAction(actionName);
940 			if (action && defaultShortcuts.contains(customKey) && actionMgr->findActionFromShortcut(customKey)->getId()!=action->getId())
941 				conflicts << customKey;
942 		}
943 		configuration->endGroup();
944 
945 		if (!conflicts.isEmpty())
946 		{
947 			QMessageBox::warning(&getInstance(), q_("Attention!"), QString("%1: %2").arg(q_("Shortcuts have conflicts! Please press F7 after program startup and check following multiple assignments"), conflicts.join("; ")), QMessageBox::Ok);
948 			qWarning() << "Attention! Conflicting keyboard shortcut assignments found. Please resolve:" << conflicts.join("; "); // Repeat in logfile for later retrieval.
949 		}
950 	}
951 }
952 
updateNightModeProperty(bool b)953 void StelMainView::updateNightModeProperty(bool b)
954 {
955 	// So that the bottom bar tooltips get properly rendered in night mode.
956 	setProperty("nightMode", b);
957 	nightModeEffect->setEnabled(b);
958 }
959 
reloadShaders()960 void StelMainView::reloadShaders()
961 {
962 	//make sure GL context is bound
963 	glContextMakeCurrent();
964 	emit reloadShadersRequested();
965 }
966 
967 // This is a series of various diagnostics based on "bugs" reported for 0.13.0 and 0.13.1.
968 // Almost all can be traced to insufficient driver level.
969 // No changes of OpenGL state is done here.
970 // If problems are detected, warn the user one time, but continue. Warning panel will be suppressed on next start.
971 // Work in progress, as long as we get reports about bad systems or until OpenGL startup is finalized and safe.
972 // Several tests do not apply to MacOS X.
processOpenGLdiagnosticsAndWarnings(QSettings * conf,QOpenGLContext * context) const973 void StelMainView::processOpenGLdiagnosticsAndWarnings(QSettings *conf, QOpenGLContext *context) const
974 {
975 #ifdef Q_OS_MAC
976 	Q_UNUSED(conf);
977 #endif
978 	QSurfaceFormat format=context->format();
979 
980 	// These tests are not required on MacOS X
981 #ifndef Q_OS_MAC
982 	bool openGLerror=false;
983 	if (format.renderableType()==QSurfaceFormat::OpenGL || format.renderableType()==QSurfaceFormat::OpenGLES)
984 	{
985 		qDebug() << "Detected:" << (format.renderableType()==QSurfaceFormat::OpenGL  ? "OpenGL" : "OpenGL ES" ) << QString("%1.%2").arg(format.majorVersion()).arg(format.minorVersion());
986 	}
987 	else
988 	{
989 		openGLerror=true;
990 		qDebug() << "Neither OpenGL nor OpenGL ES detected: Unsupported Format!";
991 	}
992 #endif
993 	QOpenGLFunctions* gl = context->functions();
994 
995 	QString glDriver(reinterpret_cast<const char*>(gl->glGetString(GL_VERSION)));
996 	qDebug() << "Driver version string:" << glDriver;
997 	qDebug() << "GL vendor is" << QString(reinterpret_cast<const char*>(gl->glGetString(GL_VENDOR)));
998 	QString glRenderer(reinterpret_cast<const char*>(gl->glGetString(GL_RENDERER)));
999 	qDebug() << "GL renderer is" << glRenderer;
1000 
1001 	// Minimal required version of OpenGL for Qt5 is 2.1 and OpenGL Shading Language may be 1.20 (or OpenGL ES is 2.0 and GLSL ES is 1.0).
1002 	// As of V0.13.0..1, we use GLSL 1.10/GLSL ES 1.00 (implicitly, by omitting a #version line), but in case of using ANGLE we need hardware
1003 	// detected as providing ps_3_0.
1004 	// This means, usually systems with OpenGL3 support reported in the driver will work, those with reported 2.1 only will almost certainly fail.
1005 	// If platform does not even support minimal OpenGL version for Qt5, then tell the user about troubles and quit from application.
1006 	// This test is apparently not applicable on MacOS X due to its behaving differently from all other known OSes.
1007 	// The correct way to handle driver issues on MacOS X remains however unclear for now.
1008 #ifndef Q_OS_MAC
1009 	bool isMesa=glDriver.contains("Mesa", Qt::CaseInsensitive);
1010 	#ifdef Q_OS_WIN
1011 	bool isANGLE=glRenderer.startsWith("ANGLE", Qt::CaseSensitive);
1012 	#endif
1013 	if ( openGLerror ||
1014 	     ((format.renderableType()==QSurfaceFormat::OpenGL  ) && (format.version() < QPair<int, int>(2, 1)) && !isMesa) ||
1015 	     ((format.renderableType()==QSurfaceFormat::OpenGL  ) && (format.version() < QPair<int, int>(2, 0)) &&  isMesa) || // Mesa defaults to 2.0 but works!
1016 	     ((format.renderableType()==QSurfaceFormat::OpenGLES) && (format.version() < QPair<int, int>(2, 0)))  )
1017 	{
1018 		#ifdef Q_OS_WIN
1019 		if ((!isANGLE) && (!isMesa))
1020 			qWarning() << "Oops... Insufficient OpenGL version. Please update drivers, graphics hardware, or use --angle-mode (or even --mesa-mode) option.";
1021 		else if (isANGLE)
1022 			qWarning() << "Oops... Insufficient OpenGLES version in ANGLE. Please update drivers, graphics hardware, or use --mesa-mode option.";
1023 		else
1024 			qWarning() << "Oops... Insufficient OpenGL version. Mesa failed! Please send a bug report.";
1025 
1026 		QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Insufficient OpenGL version. Please update drivers, graphics hardware, or use --angle-mode (or --mesa-mode) option."), QMessageBox::Abort, QMessageBox::Abort);
1027 		#else
1028 		qWarning() << "Oops... Insufficient OpenGL version. Please update drivers, or graphics hardware.";
1029 		QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Insufficient OpenGL version. Please update drivers, or graphics hardware."), QMessageBox::Abort, QMessageBox::Abort);
1030 		#endif
1031 		exit(1);
1032 	}
1033 #endif
1034 	// This call requires OpenGL2+.
1035 	QString glslString(reinterpret_cast<const char*>(gl->glGetString(GL_SHADING_LANGUAGE_VERSION)));
1036 	qDebug() << "GL Shading Language version is" << glslString;
1037 
1038 	// Only give extended info if called on command line, for diagnostic.
1039 	if (qApp->property("dump_OpenGL_details").toBool())
1040 		dumpOpenGLdiagnostics();
1041 
1042 #ifdef Q_OS_WIN
1043 	// If we have ANGLE, check esp. for insufficient ps_2 level.
1044 	if (isANGLE)
1045 	{
1046 		QRegularExpression angleVsPsRegExp(" vs_(\\d)_(\\d) ps_(\\d)_(\\d)");
1047 		int angleVSPSpos=glRenderer.indexOf(angleVsPsRegExp);
1048 
1049 		if (angleVSPSpos >-1)
1050 		{
1051 			QRegularExpressionMatch match=angleVsPsRegExp.match(glRenderer);
1052 			float vsVersion=match.captured(1).toFloat() + 0.1f*match.captured(2).toFloat();
1053 			float psVersion=match.captured(3).toFloat() + 0.1f*match.captured(4).toFloat();
1054 			qDebug() << "VS Version Number detected: " << vsVersion;
1055 			qDebug() << "PS Version Number detected: " << psVersion;
1056 			if ((vsVersion<2.0f) || (psVersion<3.0f))
1057 			{
1058 				openGLerror=true;
1059 				qDebug() << "This is not enough: we need DirectX9 with vs_2_0 and ps_3_0 or later.";
1060 				qDebug() << "You should update graphics drivers, graphics hardware, or use the --mesa-mode option.";
1061 				qDebug() << "Else, please try to use an older version like 0.12.9, and try with --safe-mode";
1062 
1063 				if (conf->value("main/ignore_opengl_warning", false).toBool())
1064 				{
1065 					qDebug() << "Config option main/ignore_opengl_warning found, continuing. Expect problems.";
1066 				}
1067 				else
1068 				{
1069 					qDebug() << "You can try to run in an unsupported degraded mode by ignoring the warning and continuing.";
1070 					qDebug() << "But more than likely problems will persist.";
1071 					QMessageBox::StandardButton answerButton=
1072 					QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Your DirectX/OpenGL ES subsystem has problems. See log for details.\nIgnore and suppress this notice in the future and try to continue in degraded mode anyway?"),
1073 							      QMessageBox::Ignore|QMessageBox::Abort, QMessageBox::Abort);
1074 					if (answerButton == QMessageBox::Abort)
1075 					{
1076 						qDebug() << "Aborting due to ANGLE OpenGL ES / DirectX vs or ps version problems.";
1077 						exit(1);
1078 					}
1079 					else
1080 					{
1081 						qDebug() << "Ignoring all warnings, continuing without further question.";
1082 						conf->setValue("main/ignore_opengl_warning", true);
1083 					}
1084 				}
1085 			}
1086 			else
1087 				qDebug() << "vs/ps version is fine, we should not see a graphics problem.";
1088 		}
1089 		else
1090 		{
1091 			qDebug() << "Cannot parse ANGLE shader version string. This may indicate future problems.";
1092 			qDebug() << "Please send a bug report that includes this log file and states if Stellarium runs or has problems.";
1093 		}
1094 	}
1095 #endif
1096 #ifndef Q_OS_MAC
1097 	// Do a similar test for MESA: Ensure we have at least Mesa 10, Mesa 9 on FreeBSD (used for hardware-acceleration of AMD IGP) was reported to lose the stars.
1098 	if (isMesa)
1099 	{
1100 		QRegularExpression mesaRegExp("Mesa (\\d+\\.\\d+)"); // we need only major version. Minor should always be here. Test?
1101 		int mesaPos=glDriver.indexOf(mesaRegExp);
1102 
1103 		if (mesaPos >-1)
1104 		{
1105 			float mesaVersion=mesaRegExp.match(glDriver).captured(1).toFloat();
1106 			qDebug() << "MESA Version Number detected: " << mesaVersion;
1107 			if ((mesaVersion<10.0f))
1108 			{
1109 				openGLerror=true;
1110 				qDebug() << "This is not enough: we need Mesa 10.0 or later.";
1111 				qDebug() << "You should update graphics drivers or graphics hardware.";
1112 				qDebug() << "Else, please try to use an older version like 0.12.9, and try there with --safe-mode";
1113 
1114 				if (conf->value("main/ignore_opengl_warning", false).toBool())
1115 				{
1116 					qDebug() << "Config option main/ignore_opengl_warning found, continuing. Expect problems.";
1117 				}
1118 				else
1119 				{
1120 					qDebug() << "You can try to run in an unsupported degraded mode by ignoring the warning and continuing.";
1121 					qDebug() << "But more than likely problems will persist.";
1122 					QMessageBox::StandardButton answerButton=
1123 					QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Your OpenGL/Mesa subsystem has problems. See log for details.\nIgnore and suppress this notice in the future and try to continue in degraded mode anyway?"),
1124 							      QMessageBox::Ignore|QMessageBox::Abort, QMessageBox::Abort);
1125 					if (answerButton == QMessageBox::Abort)
1126 					{
1127 						qDebug() << "Aborting due to OpenGL/Mesa insufficient version problems.";
1128 						exit(1);
1129 					}
1130 					else
1131 					{
1132 						qDebug() << "Ignoring all warnings, continuing without further question.";
1133 						conf->setValue("main/ignore_opengl_warning", true);
1134 					}
1135 				}
1136 			}
1137 			else
1138 				qDebug() << "Mesa version is fine, we should not see a graphics problem.";
1139 		}
1140 		else
1141 		{
1142 			qDebug() << "Cannot parse Mesa Driver version string. This may indicate future problems.";
1143 			qDebug() << "Please send a bug report that includes this log file and states if Stellarium runs or has problems.";
1144 		}
1145 	}
1146 #endif
1147 
1148 	// Although our shaders are only GLSL1.10, there are frequent problems with systems just at this level of programmable shaders.
1149 	// If GLSL version is less than 1.30 or GLSL ES 1.00, Stellarium usually does run properly on Windows or various Linux flavours.
1150 	// Depending on whatever driver/implementation details, Stellarium may crash or show only minor graphical errors.
1151 	// On these systems, we show a warning panel that can be suppressed by a config option which is automatically added on first run.
1152 	// Again, based on a sample size of one, Macs have been reported already to always work in this case.
1153 #ifndef Q_OS_MAC
1154 	QRegularExpression glslRegExp("^(\\d\\.\\d\\d)");
1155 	int pos=glslString.indexOf(glslRegExp);
1156 	// VC4 drivers on Raspberry Pi reports ES 1.0.16 or so, we must step down to one cipher after decimal.
1157 	QRegularExpression glslesRegExp("ES (\\d\\.\\d)");
1158 	int posES=glslString.indexOf(glslesRegExp);
1159 	if (pos >-1)
1160 	{
1161 		float glslVersion=glslRegExp.match(glslString).captured(1).toFloat();
1162 		qDebug() << "GLSL Version Number detected: " << glslVersion;
1163 		if (glslVersion<1.3f)
1164 		{
1165 			openGLerror=true;
1166 			qDebug() << "This is not enough: we need GLSL1.30 or later.";
1167 			#ifdef Q_OS_WIN
1168 			qDebug() << "You should update graphics drivers, graphics hardware, or use the --mesa-mode option.";
1169 			#else
1170 			qDebug() << "You should update graphics drivers or graphics hardware.";
1171 			#endif
1172 			qDebug() << "Else, please try to use an older version like 0.12.9, and try there with --safe-mode";
1173 
1174 			if (conf->value("main/ignore_opengl_warning", false).toBool())
1175 			{
1176 				qDebug() << "Config option main/ignore_opengl_warning found, continuing. Expect problems.";
1177 			}
1178 			else
1179 			{
1180 				qDebug() << "You can try to run in an unsupported degraded mode by ignoring the warning and continuing.";
1181 				qDebug() << "But more than likely problems will persist.";
1182 				QMessageBox::StandardButton answerButton=
1183 				QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Your OpenGL subsystem has problems. See log for details.\nIgnore and suppress this notice in the future and try to continue in degraded mode anyway?"),
1184 						      QMessageBox::Ignore|QMessageBox::Abort, QMessageBox::Abort);
1185 				if (answerButton == QMessageBox::Abort)
1186 				{
1187 					qDebug() << "Aborting due to OpenGL/GLSL version problems.";
1188 					exit(1);
1189 				}
1190 				else
1191 				{
1192 					qDebug() << "Ignoring all warnings, continuing without further question.";
1193 					conf->setValue("main/ignore_opengl_warning", true);
1194 				}
1195 			}
1196 		}
1197 		else
1198 			qDebug() << "GLSL version is fine, we should not see a graphics problem.";
1199 	}
1200 	else if (posES >-1)
1201 	{
1202 		float glslesVersion=glslesRegExp.match(glslString).captured(1).toFloat();
1203 		qDebug() << "GLSL ES Version Number detected: " << glslesVersion;
1204 		if (glslesVersion<1.0f) // TBD: is this possible at all?
1205 		{
1206 			openGLerror=true;
1207 			qDebug() << "This is not enough: we need GLSL ES 1.00 or later.";
1208 #ifdef Q_OS_WIN
1209 			qDebug() << "You should update graphics drivers, graphics hardware, or use the --mesa-mode option.";
1210 #else
1211 			qDebug() << "You should update graphics drivers or graphics hardware.";
1212 #endif
1213 			qDebug() << "Else, please try to use an older version like 0.12.5, and try there with --safe-mode";
1214 
1215 			if (conf->value("main/ignore_opengl_warning", false).toBool())
1216 			{
1217 				qDebug() << "Config option main/ignore_opengl_warning found, continuing. Expect problems.";
1218 			}
1219 			else
1220 			{
1221 				qDebug() << "You can try to run in an unsupported degraded mode by ignoring the warning and continuing.";
1222 				qDebug() << "But more than likely problems will persist.";
1223 				QMessageBox::StandardButton answerButton=
1224 				QMessageBox::critical(Q_NULLPTR, "Stellarium", q_("Your OpenGL ES subsystem has problems. See log for details.\nIgnore and suppress this notice in the future and try to continue in degraded mode anyway?"),
1225 						      QMessageBox::Ignore|QMessageBox::Abort, QMessageBox::Abort);
1226 				if (answerButton == QMessageBox::Abort)
1227 				{
1228 					qDebug() << "Aborting due to OpenGL ES/GLSL ES version problems.";
1229 					exit(1);
1230 				}
1231 				else
1232 				{
1233 					qDebug() << "Ignoring all warnings, continuing without further question.";
1234 					conf->setValue("main/ignore_opengl_warning", true);
1235 				}
1236 			}
1237 		}
1238 		else
1239 		{
1240 			if (openGLerror)
1241 				qDebug() << "GLSL ES version is OK, but there were previous errors, expect problems.";
1242 			else
1243 				qDebug() << "GLSL ES version is fine, we should not see a graphics problem.";
1244 		}
1245 	}
1246 	else
1247 	{
1248 		qDebug() << "Cannot parse GLSL (ES) version string. This may indicate future problems.";
1249 		qDebug() << "Please send a bug report that includes this log file and states if Stellarium works or has problems.";
1250 	}
1251 #endif
1252 }
1253 
1254 // Debug info about OpenGL capabilities.
dumpOpenGLdiagnostics() const1255 void StelMainView::dumpOpenGLdiagnostics() const
1256 {
1257 	QOpenGLContext *context = QOpenGLContext::currentContext();
1258 	if (context)
1259 	{
1260 		context->functions()->initializeOpenGLFunctions();
1261 		qDebug() << "initializeOpenGLFunctions()...";
1262 		QOpenGLFunctions::OpenGLFeatures oglFeatures=context->functions()->openGLFeatures();
1263 		qDebug() << "OpenGL Features:";
1264 		qDebug() << " - glActiveTexture() function" << (oglFeatures&QOpenGLFunctions::Multitexture ? "is" : "is NOT") << "available.";
1265 		qDebug() << " - Shader functions" << (oglFeatures&QOpenGLFunctions::Shaders ? "are" : "are NOT ") << "available.";
1266 		qDebug() << " - Vertex and index buffer functions" << (oglFeatures&QOpenGLFunctions::Buffers ? "are" : "are NOT") << "available.";
1267 		qDebug() << " - Framebuffer object functions" << (oglFeatures&QOpenGLFunctions::Framebuffers ? "are" : "are NOT") << "available.";
1268 		qDebug() << " - glBlendColor()" << (oglFeatures&QOpenGLFunctions::BlendColor ? "is" : "is NOT") << "available.";
1269 		qDebug() << " - glBlendEquation()" << (oglFeatures&QOpenGLFunctions::BlendEquation ? "is" : "is NOT") << "available.";
1270 		qDebug() << " - glBlendEquationSeparate()" << (oglFeatures&QOpenGLFunctions::BlendEquationSeparate ? "is" : "is NOT") << "available.";
1271 		qDebug() << " - glBlendFuncSeparate()" << (oglFeatures&QOpenGLFunctions::BlendFuncSeparate ? "is" : "is NOT") << "available.";
1272 		qDebug() << " - Blend subtract mode" << (oglFeatures&QOpenGLFunctions::BlendSubtract ? "is" : "is NOT") << "available.";
1273 		qDebug() << " - Compressed texture functions" << (oglFeatures&QOpenGLFunctions::CompressedTextures ? "are" : "are NOT") << "available.";
1274 		qDebug() << " - glSampleCoverage() function" << (oglFeatures&QOpenGLFunctions::Multisample ? "is" : "is NOT") << "available.";
1275 		qDebug() << " - Separate stencil functions" << (oglFeatures&QOpenGLFunctions::StencilSeparate ? "are" : "are NOT") << "available.";
1276 		qDebug() << " - Non power of two textures" << (oglFeatures&QOpenGLFunctions::NPOTTextures ? "are" : "are NOT") << "available.";
1277 		qDebug() << " - Non power of two textures" << (oglFeatures&QOpenGLFunctions::NPOTTextureRepeat ? "can" : "CANNOT") << "use GL_REPEAT as wrap parameter.";
1278 		qDebug() << " - The fixed function pipeline" << (oglFeatures&QOpenGLFunctions::FixedFunctionPipeline ? "is" : "is NOT") << "available.";
1279 		GLfloat lineWidthRange[2];
1280 		context->functions()->glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
1281 		qDebug() << "Line widths available from" << lineWidthRange[0] << "to" << lineWidthRange[1];
1282 
1283 		qDebug() << "OpenGL shader capabilities and details:";
1284 		qDebug() << " - Vertex Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Vertex, context) ? "YES" : "NO");
1285 		qDebug() << " - Fragment Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Fragment, context) ? "YES" : "NO");
1286 		qDebug() << " - Geometry Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Geometry, context) ? "YES" : "NO");
1287 		qDebug() << " - TessellationControl Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::TessellationControl, context) ? "YES" : "NO");
1288 		qDebug() << " - TessellationEvaluation Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::TessellationEvaluation, context) ? "YES" : "NO");
1289 		qDebug() << " - Compute Shader:" << (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Compute, context) ? "YES" : "NO");
1290 
1291 		// GZ: List available extensions. Not sure if this is in any way useful?
1292 		QSet<QByteArray> extensionSet=context->extensions();
1293 		qDebug() << "We have" << extensionSet.count() << "OpenGL extensions:";
1294 		QMap<QString, QString> extensionMap;
1295 		QSetIterator<QByteArray> iter(extensionSet);
1296 		while (iter.hasNext())
1297 		{
1298 			if (!iter.peekNext().isEmpty()) {// Don't insert empty lines
1299 				extensionMap.insert(QString(iter.peekNext()), QString(iter.peekNext()));
1300 			}
1301 			iter.next();
1302 		}
1303 		QMapIterator<QString, QString> iter2(extensionMap);
1304 		while (iter2.hasNext()) {
1305 			qDebug() << " -" << iter2.next().key();
1306 		}
1307 		// Apparently EXT_gpu_shader4 is required for GLSL1.3. (http://en.wikipedia.org/wiki/OpenGL#OpenGL_3.0).
1308 		qDebug() << "EXT_gpu_shader4" << (extensionSet.contains(("EXT_gpu_shader4")) ? "present, OK." : "MISSING!");
1309 
1310 		QFunctionPointer programParameterPtr =context->getProcAddress("glProgramParameteri");
1311 		if (programParameterPtr == Q_NULLPTR) {
1312 			qDebug() << "glProgramParameteri cannot be resolved here. BAD!";
1313 		}
1314 		programParameterPtr =context->getProcAddress("glProgramParameteriEXT");
1315 		if (programParameterPtr == Q_NULLPTR) {
1316 			qDebug() << "glProgramParameteriEXT cannot be resolved here. BAD!";
1317 		}
1318 	}
1319 	else
1320 	{
1321 		qDebug() << "dumpOpenGLdiagnostics(): No OpenGL context";
1322 	}
1323 }
1324 
deinit()1325 void StelMainView::deinit()
1326 {
1327 	glContextMakeCurrent();
1328 	deinitGL();
1329 	delete stelApp;
1330 	stelApp = Q_NULLPTR;
1331 }
1332 
1333 // Update the translated title
initTitleI18n()1334 void StelMainView::initTitleI18n()
1335 {
1336 	QString appNameI18n = q_("Stellarium %1").arg(StelUtils::getApplicationVersion());
1337 	setWindowTitle(appNameI18n);
1338 }
1339 
setFullScreen(bool b)1340 void StelMainView::setFullScreen(bool b)
1341 {
1342 	if (b)
1343 		showFullScreen();
1344 	else
1345 	{
1346 		showNormal();
1347 		// Not enough. If we had started in fullscreen, the inner part of the window is at 0/0, with the frame extending to top/left off screen.
1348 		// Therefore moving is not possible. We must move to the stored position or at least defaults.
1349 		if ( (x()<0)  && (y()<0))
1350 		{
1351 			QSettings *conf = stelApp->getSettings();
1352 			int screen = conf->value("video/screen_number", 0).toInt();
1353 			if (screen < 0 || screen >= qApp->screens().count())
1354 			{
1355 				qWarning() << "WARNING: screen" << screen << "not found";
1356 				screen = 0;
1357 			}
1358 			QRect screenGeom = qApp->screens().at(screen)->geometry();
1359 			int x = conf->value("video/screen_x", 0).toInt();
1360 			int y = conf->value("video/screen_y", 0).toInt();
1361 			move(x + screenGeom.x(), y + screenGeom.y());
1362 		}
1363 	}
1364 	emit fullScreenChanged(b);
1365 }
1366 
drawEnded()1367 void StelMainView::drawEnded()
1368 {
1369 	updateQueued = false;
1370 
1371 	int requiredFpsInterval = qRound(needsMaxFPS()?1000.f/maxfps:1000.f/minfps);
1372 
1373 	if(fpsTimer->interval() != requiredFpsInterval)
1374 		fpsTimer->setInterval(requiredFpsInterval);
1375 
1376 	if(!fpsTimer->isActive())
1377 		fpsTimer->start();
1378 }
1379 
setFlagCursorTimeout(bool b)1380 void StelMainView::setFlagCursorTimeout(bool b)
1381 {
1382 	if (b == flagCursorTimeout) return;
1383 
1384 	flagCursorTimeout = b;
1385 	if (b) 	// enable timer
1386 	{
1387 		cursorTimeoutTimer->start();
1388 	}
1389 	else	// disable timer
1390 	{
1391 		// Show the previous cursor if the current is "hidden"
1392 		if (QGuiApplication::overrideCursor() && (QGuiApplication::overrideCursor()->shape() == Qt::BlankCursor) )
1393 		{
1394 			// pop the blank cursor
1395 			QGuiApplication::restoreOverrideCursor();
1396 		}
1397 		// and stop the timer
1398 		cursorTimeoutTimer->stop();
1399 	}
1400 
1401 	emit flagCursorTimeoutChanged(b);
1402 }
1403 
hideCursor()1404 void StelMainView::hideCursor()
1405 {
1406 	// timout fired...
1407 	// if the feature is not asked, do nothing
1408 	if (!flagCursorTimeout) return;
1409 
1410 	// "hide" the current cursor by pushing a Blank cursor
1411 	QGuiApplication::setOverrideCursor(Qt::BlankCursor);
1412 }
1413 
fpsTimerUpdate()1414 void StelMainView::fpsTimerUpdate()
1415 {
1416 	if(!updateQueued)
1417 	{
1418 		updateQueued = true;
1419 		QTimer::singleShot(0, glWidget, SLOT(update()));
1420 	}
1421 }
1422 
1423 #ifdef OPENGL_DEBUG_LOGGING
logGLMessage(const QOpenGLDebugMessage & debugMessage)1424 void StelMainView::logGLMessage(const QOpenGLDebugMessage &debugMessage)
1425 {
1426 	qDebug()<<debugMessage;
1427 }
1428 
contextDestroyed()1429 void StelMainView::contextDestroyed()
1430 {
1431 	qDebug()<<"Main OpenGL context destroyed";
1432 }
1433 #endif
1434 
thereWasAnEvent()1435 void StelMainView::thereWasAnEvent()
1436 {
1437 	lastEventTimeSec = StelApp::getTotalRunTime();
1438 }
1439 
needsMaxFPS() const1440 bool StelMainView::needsMaxFPS() const
1441 {
1442 	const double now = StelApp::getTotalRunTime();
1443 
1444 	// Determines when the next display will need to be triggered
1445 	// The current policy is that after an event, the FPS is maximum for 2.5 seconds
1446 	// after that, it switches back to the default minfps value to save power.
1447 	// The fps is also kept to max if the timerate is higher than normal speed.
1448 	const double timeRate = stelApp->getCore()->getTimeRate();
1449 	return (now - lastEventTimeSec < 2.5) || fabs(timeRate) > StelCore::JD_SECOND;
1450 }
1451 
moveEvent(QMoveEvent * event)1452 void StelMainView::moveEvent(QMoveEvent * event)
1453 {
1454 	Q_UNUSED(event);
1455 
1456 	// We use the glWidget instead of the event, as we want the screen that shows most of the widget.
1457 	QWindow* win = glWidget->windowHandle();
1458 	if(win)
1459 		stelApp->setDevicePixelsPerPixel(win->devicePixelRatio());
1460 }
1461 
closeEvent(QCloseEvent * event)1462 void StelMainView::closeEvent(QCloseEvent* event)
1463 {
1464 	Q_UNUSED(event);
1465 	stelApp->quit();
1466 }
1467 
1468 //! Delete openGL textures (to call before the GLContext disappears)
deinitGL()1469 void StelMainView::deinitGL()
1470 {
1471 	//fix for bug 1628072 caused by QTBUG-56798
1472 #ifndef QT_NO_DEBUG
1473 	StelOpenGL::clearGLErrors();
1474 #endif
1475 
1476 	stelApp->deinit();
1477 	delete gui;
1478 	gui = Q_NULLPTR;
1479 }
1480 
setScreenshotFormat(const QString filetype)1481 void StelMainView::setScreenshotFormat(const QString filetype)
1482 {
1483 	const QString candidate=filetype.toLower();
1484 	const QByteArray candBA=candidate.toUtf8();
1485 
1486 	// Make sure format is supported by Qt, but restrict some useless formats.
1487 	QList<QByteArray> formats = QImageWriter::supportedImageFormats();
1488 	formats.removeOne("icns");
1489 	formats.removeOne("wbmp");
1490 	formats.removeOne("cur");
1491 	if (formats.contains(candBA))
1492 	{
1493 		screenShotFormat=candidate;
1494 		// apply setting immediately
1495 		configuration->setValue("main/screenshot_format", candidate);
1496 		emit screenshotFormatChanged(candidate);
1497 	}
1498 	else
1499 	{
1500 		qDebug() << "Invalid filetype for screenshot: " << filetype;
1501 	}
1502 }
saveScreenShot(const QString & filePrefix,const QString & saveDir,const bool overwrite)1503 void StelMainView::saveScreenShot(const QString& filePrefix, const QString& saveDir, const bool overwrite)
1504 {
1505 	screenShotPrefix = filePrefix;
1506 	screenShotDir = saveDir;
1507 	flagOverwriteScreenshots=overwrite;
1508 	emit(screenshotRequested());
1509 }
1510 
doScreenshot(void)1511 void StelMainView::doScreenshot(void)
1512 {
1513 	QFileInfo shotDir;
1514 	// Make a screenshot which may be larger than the current window. This is harder than you would think:
1515 	// fbObj the framebuffer governs size of the target image, that's the easy part, but it also has its limits.
1516 	// However, the GUI parts need to be placed properly,
1517 	// HiDPI screens interfere, and the viewing angle has to be maintained.
1518 	// First, image size:
1519 	glWidget->makeCurrent();
1520 	float pixelRatio = static_cast<float>(QOpenGLContext::currentContext()->screen()->devicePixelRatio());
1521 	int imgWidth =static_cast<int>(stelScene->width());
1522 	int imgHeight=static_cast<int>(stelScene->height());
1523 	bool nightModeWasEnabled=nightModeEffect->isEnabled();
1524 	nightModeEffect->setEnabled(false);
1525 	if (flagUseCustomScreenshotSize)
1526 	{
1527 		// Borrowed from Scenery3d renderer: determine maximum framebuffer size as minimum of texture, viewport and renderbuffer size
1528 		QOpenGLContext *context = QOpenGLContext::currentContext();
1529 		if (context)
1530 		{
1531 			context->functions()->initializeOpenGLFunctions();
1532 			//qDebug() << "initializeOpenGLFunctions()...";
1533 			// TODO: Investigate this further when GL memory issues should appear.
1534 			// Make sure we have enough free GPU memory!
1535 #ifndef NDEBUG
1536 #ifdef GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX
1537 			GLint freeGLmemory;
1538 			context->functions()->glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &freeGLmemory);
1539 			qCDebug(mainview)<<"Free GPU memory:" << freeGLmemory << "kB -- we ask for " << customScreenshotWidth*customScreenshotHeight*8 / 1024 <<"kB";
1540 #endif
1541 #ifdef GL_RENDERBUFFER_FREE_MEMORY_ATI
1542 			GLint freeGLmemoryAMD[4];
1543 			context->functions()->glGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, freeGLmemoryAMD);
1544 			qCDebug(mainview)<<"Free GPU memory (AMD version):" << static_cast<uint>(freeGLmemoryAMD[1])/1024 << "+"
1545 					  << static_cast<uint>(freeGLmemoryAMD[3])/1024 << " of "
1546 					  << static_cast<uint>(freeGLmemoryAMD[0])/1024 << "+"
1547 					  << static_cast<uint>(freeGLmemoryAMD[2])/1024 << "kB -- we ask for "
1548 					  << customScreenshotWidth*customScreenshotHeight*8 / 1024 <<"kB";
1549 #endif
1550 #endif
1551 			GLint texSize,viewportSize[2],rbSize;
1552 			context->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
1553 			context->functions()->glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewportSize);
1554 			context->functions()->glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &rbSize);
1555 			qCDebug(mainview)<<"Maximum texture size:"<<texSize;
1556 			qCDebug(mainview)<<"Maximum viewport dims:"<<viewportSize[0]<<viewportSize[1];
1557 			qCDebug(mainview)<<"Maximum renderbuffer size:"<<rbSize;
1558 			int maximumFramebufferSize = qMin(texSize,qMin(rbSize,qMin(viewportSize[0],viewportSize[1])));
1559 			qCDebug(mainview)<<"Maximum framebuffer size:"<<maximumFramebufferSize;
1560 
1561 			imgWidth =qMin(maximumFramebufferSize, customScreenshotWidth);
1562 			imgHeight=qMin(maximumFramebufferSize, customScreenshotHeight);
1563 		}
1564 		else
1565 		{
1566 			qCWarning(mainview) << "No GL context for screenshot! Aborting.";
1567 			return;
1568 		}
1569 	}
1570 	// The texture format depends on used GL version. RGB is fine on OpenGL. on GLES, we must use RGBA and circumvent problems with a few more steps.
1571 	bool isGLES=(QOpenGLContext::currentContext()->format().renderableType() == QSurfaceFormat::OpenGLES);
1572 
1573 	QOpenGLFramebufferObjectFormat fbFormat;
1574 	fbFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
1575 	fbFormat.setInternalTextureFormat(isGLES ? GL_RGBA : GL_RGB); // try to avoid transparent background!
1576 	if(const auto multisamplingLevel = configuration->value("video/multisampling", 0).toInt())
1577         fbFormat.setSamples(multisamplingLevel);
1578 	QOpenGLFramebufferObject * fbObj = new QOpenGLFramebufferObject(static_cast<int>(imgWidth * pixelRatio), static_cast<int>(imgHeight * pixelRatio), fbFormat);
1579 	fbObj->bind();
1580 	// Now the painter has to be convinced to paint to the potentially larger image frame.
1581 	QOpenGLPaintDevice fbObjPaintDev(static_cast<int>(imgWidth * pixelRatio), static_cast<int>(imgHeight * pixelRatio));
1582 
1583 	// It seems the projector has its own knowledge about image size. We must adjust fov and image size, but reset afterwards.
1584 	StelProjector::StelProjectorParams pParams=StelApp::getInstance().getCore()->getCurrentStelProjectorParams();
1585 	StelProjector::StelProjectorParams sParams=pParams;
1586 	//qCDebug(mainview) << "Screenshot Viewport: x" << pParams.viewportXywh[0] << "/y" << pParams.viewportXywh[1] << "/w" << pParams.viewportXywh[2] << "/h" << pParams.viewportXywh[3];
1587 	sParams.viewportXywh[2]=imgWidth;
1588 	sParams.viewportXywh[3]=imgHeight;
1589 
1590 	// Configure a helper value to allow some modules to tweak their output sizes. Currently used by StarMgr, maybe solve font issues?
1591 	customScreenshotMagnification=static_cast<float>(imgHeight)/qApp->screens().at(qApp->desktop()->screenNumber())->geometry().height();
1592 
1593 	sParams.viewportCenter.set(0.0+(0.5+pParams.viewportCenterOffset.v[0])*imgWidth, 0.0+(0.5+pParams.viewportCenterOffset.v[1])*imgHeight);
1594 	sParams.viewportFovDiameter = qMin(imgWidth,imgHeight);
1595 	StelApp::getInstance().getCore()->setCurrentStelProjectorParams(sParams);
1596 
1597 	QPainter painter;
1598 	painter.begin(&fbObjPaintDev);
1599 	// next line was above begin(), but caused a complaint. Maybe use after begin()?
1600 	painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
1601 	stelScene->setSceneRect(0, 0, imgWidth, imgHeight);
1602 
1603 	// push the button bars back to the sides where they belong, and fix root item clipping its children.
1604 	dynamic_cast<StelGui*>(gui)->getSkyGui()->setGeometry(0, 0, imgWidth, imgHeight);
1605 	rootItem->setSize(QSize(imgWidth, imgHeight));
1606 	dynamic_cast<StelGui*>(gui)->forceRefreshGui(); // refresh bar position.
1607 
1608 	stelScene->render(&painter, QRectF(), QRectF(0,0,imgWidth,imgHeight) , Qt::KeepAspectRatio);
1609 	painter.end();
1610 
1611 	QImage im;
1612 	if (isGLES)
1613 	{
1614 		// We have RGBA texture with possibly empty spots when atmosphere was off.
1615 		// See toImage() help entry why to create wrapper here.
1616 		QImage fboImage(fbObj->toImage());
1617 		//qDebug() << "FBOimage format:" << fboImage.format(); // returns Format_RGBA8888_Premultiplied
1618 		QImage im2(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_RGBX8888);
1619 		im=im2.copy();
1620 	}
1621 	else
1622 		im=fbObj->toImage();
1623 	fbObj->release();
1624 	delete fbObj;
1625 	// reset viewport and GUI
1626 	StelApp::getInstance().getCore()->setCurrentStelProjectorParams(pParams);
1627 	customScreenshotMagnification=1.0f;
1628 	nightModeEffect->setEnabled(nightModeWasEnabled);
1629 	stelScene->setSceneRect(0, 0, pParams.viewportXywh[2], pParams.viewportXywh[3]);
1630 	rootItem->setSize(QSize(pParams.viewportXywh[2], pParams.viewportXywh[3]));
1631 	StelGui* stelGui = dynamic_cast<StelGui*>(gui);
1632 	if (stelGui)
1633 	{
1634 		stelGui->getSkyGui()->setGeometry(0, 0, pParams.viewportXywh[2], pParams.viewportXywh[3]);
1635 		stelGui->forceRefreshGui();
1636 	}
1637 
1638 	if (nightModeWasEnabled)
1639 	{
1640 		for (int row=0; row<im.height(); ++row)
1641 			for (int col=0; col<im.width(); ++col)
1642 			{
1643 				QRgb rgb=im.pixel(col, row);
1644 				int gray=qGray(rgb);
1645 				im.setPixel(col, row, qRgb(gray, 0, 0));
1646 			}
1647 	}
1648 	if (flagInvertScreenShotColors)
1649 		im.invertPixels();
1650 
1651 	if (StelFileMgr::getScreenshotDir().isEmpty())
1652 	{
1653 		qWarning() << "Oops, the directory for screenshots is not set! Let's try create and set it...";
1654 		// Create a directory for screenshots if main/screenshot_dir option is unset and user do screenshot at the moment!
1655 		QString screenshotDirSuffix = "/Stellarium";
1656 		QString screenshotDir;
1657 		if (!QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).isEmpty())
1658 			screenshotDir = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation)[0].append(screenshotDirSuffix);
1659 		else
1660 			screenshotDir = StelFileMgr::getUserDir().append(screenshotDirSuffix);
1661 
1662 		try
1663 		{
1664 			StelFileMgr::setScreenshotDir(screenshotDir);
1665 			StelApp::getInstance().getSettings()->setValue("main/screenshot_dir", screenshotDir);
1666 		}
1667 		catch (std::runtime_error &e)
1668 		{
1669 			qDebug("Error: cannot create screenshot directory: %s", e.what());
1670 		}
1671 	}
1672 
1673 	if (screenShotDir == "")
1674 		shotDir = QFileInfo(StelFileMgr::getScreenshotDir());
1675 	else
1676 		shotDir = QFileInfo(screenShotDir);
1677 
1678 	if (!shotDir.isDir())
1679 	{
1680 		qWarning() << "ERROR requested screenshot directory is not a directory: " << QDir::toNativeSeparators(shotDir.filePath());
1681 		return;
1682 	}
1683 	else if (!shotDir.isWritable())
1684 	{
1685 		qWarning() << "ERROR requested screenshot directory is not writable: " << QDir::toNativeSeparators(shotDir.filePath());
1686 		return;
1687 	}
1688 
1689 	QFileInfo shotPath;
1690 	if (flagOverwriteScreenshots)
1691 	{
1692 		shotPath = QFileInfo(shotDir.filePath() + "/" + screenShotPrefix + "." + screenShotFormat);
1693 	}
1694 	else
1695 	{
1696 		for (int j=0; j<100000; ++j)
1697 		{
1698 			shotPath = QFileInfo(shotDir.filePath() + "/" + screenShotPrefix + QString("%1").arg(j, 3, 10, QLatin1Char('0')) + "." + screenShotFormat);
1699 			if (!shotPath.exists())
1700 				break;
1701 		}
1702 	}
1703 	qDebug() << "INFO Saving screenshot in file: " << QDir::toNativeSeparators(shotPath.filePath());
1704 	QImageWriter imageWriter(shotPath.filePath());
1705 	if (screenShotFormat=="tif")
1706 		imageWriter.setCompression(1); // use LZW
1707 	if (screenShotFormat=="jpg")
1708 	{
1709 		imageWriter.setQuality(75); // This is actually default
1710 	}
1711 	if (screenShotFormat=="jpeg")
1712 	{
1713 		imageWriter.setQuality(100);
1714 	}
1715 	if (!imageWriter.write(im))
1716 	{
1717 		qWarning() << "WARNING failed to write screenshot to: " << QDir::toNativeSeparators(shotPath.filePath());
1718 	}
1719 }
1720 
getMousePos() const1721 QPoint StelMainView::getMousePos() const
1722 {
1723 	return glWidget->mapFromGlobal(QCursor::pos());
1724 }
1725 
glContext() const1726 QOpenGLContext* StelMainView::glContext() const
1727 {
1728 	return glWidget->context();
1729 }
1730 
glContextMakeCurrent()1731 void StelMainView::glContextMakeCurrent()
1732 {
1733 	glWidget->makeCurrent();
1734 }
1735 
glContextDoneCurrent()1736 void StelMainView::glContextDoneCurrent()
1737 {
1738 	glWidget->doneCurrent();
1739 }
1740 
1741 // Set the sky background color. Everything else than black creates a work of art!
setSkyBackgroundColor(Vec3f color)1742 void StelMainView::setSkyBackgroundColor(Vec3f color)
1743 {
1744 	rootItem->setSkyBackgroundColor(color);
1745 	StelApp::getInstance().getSettings()->setValue("color/sky_background_color", color.toStr());
1746 	emit skyBackgroundColorChanged(color);
1747 }
1748 
1749 // Get the sky background color. Everything else than black creates a work of art!
getSkyBackgroundColor() const1750 Vec3f StelMainView::getSkyBackgroundColor() const
1751 {
1752 	return rootItem->getSkyBackgroundColor();
1753 }
1754