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