1 /*
2     SPDX-FileCopyrightText: Thomas Kabelmann
3     SPDX-FileCopyrightText: 2018 Robert Lancaster <rlancaste@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "xplanetimageviewer.h"
9 #include "Options.h"
10 #include "dialogs/timedialog.h"
11 #include "ksnotification.h"
12 
13 #include <QtConcurrent>
14 
15 #ifndef KSTARS_LITE
16 #include "kstars.h"
17 #endif
18 
19 #ifndef KSTARS_LITE
20 #include <KMessageBox>
21 #endif
22 
23 #include <QFileDialog>
24 #include <QPainter>
25 #include <QResizeEvent>
26 #include <QStatusBar>
27 #include <QTemporaryFile>
28 #include <QVBoxLayout>
29 #include <QPushButton>
30 #include <QApplication>
31 #include <QScreen>
32 #include <QSlider>
33 #include "skymap.h"
34 #include "kspaths.h"
35 #include "fov.h"
36 
37 #include <QUuid>
38 #include <sys/stat.h>
39 #include <QInputDialog>
40 
41 typedef enum
42 {
43     SUN, MERCURY, VENUS,
44     EARTH, MOON,
45     MARS, PHOBOS, DEIMOS,
46     JUPITER, GANYMEDE, IO, CALLISTO, EUROPA,
47     SATURN, TITAN, MIMAS, ENCELADUS, TETHYS, DIONE, RHEA, HYPERION, IAPETUS, PHOEBE,
48     URANUS, UMBRIEL, ARIEL, MIRANDA, TITANIA, OBERON,
49     NEPTUNE, TRITON
50 } objects;
51 
XPlanetImageLabel(QWidget * parent)52 XPlanetImageLabel::XPlanetImageLabel(QWidget *parent) : QFrame(parent)
53 {
54 #ifndef KSTARS_LITE
55     grabGesture(Qt::PinchGesture);
56     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
57     setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
58     setLineWidth(2);
59 #endif
60 }
61 
setImage(const QImage & img)62 void XPlanetImageLabel::setImage(const QImage &img)
63 {
64 #ifndef KSTARS_LITE
65     m_Image = img;
66     m_Pix     = QPixmap::fromImage(m_Image);
67 #endif
68 }
69 
invertPixels()70 void XPlanetImageLabel::invertPixels()
71 {
72 #ifndef KSTARS_LITE
73     m_Image.invertPixels();
74     m_Pix = QPixmap::fromImage(m_Image.scaled(width(), height(), Qt::KeepAspectRatio));
75 #endif
76 }
77 
paintEvent(QPaintEvent *)78 void XPlanetImageLabel::paintEvent(QPaintEvent *)
79 {
80 #ifndef KSTARS_LITE
81     QPainter p;
82     p.begin(this);
83     int x = 0;
84     if (m_Pix.width() < width())
85         x = (width() - m_Pix.width()) / 2;
86     p.drawPixmap(x, 0, m_Pix);
87     p.end();
88 #endif
89 }
90 
resizeEvent(QResizeEvent * event)91 void XPlanetImageLabel::resizeEvent(QResizeEvent *event)
92 {
93     if (event->size() == m_Pix.size())
94         return;
95 
96     m_Pix = QPixmap::fromImage(m_Image.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
97 }
98 
refreshImage()99 void XPlanetImageLabel::refreshImage()
100 {
101     m_Pix = QPixmap::fromImage(m_Image.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
102     update();
103 }
104 
wheelEvent(QWheelEvent * e)105 void XPlanetImageLabel::wheelEvent(QWheelEvent *e)
106 {
107     //This attempts to send the wheel event back to the Scroll Area if it was taken from a trackpad
108     //It should still do the zoom if it is a mouse wheel
109     if (e->source() == Qt::MouseEventSynthesizedBySystem)
110     {
111         QFrame::wheelEvent(e);
112     }
113     else
114     {
115         if (e->delta() > 0)
116             emit zoomIn();
117         else if (e->delta() < 0)
118             emit zoomOut();
119         e->accept();
120     }
121 }
122 
event(QEvent * event)123 bool XPlanetImageLabel::event(QEvent *event)
124 {
125     if (event->type() == QEvent::Gesture)
126         return gestureEvent(dynamic_cast<QGestureEvent *>(event));
127     return QFrame::event(event);
128 }
129 
gestureEvent(QGestureEvent * event)130 bool XPlanetImageLabel::gestureEvent(QGestureEvent *event)
131 {
132     if (QGesture *pinch = event->gesture(Qt::PinchGesture))
133         pinchTriggered(dynamic_cast<QPinchGesture *>(pinch));
134     return true;
135 }
136 
137 
pinchTriggered(QPinchGesture * gesture)138 void XPlanetImageLabel::pinchTriggered(QPinchGesture *gesture)
139 {
140     if (gesture->totalScaleFactor() > 1)
141         emit zoomIn();
142     else
143         emit zoomOut();
144 }
145 
146 
mousePressEvent(QMouseEvent * e)147 void XPlanetImageLabel::mousePressEvent(QMouseEvent *e)
148 {
149     m_MouseButtonDown = true;
150     m_LastMousePoint = e->globalPos();
151     e->accept();
152 }
153 
mouseReleaseEvent(QMouseEvent * e)154 void XPlanetImageLabel::mouseReleaseEvent(QMouseEvent *e)
155 {
156     m_MouseButtonDown = false;
157     e->accept();
158 }
159 
mouseMoveEvent(QMouseEvent * e)160 void XPlanetImageLabel::mouseMoveEvent(QMouseEvent *e)
161 {
162     if(m_MouseButtonDown)
163     {
164         QPoint newPoint = e->globalPos();
165         int dx = newPoint.x() - m_LastMousePoint.x();
166         int dy = newPoint.y() - m_LastMousePoint.y();
167         if(e->buttons() & Qt::RightButton)
168             emit changeLocation(QPoint(dx, dy));
169         if(e->buttons() & Qt::LeftButton)
170             emit changePosition(QPoint(dx, dy));
171         m_LastMousePoint = newPoint;
172     }
173     e->accept();
174 }
175 
XPlanetImageViewer(const QString & obj,QWidget * parent)176 XPlanetImageViewer::XPlanetImageViewer(const QString &obj, QWidget *parent): QDialog(parent)
177 {
178 #ifndef KSTARS_LITE
179     m_LastFile = QDir::homePath();
180 
181 #ifdef Q_OS_OSX
182     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
183 #endif
184     setAttribute(Qt::WA_DeleteOnClose, true);
185     setModal(false);
186     setWindowTitle(i18nc("@title:window", "XPlanet Solar System Simulator: %1", obj));
187 
188     setXPlanetDate(KStarsData::Instance()->ut());
189 
190     // Create widget
191     QFrame *page = new QFrame(this);
192 
193     //setMainWidget( page );
194     QVBoxLayout *mainLayout = new QVBoxLayout(this);
195     mainLayout->addWidget(page);
196     setLayout(mainLayout);
197 
198     QWidget *selectorsWidget = new QWidget(this);
199     QHBoxLayout *selectorsLayout = new QHBoxLayout(selectorsWidget);
200     selectorsLayout->setContentsMargins(0, 0, 0, 0);
201     mainLayout->addWidget(selectorsWidget);
202 
203     m_ObjectNames       << i18n("Sun")      << i18n("Mercury")  << i18n("Venus");
204     m_objectDefaultFOVs << 0.74818          << 0.004          << 0.02;
205     m_ObjectNames       << i18n("Earth")    << i18n("Moon");
206     m_objectDefaultFOVs << 1.0              << 0.74818;
207     m_ObjectNames       << i18n("Mars")     << i18n("Phobos")   << i18n("Deimos");
208     m_objectDefaultFOVs << 0.00865          << 0.00002          << 0.00002;
209     m_ObjectNames       << i18n("Jupiter")  << i18n("Ganymede") << i18n("Io")       << i18n("Callisto") << i18n("Europa");
210     m_objectDefaultFOVs << 0.02             << 0.0005           << 0.0004           << 0.0005           << 0.0003;
211     m_ObjectNames       << i18n("Saturn")   << i18n("Titan")    << i18n("Mimas")    << i18n("Enceladus") << i18n("Tethys")   << i18n("Dione")    << i18n("Rhea")     << i18n("Hyperion") << i18n("Iapetus")  << i18n("Phoebe");
212     m_objectDefaultFOVs << 0.02             << 0.0003            << 0.00002            << 0.00003            << 0.00007            << 0.00007            << 0.0001            << 0.00002            << 0.0001            << 0.00002;
213     m_ObjectNames       << i18n("Uranus")   << i18n("Umbriel")  << i18n("Ariel")    << i18n("Miranda")  << i18n("Titania")  << i18n("Oberon");
214     m_objectDefaultFOVs << 0.00256          << 0.00004            << 0.00004            << 0.00002            << 0.00005            << 0.00005;
215     m_ObjectNames       << i18n("Neptune")  << i18n("Triton");
216     m_objectDefaultFOVs << 0.00114          << 0.0001;
217 
218     m_CurrentObjectIndex = m_ObjectNames.indexOf(obj);
219     if (m_CurrentObjectIndex < 0)
220         // Set to Saturn if current object is not in the list.
221         m_CurrentObjectIndex = 13;
222     m_ObjectName = m_ObjectNames.at(m_CurrentObjectIndex);
223 
224     QComboBox *objectSelector = new QComboBox(this);
225     objectSelector->addItems(m_ObjectNames);
226     objectSelector->setToolTip(i18n("This allows you to select a new object/target for XPlanet to view"));
227     selectorsLayout->addWidget(objectSelector);
228     objectSelector->setCurrentIndex(m_CurrentObjectIndex);
229     connect(objectSelector,  SIGNAL(currentIndexChanged(int)), this, SLOT(updateXPlanetObject(int)));
230 
231     m_CurrentOriginIndex = EARTH;
232     m_OriginName = m_ObjectNames.at(EARTH);
233 
234     selectorsLayout->addWidget(new QLabel(i18n("from"), this));
235     m_OriginSelector = new QComboBox(this);
236     m_OriginSelector->addItems(m_ObjectNames);
237     m_OriginSelector->setToolTip(i18n("This allows you to select a viewing location"));
238     selectorsLayout->addWidget(m_OriginSelector);
239     m_OriginSelector->setCurrentIndex(EARTH);
240     connect(m_OriginSelector,  SIGNAL(currentIndexChanged(int)), this, SLOT(updateXPlanetOrigin(int)));
241 
242     m_lat = Options::xplanetLatitude().toDouble();
243     m_lon = Options::xplanetLongitude().toDouble();
244     m_Radius = 45;
245 
246     selectorsLayout->addWidget(new QLabel(i18n("Location:"), this));
247 
248     m_PositionDisplay = new QLabel(this);
249     m_PositionDisplay->setToolTip(i18n("XPlanet Latitude, Longitude, and object radius in %. This is only valid when viewing the object from the same object"));
250     updatePositionDisplay();
251     m_PositionDisplay->setDisabled(true);
252     selectorsLayout->addWidget(m_PositionDisplay);
253 
254     QPushButton *resetXPlanetLocation = new QPushButton(this);
255     resetXPlanetLocation->setIcon(QIcon::fromTheme("system-reboot"));
256     resetXPlanetLocation->setAttribute(Qt::WA_LayoutUsesWidgetRect);
257     resetXPlanetLocation->setMaximumSize(QSize(32, 32));
258     resetXPlanetLocation->setMinimumSize(QSize(32, 32));
259     resetXPlanetLocation->setToolTip(i18n("Reset XPlanet Location to the location specified in the XPlanet Options"));
260     selectorsLayout->addWidget(resetXPlanetLocation);
261     connect(resetXPlanetLocation, SIGNAL(clicked()), this, SLOT(resetLocation()));
262 
263     m_FreeRotate = new QPushButton(this);
264     m_FreeRotate->setIcon(QIcon::fromTheme("object-rotate-left"));
265     m_FreeRotate->setAttribute(Qt::WA_LayoutUsesWidgetRect);
266     m_FreeRotate->setMaximumSize(QSize(32, 32));
267     m_FreeRotate->setMinimumSize(QSize(32, 32));
268     m_FreeRotate->setCheckable(true);
269     m_FreeRotate->setToolTip(i18n("Hover over target and freely rotate view with mouse in XPlanet Viewer"));
270     selectorsLayout->addWidget(m_FreeRotate);
271     connect(m_FreeRotate, SIGNAL(clicked()), this, SLOT(slotFreeRotate()));
272 
273     QPushButton *reCenterB = new QPushButton(this);
274     reCenterB->setIcon(QIcon::fromTheme("snap-bounding-box-center"));
275     reCenterB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
276     reCenterB->setMaximumSize(QSize(32, 32));
277     reCenterB->setMinimumSize(QSize(32, 32));
278     reCenterB->setToolTip(i18n("Recenters the XPlanet image once it has been moved"));
279     selectorsLayout->addWidget(reCenterB);
280     connect(reCenterB, SIGNAL(clicked()), this, SLOT(reCenterXPlanet()));
281 
282     QPushButton *saveB = new QPushButton(this);
283     saveB->setIcon(QIcon::fromTheme("document-save"));
284     saveB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
285     saveB->setMaximumSize(QSize(32, 32));
286     saveB->setMinimumSize(QSize(32, 32));
287     saveB->setToolTip(i18n("Save the image to disk"));
288     selectorsLayout->addWidget(saveB);
289     connect(saveB, SIGNAL(clicked()), this, SLOT(saveFileToDisk()));
290 
291     QWidget *viewControlsWidget = new QWidget(this);
292     QHBoxLayout *viewControlsLayout = new QHBoxLayout(viewControlsWidget);
293     viewControlsLayout->setContentsMargins(0, 0, 0, 0);
294     mainLayout->addWidget(viewControlsWidget);
295 
296     viewControlsLayout->addWidget(new QLabel(i18n("FOV:"), this));
297 
298     m_FOVEdit = new NonLinearDoubleSpinBox();
299     m_FOVEdit->setDecimals(5);
300     QList<double> possibleValues;
301     possibleValues << 0;
302     for(double i = .0001; i < 100; i *= 1.5)
303         possibleValues << i;
304     m_FOVEdit->setRecommendedValues(possibleValues);
305     m_FOVEdit->setToolTip(i18n("Sets the FOV to the Specified value.   Note: has no effect if hovering over object."));
306     viewControlsLayout->addWidget(m_FOVEdit);
307 
308     if (Options::xplanetFOV())
309         m_FOV = KStars::Instance()->map()->fov();
310     else
311         m_FOV = m_objectDefaultFOVs.at( m_CurrentObjectIndex);
312     m_FOVEdit->setValue(m_FOV);
313 
314     connect(m_FOVEdit,  SIGNAL(valueChanged(double)), this, SLOT(updateXPlanetFOVEdit()));
315 
316     m_KStarsFOV = new QPushButton(this);
317     m_KStarsFOV->setIcon(QIcon::fromTheme("zoom-fit-width"));
318     m_KStarsFOV->setAttribute(Qt::WA_LayoutUsesWidgetRect);
319     m_KStarsFOV->setMaximumSize(QSize(32, 32));
320     m_KStarsFOV->setMinimumSize(QSize(32, 32));
321     m_KStarsFOV->setToolTip(i18n("Zoom to the current KStars FOV.   Note: has no effect if hovering over object."));
322     viewControlsLayout->addWidget(m_KStarsFOV);
323     connect(m_KStarsFOV, SIGNAL(clicked()), this, SLOT(setKStarsXPlanetFOV()));
324 
325     m_setFOV = new QPushButton(this);
326     m_setFOV->setIcon(QIcon::fromTheme("view-list-details"));
327     m_setFOV->setAttribute(Qt::WA_LayoutUsesWidgetRect);
328     m_setFOV->setMaximumSize(QSize(32, 32));
329     m_setFOV->setMinimumSize(QSize(32, 32));
330     m_setFOV->setToolTip(i18n("Zoom to a specific FOV. This has no effect when hovering over an object"));
331     viewControlsLayout->addWidget(m_setFOV);
332     connect(m_setFOV, SIGNAL(clicked()), this, SLOT(setFOVfromList()));
333 
334     m_NoFOV = new QPushButton(this);
335     m_NoFOV->setIcon(QIcon::fromTheme("system-reboot"));
336     m_NoFOV->setAttribute(Qt::WA_LayoutUsesWidgetRect);
337     m_NoFOV->setMaximumSize(QSize(32, 32));
338     m_NoFOV->setMinimumSize(QSize(32, 32));
339     m_NoFOV->setToolTip(i18n("Optimum FOV for the target, FOV parameter not specified.  Note: has no effect if hovering over object."));
340     viewControlsLayout->addWidget(m_NoFOV);
341     connect(m_NoFOV, SIGNAL(clicked()), this, SLOT(resetXPlanetFOV()));
342 
343     m_Rotation = 0;
344 
345     viewControlsLayout->addWidget(new QLabel(i18n("Rotation:"), this));
346 
347     m_RotateEdit = new QSpinBox();
348 
349     m_RotateEdit->setRange(-180, 180);
350     m_RotateEdit->setValue(0);
351     m_RotateEdit->setSingleStep(10);
352     m_RotateEdit->setToolTip(i18n("Set the view rotation to the desired angle"));
353     viewControlsLayout->addWidget(m_RotateEdit);
354     connect(m_RotateEdit,  SIGNAL(valueChanged(int)), this, SLOT(updateXPlanetRotationEdit()));
355 
356     QPushButton *invertRotation = new QPushButton(this);
357     invertRotation->setIcon(QIcon::fromTheme("object-flip-vertical"));
358     invertRotation->setAttribute(Qt::WA_LayoutUsesWidgetRect);
359     invertRotation->setMaximumSize(QSize(32, 32));
360     invertRotation->setMinimumSize(QSize(32, 32));
361     invertRotation->setToolTip(i18n("Rotate the view 180 degrees"));
362     viewControlsLayout->addWidget(invertRotation);
363     connect(invertRotation, SIGNAL(clicked()), this, SLOT(invertXPlanetRotation()));
364 
365     QPushButton *resetRotation = new QPushButton(this);
366     resetRotation->setIcon(QIcon::fromTheme("system-reboot"));
367     resetRotation->setAttribute(Qt::WA_LayoutUsesWidgetRect);
368     resetRotation->setMaximumSize(QSize(32, 32));
369     resetRotation->setMinimumSize(QSize(32, 32));
370     resetRotation->setToolTip(i18n("Reset view rotation to 0"));
371     viewControlsLayout->addWidget(resetRotation);
372     connect(resetRotation, SIGNAL(clicked()), this, SLOT(resetXPlanetRotation()));
373 
374     QPushButton *optionsB = new QPushButton(this);
375     optionsB->setIcon(QIcon::fromTheme("configure"));
376     optionsB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
377     optionsB->setMaximumSize(QSize(32, 32));
378     optionsB->setMinimumSize(QSize(32, 32));
379     optionsB->setToolTip(i18n("Bring up XPlanet Options"));
380     viewControlsLayout->addWidget(optionsB);
381     connect(optionsB, SIGNAL(clicked()), KStars::Instance(), SLOT(slotViewOps()));
382 
383     QPushButton *invertB = new QPushButton(this);
384     invertB->setIcon(QIcon::fromTheme("edit-select-invert"));
385     invertB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
386     invertB->setMaximumSize(QSize(32, 32));
387     invertB->setMinimumSize(QSize(32, 32));
388     invertB->setToolTip(i18n("Reverse colors of the image. This is useful to enhance contrast at times. This affects "
389                              "only the display and not the saving."));
390     viewControlsLayout->addWidget(invertB);
391     connect(invertB, SIGNAL(clicked()), this, SLOT(invertColors()));
392 
393     QWidget *timeWidget = new QWidget(this);
394     QHBoxLayout *timeLayout = new QHBoxLayout(timeWidget);
395     mainLayout->addWidget(timeWidget);
396     timeLayout->setContentsMargins(0, 0, 0, 0);
397 
398     m_XPlanetTime = KStarsData::Instance()->lt();
399 
400     QPushButton *setTime = new QPushButton(this);
401     setTime->setIcon(QIcon::fromTheme("clock"));
402     setTime->setAttribute(Qt::WA_LayoutUsesWidgetRect);
403     setTime->setMaximumSize(QSize(32, 32));
404     setTime->setMinimumSize(QSize(32, 32));
405     setTime->setToolTip(i18n("Allows you to set the XPlanet time to a different date/time from KStars"));
406     timeLayout->addWidget(setTime);
407     connect(setTime, SIGNAL(clicked()), this, SLOT(setXPlanetTime()));
408 
409     QPushButton *kstarsTime = new QPushButton(this);
410     kstarsTime->setIcon(QIcon::fromTheme("system-reboot"));
411     kstarsTime->setAttribute(Qt::WA_LayoutUsesWidgetRect);
412     kstarsTime->setMaximumSize(QSize(32, 32));
413     kstarsTime->setMinimumSize(QSize(32, 32));
414     kstarsTime->setToolTip(i18n("Sets the XPlanet time to the current KStars time"));
415     timeLayout->addWidget(kstarsTime);
416     connect(kstarsTime, SIGNAL(clicked()), this, SLOT(setXPlanetTimetoKStarsTime()));
417 
418     m_XPlanetTimeDisplay = new QLabel(this);
419     m_XPlanetTimeDisplay->setToolTip(i18n("Current XPlanet Time"));
420     timeLayout->addWidget(m_XPlanetTimeDisplay);
421 
422     m_XPlanetTimeDisplay->setText(i18n("%1, %2", m_XPlanetTime.date().toString(), m_XPlanetTime.time().toString()));
423 
424     m_TimeSlider = new QSlider(Qt::Horizontal, this);
425     m_TimeSlider->setTracking(false);
426     connect(m_TimeSlider, SIGNAL(sliderMoved(int)), this, SLOT(timeSliderDisplay(int)));
427     timeLayout->addWidget(m_TimeSlider);
428     m_TimeSlider->setRange(-100, 100);
429     m_TimeSlider->setToolTip(i18n("This sets the time step from the current XPlanet time, good for viewing events"));
430     connect(m_TimeSlider,  SIGNAL(valueChanged(int)), this, SLOT(updateXPlanetTime(int)));
431 
432     m_TimeEdit = new QSpinBox(this);
433     m_TimeEdit->setRange(-10000, 10000);
434     m_TimeEdit->setMaximumWidth(50);
435     m_TimeEdit->setToolTip(i18n("This sets the time step from the current XPlanet time"));
436     timeLayout->addWidget(m_TimeEdit);
437     connect(m_TimeEdit,  SIGNAL(valueChanged(int)), this, SLOT(updateXPlanetTimeEdit()));
438 
439     m_CurrentTimeUnitIndex = MINS;
440     m_TimeUnitsSelect = new QComboBox(this);
441     timeLayout->addWidget(m_TimeUnitsSelect);
442     m_TimeUnitsSelect->addItem(i18n("years"));
443     m_TimeUnitsSelect->addItem(i18n("months"));
444     m_TimeUnitsSelect->addItem(i18n("days"));
445     m_TimeUnitsSelect->addItem(i18n("hours"));
446     m_TimeUnitsSelect->addItem(i18n("minutes"));
447     m_TimeUnitsSelect->addItem(i18n("seconds"));
448     m_TimeUnitsSelect->setCurrentIndex(MINS);
449     m_TimeUnitsSelect->setToolTip(i18n("Lets you change the units for the timestep in the animation"));
450     connect(m_TimeUnitsSelect,  SIGNAL(currentIndexChanged(int)), this, SLOT(updateXPlanetTimeUnits(int)));
451 
452     m_XPlanetTimer = new QTimer(this);
453     m_XPlanetTimer->setInterval(Options::xplanetAnimationDelay());
454     connect(m_XPlanetTimer, SIGNAL(timeout()), this, SLOT(incrementXPlanetTime()));
455 
456     m_RunTime = new QPushButton(this);
457     m_RunTime->setIcon(QIcon::fromTheme("media-playback-start"));
458     m_RunTime->setAttribute(Qt::WA_LayoutUsesWidgetRect);
459     m_RunTime->setCheckable(true);
460     m_RunTime->setMaximumSize(QSize(32, 32));
461     m_RunTime->setMinimumSize(QSize(32, 32));
462     m_RunTime->setToolTip(i18n("Lets you run the animation"));
463     timeLayout->addWidget(m_RunTime);
464     connect(m_RunTime, SIGNAL(clicked()), this, SLOT(toggleXPlanetRun()));
465 
466     QPushButton *resetTime = new QPushButton(this);
467     resetTime->setIcon(QIcon::fromTheme("system-reboot"));
468     resetTime->setAttribute(Qt::WA_LayoutUsesWidgetRect);
469     resetTime->setMaximumSize(QSize(32, 32));
470     resetTime->setMinimumSize(QSize(32, 32));
471     resetTime->setToolTip(i18n("Resets the animation to 0 timesteps from the current XPlanet Time"));
472     timeLayout->addWidget(resetTime);
473     connect(resetTime, SIGNAL(clicked()), this, SLOT(resetXPlanetTime()));
474 
475     m_View = new XPlanetImageLabel(page);
476     m_View->setAutoFillBackground(false);
477     m_Caption = new QLabel(page);
478     m_Caption->setAutoFillBackground(true);
479     m_Caption->setFrameShape(QFrame::StyledPanel);
480     m_Caption->setText(m_ObjectName);
481     // Add layout
482     QVBoxLayout *vlay = new QVBoxLayout(page);
483     vlay->setSpacing(0);
484     vlay->setContentsMargins(0, 0, 0, 0);
485     vlay->addWidget(m_View);
486     vlay->addWidget(m_Caption);
487 
488 
489     connect(m_View, SIGNAL(zoomIn()), this, SLOT(zoomInXPlanetFOV()));
490     connect(m_View, SIGNAL(zoomOut()), this, SLOT(zoomOutXPlanetFOV()));
491     connect(m_View, SIGNAL(changePosition(QPoint)), this, SLOT(changeXPlanetPosition(QPoint)));
492     connect(m_View, SIGNAL(changeLocation(QPoint)), this, SLOT(changeXPlanetLocation(QPoint)));
493 
494     //Reverse colors
495     QPalette p = palette();
496     p.setColor(QPalette::Window, palette().color(QPalette::WindowText));
497     p.setColor(QPalette::WindowText, palette().color(QPalette::Window));
498     m_Caption->setPalette(p);
499     m_View->setPalette(p);
500 
501 #ifndef Q_OS_WIN
502     if(Options::xplanetUseFIFO())
503     {
504         connect(&watcherTimeout, SIGNAL(timeout()), &fifoImageLoadWatcher, SLOT(cancel()));
505         connect(&fifoImageLoadWatcher, SIGNAL(finished()), this, SLOT(showImage()));
506     }
507 #endif
508 
509 
510 #ifdef Q_OS_OSX
511     QList<QPushButton *> qButtons = findChildren<QPushButton *>();
512     for (auto &button : qButtons)
513         button->setAutoDefault(false);
514 #endif
515     updateXPlanetTime(0);
516 #endif
517 }
518 
~XPlanetImageViewer()519 XPlanetImageViewer::~XPlanetImageViewer()
520 {
521     QApplication::restoreOverrideCursor();
522 }
523 
startXplanet()524 void XPlanetImageViewer::startXplanet()
525 {
526     if(m_XPlanetRunning)
527         return;
528 
529     //This means something failed in the file output
530     if(!setupOutputFile())
531         return;
532 
533     QString xPlanetLocation = Options::xplanetPath();
534 #ifdef Q_OS_OSX
535     if (Options::xplanetIsInternal())
536         xPlanetLocation   = QCoreApplication::applicationDirPath() + "/xplanet/bin/xplanet";
537 #endif
538 
539     // If Options::xplanetPath() is empty, return
540     if (xPlanetLocation.isEmpty())
541     {
542         KSNotification::error(i18n("Xplanet binary path is empty in config panel."));
543         return;
544     }
545 
546     // If Options::xplanetPath() does not exist, return
547     const QFileInfo xPlanetLocationInfo(xPlanetLocation);
548     if (!xPlanetLocationInfo.exists() || !xPlanetLocationInfo.isExecutable())
549     {
550         KSNotification::error(i18n("The configured Xplanet binary does not exist or is not executable."));
551         return;
552     }
553 
554     // Create xplanet process
555     QProcess *xplanetProc = new QProcess(this);
556 
557     // Add some options
558     QStringList args;
559 
560     //This specifies the object to be viewed
561     args << "-body" << m_ObjectName.toLower();
562     //This is the date and time requested
563     args << "-date" << m_Date;
564     //This is the glare from the sun
565     args << "-glare" << Options::xplanetGlare();
566     args << "-base_magnitude" << Options::xplanetMagnitude();
567     //This is the correction for light's travel time.
568     args << "-light_time";
569 
570     args << "-geometry" << QString::number(Options::xplanetWidth()) + 'x' + QString::number(Options::xplanetHeight());
571 
572     if(m_FOV != 0)
573         args << "-fov" << QString::number(m_FOV);
574     //Need to convert to locale for places that don't use decimals??
575     //args << "-fov" << fov.setNum(fov());//.replace('.', ',');
576 
577     //This rotates the view for different object angles
578     args << "-rotate" << QString::number(m_Rotation);
579 
580     if (Options::xplanetConfigFile())
581         args << "-config" << Options::xplanetConfigFilePath();
582     if (Options::xplanetStarmap())
583         args << "-starmap" << Options::xplanetStarmapPath();
584     if (Options::xplanetArcFile())
585         args << "-arc_file" << Options::xplanetArcFilePath();
586     if (!m_File.fileName().isEmpty())
587         args << "-output" << m_File.fileName() << "-quality" << Options::xplanetQuality();
588 
589     // Labels
590     if (Options::xplanetLabel())
591     {
592         args << "-fontsize" << Options::xplanetFontSize() << "-color"
593              << "0x" + Options::xplanetColor().mid(1) << "-date_format" << Options::xplanetDateFormat();
594 
595         if (Options::xplanetLabelGMT())
596             args << "-gmtlabel";
597         else
598             args << "-label";
599         if (!Options::xplanetLabelString().isEmpty())
600             args << "-label_string"
601                  << "\"" + Options::xplanetLabelString() + "\"";
602         if (Options::xplanetLabelTL())
603             args << "-labelpos"
604                  << "+15+15";
605         else if (Options::xplanetLabelTR())
606             args << "-labelpos"
607                  << "-15+15";
608         else if (Options::xplanetLabelBR())
609             args << "-labelpos"
610                  << "-15-15";
611         else if (Options::xplanetLabelBL())
612             args << "-labelpos"
613                  << "+15-15";
614     }
615 
616     // Markers
617     if (Options::xplanetMarkerFile())
618         args << "-marker_file" << Options::xplanetMarkerFilePath();
619     if (Options::xplanetMarkerBounds())
620         args << "-markerbounds" << Options::xplanetMarkerBoundsPath();
621 
622     // Position
623     // This sets the position from which the planet is viewed.
624     // Note that setting Latitude and Longitude means that position above the SAME object
625 
626     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
627     {
628         if (Options::xplanetRandom())
629             args << "-random";
630         else
631             args << "-latitude" << QString::number(m_lat) << "-longitude" << QString::number(m_lon) << "-radius" << QString::number(m_Radius);
632     }
633     else
634         args << "-origin" << m_OriginName;
635 
636     //Centering
637     //This allows you to recenter the xplanet view
638 
639     args << "-center" << "+" + QString::number(Options::xplanetWidth() / 2 + center.x()) + "+" + QString::number(Options::xplanetHeight() / 2 + center.y());
640 
641     // Projection
642     if (Options::xplanetProjection())
643     {
644         switch (Options::xplanetProjection())
645         {
646             case 1:
647                 args << "-projection"
648                      << "ancient";
649                 break;
650             case 2:
651                 args << "-projection"
652                      << "azimuthal";
653                 break;
654             case 3:
655                 args << "-projection"
656                      << "bonne";
657                 break;
658             case 4:
659                 args << "-projection"
660                      << "gnomonic";
661                 break;
662             case 5:
663                 args << "-projection"
664                      << "hemisphere";
665                 break;
666             case 6:
667                 args << "-projection"
668                      << "lambert";
669                 break;
670             case 7:
671                 args << "-projection"
672                      << "mercator";
673                 break;
674             case 8:
675                 args << "-projection"
676                      << "mollweide";
677                 break;
678             case 9:
679                 args << "-projection"
680                      << "orthographic";
681                 break;
682             case 10:
683                 args << "-projection"
684                      << "peters";
685                 break;
686             case 11:
687                 args << "-projection"
688                      << "polyconic";
689                 break;
690             case 12:
691                 args << "-projection"
692                      << "rectangular";
693                 break;
694             case 13:
695                 args << "-projection"
696                      << "tsc";
697                 break;
698             default:
699                 break;
700         }
701         if (Options::xplanetBackground())
702         {
703             if (Options::xplanetBackgroundImage())
704                 args << "-background" << Options::xplanetBackgroundImagePath();
705             else
706                 args << "-background"
707                      << "0x" + Options::xplanetBackgroundColorValue().mid(1);
708         }
709     }
710 
711 #ifdef Q_OS_OSX
712     if (Options::xplanetIsInternal())
713     {
714         QString searchDir = QCoreApplication::applicationDirPath() + "/xplanet/share/xplanet/";
715         args << "-searchdir" << searchDir;
716     }
717 #endif
718 
719 #ifdef Q_OS_WIN
720     QString searchDir = xPlanetLocationInfo.dir().absolutePath() + QDir::separator() + "xplanet";
721     args << "-searchdir" << searchDir;
722 #endif
723 
724     //This prevents it from running forever.
725     args << "-num_times" << "1";
726 
727     m_XPlanetRunning = true;
728     m_ImageLoadSucceeded = false; //This will be set to true if it works.
729     uint32_t timeout = Options::xplanetTimeout();
730 
731     //FIFO files don't work on windows
732 #ifndef Q_OS_WIN
733     if(Options::xplanetUseFIFO())
734     {
735         fifoImageLoadWatcher.setFuture(QtConcurrent::run(this, &XPlanetImageViewer::loadImage));
736         watcherTimeout.setSingleShot(true);
737         watcherTimeout.start(timeout);
738     }
739 #endif
740 
741     xplanetProc->start(xPlanetLocation, args);
742 
743     //Uncomment to print the XPlanet commands to the console
744     // qDebug() << "Run:" << xplanetProc->program() << args.join(" ");
745 
746     bool XPlanetSucceeded = xplanetProc->waitForFinished(timeout);
747     m_XPlanetRunning = false;
748     xplanetProc->kill(); //In case it timed out
749     xplanetProc->deleteLater();
750     if(XPlanetSucceeded)
751     {
752         if(m_FOV == 0)
753             m_Caption->setText(i18n("XPlanet View: %1 from %2 on %3", m_ObjectName, m_OriginName, m_DateText));
754         else
755             m_Caption->setText(i18n("XPlanet View: %1 from %2 on %3 at FOV: %4 deg", m_ObjectName, m_OriginName, m_DateText, m_FOV));
756 #ifdef Q_OS_WIN
757         loadImage(); //This will also set imageLoadSucceeded based on whether it worked.
758 #else
759         if(Options::xplanetUseFIFO())
760             return;  //The loading of the image is handled with the watcher
761         else
762             loadImage(); //This will also set imageLoadSucceeded based on whether it worked.
763 #endif
764 
765         if(m_ImageLoadSucceeded)
766             showImage();
767         else
768         {
769             KSNotification::error(i18n("Loading of the image of object %1 failed.", m_ObjectName));
770         }
771     }
772     else
773     {
774         KStars::Instance()->statusBar()->showMessage(i18n("XPlanet failed to generate the image for object %1 before the timeout expired.", m_ObjectName));
775 #ifndef Q_OS_WIN
776         if(Options::xplanetUseFIFO())
777             fifoImageLoadWatcher.cancel();
778 #endif
779     }
780 }
781 
setupOutputFile()782 bool XPlanetImageViewer::setupOutputFile()
783 {
784 #ifndef Q_OS_WIN
785     if(Options::xplanetUseFIFO())
786     {
787         if(m_File.fileName().contains("xplanetfifo") && m_File.exists())
788             return true;
789         QDir kstarsTempDir(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/" + qAppName());
790         kstarsTempDir.mkpath(".");
791         m_File.setFileName(kstarsTempDir.filePath(QString("xplanetfifo%1.png").arg(QUuid::createUuid().toString().mid(1, 8)).toLatin1()));
792         int mkFifoSuccess = 0; //Note if the return value of the command is 0 it succeeded, -1 means it failed.
793         if ((mkFifoSuccess = mkfifo(m_File.fileName().toLatin1(), S_IRUSR | S_IWUSR) < 0))
794         {
795             KSNotification::error(i18n("Error making FIFO file %1: %2.", m_File.fileName(), strerror(errno)));
796             return false;
797         }
798         return true;
799     }
800 #endif
801 
802     //If the user is using windows or has not selected to use FIFO, it uses files in the KStars data directory.
803     QDir xPlanetDirPath(KSPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + "xplanet");
804     xPlanetDirPath.mkpath(".");
805     m_File.setFileName(xPlanetDirPath.filePath(m_ObjectName + ".png"));
806     return true;
807 }
808 
zoomInXPlanetFOV()809 void XPlanetImageViewer::zoomInXPlanetFOV()
810 {
811     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
812     {
813         m_Radius += 5;
814         updatePositionDisplay();
815         startXplanet();
816     }
817     else
818     {
819         m_FOVEdit->stepDown();
820         startXplanet();
821     }
822 
823 }
824 
zoomOutXPlanetFOV()825 void XPlanetImageViewer::zoomOutXPlanetFOV()
826 {
827     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
828     {
829         if(m_Radius > 0)
830         {
831             m_Radius -= 5;
832             updatePositionDisplay();
833             startXplanet();
834         }
835     }
836     else
837     {
838         m_FOVEdit->stepUp();
839         startXplanet();
840     }
841 
842 }
843 
updatePositionDisplay()844 void XPlanetImageViewer::updatePositionDisplay()
845 {
846     m_PositionDisplay->setText(i18n("%1, %2, %3", QString::number(m_lat), QString::number(m_lon), QString::number(m_Radius)));
847 }
848 
updateXPlanetTime(int timeShift)849 void XPlanetImageViewer::updateXPlanetTime(int timeShift)
850 {
851 
852     KStarsDateTime shiftedXPlanetTime;
853     switch(m_CurrentTimeUnitIndex)
854     {
855         case YEARS:
856             shiftedXPlanetTime = m_XPlanetTime.addDays(timeShift * 365);
857             break;
858 
859         case MONTHS:
860             shiftedXPlanetTime = m_XPlanetTime.addDays(timeShift * 30);
861             break;
862 
863         case DAYS:
864             shiftedXPlanetTime = m_XPlanetTime.addDays(timeShift);
865             break;
866 
867         case HOURS:
868             shiftedXPlanetTime = m_XPlanetTime.addSecs(timeShift * 3600);
869             break;
870 
871         case MINS:
872             shiftedXPlanetTime = m_XPlanetTime.addSecs(timeShift * 60);
873             break;
874 
875         case SECS:
876             shiftedXPlanetTime = m_XPlanetTime.addSecs(timeShift);
877             break;
878     }
879 
880     setXPlanetDate(shiftedXPlanetTime);
881     m_DateText = i18n("%1, %2", shiftedXPlanetTime.date().toString(), shiftedXPlanetTime.time().toString());
882     if(m_TimeEdit->value() != timeShift)
883         m_TimeEdit->setValue(timeShift);
884     else
885         startXplanet();
886 }
887 
updateXPlanetObject(int objectIndex)888 void XPlanetImageViewer::updateXPlanetObject(int objectIndex)
889 {
890     center = QPoint(0, 0);
891     m_CurrentObjectIndex = objectIndex;
892     m_ObjectName = m_ObjectNames.at(objectIndex);
893 
894     setWindowTitle(i18nc("@title:window", "XPlanet Solar System Simulator: %1", m_ObjectName));
895     if(m_FreeRotate->isChecked())
896         m_OriginSelector->setCurrentIndex(m_CurrentObjectIndex);
897     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
898         startXplanet();
899     else
900         resetXPlanetFOV();
901 }
902 
updateXPlanetOrigin(int originIndex)903 void XPlanetImageViewer::updateXPlanetOrigin(int originIndex)
904 {
905     center = QPoint(0, 0);
906     m_CurrentOriginIndex = originIndex;
907     m_OriginName = m_ObjectNames.at(originIndex);
908     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
909         m_FreeRotate->setChecked(true);
910     else
911         m_FreeRotate->setChecked(false);
912     updateStates();//This will update the disabled/enabled states
913     startXplanet();
914 }
915 
changeXPlanetLocation(QPoint delta)916 void XPlanetImageViewer::changeXPlanetLocation(QPoint delta)
917 {
918     if(m_CurrentObjectIndex == m_CurrentOriginIndex)
919     {
920         double newLon = m_lon + delta.x();
921         double newLat = m_lat + delta.y();
922 
923         newLat = qBound(-90.0, newLat, 90.0);
924 
925         m_lon = newLon;
926         m_lat = newLat;
927 
928         updatePositionDisplay();
929         startXplanet();
930     }
931 }
932 
changeXPlanetPosition(QPoint delta)933 void XPlanetImageViewer::changeXPlanetPosition(QPoint delta)
934 {
935     center.setX(center.x() + delta.x());
936     center.setY(center.y() + delta.y());
937     startXplanet();
938 }
939 
reCenterXPlanet()940 void XPlanetImageViewer::reCenterXPlanet()
941 {
942     center = QPoint(0, 0);
943     startXplanet();
944 }
945 
resetLocation()946 void XPlanetImageViewer::resetLocation()
947 {
948     m_lat = Options::xplanetLatitude().toDouble();
949     m_lon = Options::xplanetLongitude().toDouble();
950     m_Radius = 45;
951     updatePositionDisplay();
952     startXplanet();
953 }
954 
slotFreeRotate()955 void XPlanetImageViewer::slotFreeRotate()
956 {
957     if(m_FreeRotate->isChecked())
958         m_OriginSelector->setCurrentIndex(m_CurrentObjectIndex);
959     else
960         m_OriginSelector->setCurrentIndex(EARTH);
961 }
962 
updateStates()963 void XPlanetImageViewer::updateStates()
964 {
965     if(m_FreeRotate->isChecked())
966     {
967         m_FOVEdit->setDisabled(true);
968         m_KStarsFOV->setDisabled(true);
969         m_NoFOV->setDisabled(true);
970 
971         m_PositionDisplay->setDisabled(false);
972     }
973     else
974     {
975         m_FOVEdit->setDisabled(false);
976         m_KStarsFOV->setDisabled(false);
977         m_NoFOV->setDisabled(false);
978 
979         m_PositionDisplay->setDisabled(true);
980     }
981 }
982 
setXPlanetDate(KStarsDateTime time)983 void XPlanetImageViewer::setXPlanetDate(KStarsDateTime time)
984 {
985     //Note Xplanet uses UT time for everything but we want the labels to all be LT
986     KStarsDateTime utTime = KStarsData::Instance()->geo()->LTtoUT(time);
987     m_Date = utTime.toString(Qt::ISODate)
988              .replace("-", QString(""))
989              .replace("T", ".")
990              .replace(":", QString(""))
991              .replace("Z", QString(""));
992 }
993 
updateXPlanetTimeUnits(int units)994 void XPlanetImageViewer::updateXPlanetTimeUnits(int units)
995 {
996     m_CurrentTimeUnitIndex = units;
997     updateXPlanetTimeEdit();
998 }
999 
updateXPlanetTimeEdit()1000 void XPlanetImageViewer::updateXPlanetTimeEdit()
1001 {
1002     if(m_TimeSlider->isSliderDown())
1003         return;
1004     int timeShift = m_TimeEdit->value();
1005     if(m_TimeSlider->value() != timeShift)
1006     {
1007 
1008         if(timeShift > m_TimeSlider->maximum() + 100)
1009             m_TimeSlider->setMaximum(timeShift);
1010         if(timeShift < m_TimeSlider->minimum() - 100)
1011             m_TimeSlider->setMinimum(timeShift);
1012         m_TimeSlider->setValue(timeShift);
1013     }
1014     else
1015         updateXPlanetTime(timeShift);
1016 }
1017 
timeSliderDisplay(int timeShift)1018 void XPlanetImageViewer::timeSliderDisplay(int timeShift)
1019 {
1020     m_TimeEdit->setValue(timeShift);
1021 }
1022 
incrementXPlanetTime()1023 void XPlanetImageViewer::incrementXPlanetTime()
1024 {
1025     if(!m_XPlanetRunning)
1026     {
1027         int timeShift = m_TimeEdit->value();
1028         if(m_TimeSlider->maximum() <= timeShift)
1029             m_TimeSlider->setMaximum(timeShift + 100);
1030         if(m_TimeEdit->maximum() <= timeShift)
1031             m_TimeEdit->setMaximum(timeShift + 100);
1032         m_TimeSlider->setValue(timeShift + 1);
1033     }
1034 }
1035 
setXPlanetTime()1036 void XPlanetImageViewer::setXPlanetTime()
1037 {
1038     QPointer<TimeDialog> timedialog = new TimeDialog(m_XPlanetTime, KStarsData::Instance()->geo(), this);
1039     if (timedialog->exec() == QDialog::Accepted)
1040     {
1041         m_XPlanetTime = timedialog->selectedDateTime();
1042         m_XPlanetTimeDisplay->setText(i18n("%1, %2", m_XPlanetTime.date().toString(), m_XPlanetTime.time().toString()));
1043         int timeShift = 0;
1044         m_TimeSlider->setRange(-100, 100);
1045         if(m_TimeSlider->value() != timeShift)
1046             m_TimeSlider->setValue(timeShift);
1047         else
1048             updateXPlanetTime(timeShift);
1049     }
1050 }
1051 
setXPlanetTimetoKStarsTime()1052 void XPlanetImageViewer::setXPlanetTimetoKStarsTime()
1053 {
1054     m_XPlanetTime = KStarsData::Instance()->lt();
1055     m_XPlanetTimeDisplay->setText(i18n("%1, %2", m_XPlanetTime.date().toString(), m_XPlanetTime.time().toString()));
1056     int timeShift = 0;
1057     m_TimeSlider->setRange(-100, 100);
1058     if(m_TimeSlider->value() != timeShift)
1059         m_TimeSlider->setValue(timeShift);
1060     else
1061         updateXPlanetTime(timeShift);
1062 }
1063 
resetXPlanetTime()1064 void XPlanetImageViewer::resetXPlanetTime()
1065 {
1066     int timeShift = 0;
1067     m_TimeSlider->setRange(-100, 100);
1068     if(m_TimeSlider->value() != timeShift)
1069         m_TimeSlider->setValue(timeShift);
1070     else
1071         updateXPlanetTime(timeShift);
1072 }
1073 
toggleXPlanetRun()1074 void XPlanetImageViewer::toggleXPlanetRun()
1075 {
1076     if(m_XPlanetTimer->isActive())
1077     {
1078         m_XPlanetTimer->stop();
1079 #ifndef Q_OS_WIN
1080         if(Options::xplanetUseFIFO())
1081             fifoImageLoadWatcher.cancel();
1082 #endif
1083     }
1084     else
1085     {
1086         m_XPlanetTimer->setInterval(Options::xplanetAnimationDelay());
1087         m_XPlanetTimer->start();
1088     }
1089 }
1090 
updateXPlanetFOVEdit()1091 void XPlanetImageViewer::updateXPlanetFOVEdit()
1092 {
1093     m_FOV = m_FOVEdit->value();
1094     startXplanet();
1095 }
1096 
resetXPlanetFOV()1097 void XPlanetImageViewer::resetXPlanetFOV()
1098 {
1099     m_FOV = m_objectDefaultFOVs.at(m_CurrentObjectIndex);
1100     m_FOVEdit->setValue(m_FOV);
1101     startXplanet();
1102 }
1103 
setKStarsXPlanetFOV()1104 void XPlanetImageViewer::setKStarsXPlanetFOV()
1105 {
1106     m_FOV = KStars::Instance()->map()->fov();
1107     m_FOVEdit->setValue(m_FOV);
1108     startXplanet();
1109 }
setFOVfromList()1110 void XPlanetImageViewer::setFOVfromList()
1111 {
1112     if (!KStarsData::Instance()->getAvailableFOVs().isEmpty())
1113     {
1114         // Ask the user to choose from a list of available FOVs.
1115         QMap<QString, const FOV *> nameToFovMap;
1116         for (const FOV *f : KStarsData::Instance()->getAvailableFOVs())
1117         {
1118             nameToFovMap.insert(f->name(), f);
1119         }
1120         bool ok = false;
1121         const FOV *fov = nullptr;
1122         fov = nameToFovMap[QInputDialog::getItem(this, i18n("Choose a field-of-view"),
1123                                                        i18n("FOV to render in XPlanet:"), nameToFovMap.uniqueKeys(), 0,
1124                                                        false, &ok)];
1125         if (ok)
1126         {
1127             m_FOV = fov->sizeX() / 60 ; //Converting from arcmin to degrees
1128             m_FOVEdit->setValue(m_FOV);
1129             startXplanet();
1130         }
1131     }
1132 }
1133 
updateXPlanetRotationEdit()1134 void XPlanetImageViewer::updateXPlanetRotationEdit()
1135 {
1136     m_Rotation = m_RotateEdit->value();
1137     startXplanet();
1138 }
1139 
resetXPlanetRotation()1140 void XPlanetImageViewer::resetXPlanetRotation()
1141 {
1142     m_RotateEdit->setValue(0);
1143 }
1144 
invertXPlanetRotation()1145 void XPlanetImageViewer::invertXPlanetRotation()
1146 {
1147     m_RotateEdit->setValue(180);
1148 }
1149 
loadImage()1150 bool XPlanetImageViewer::loadImage()
1151 {
1152 #ifndef KSTARS_LITE
1153 
1154     if (!m_Image.load(m_File.fileName()))
1155     {
1156         m_ImageLoadSucceeded = false;
1157         return false;
1158     }
1159     m_ImageLoadSucceeded = true;
1160     return true;
1161 #else
1162     imageLoadSucceeded = false;
1163     return false;
1164 #endif
1165 }
1166 
showImage()1167 bool XPlanetImageViewer::showImage()
1168 {
1169 #ifndef KSTARS_LITE
1170 
1171     //If the image is larger than screen width and/or screen height,
1172     //shrink it to fit the screen
1173     QRect deskRect = QGuiApplication::primaryScreen()->geometry();
1174     int w          = deskRect.width();  // screen width
1175     int h          = deskRect.height(); // screen height
1176 
1177     if (m_Image.width() <= w && m_Image.height() > h) //Window is taller than desktop
1178         m_Image = m_Image.scaled(int(m_Image.width() * h / m_Image.height()), h);
1179     else if (m_Image.height() <= h && m_Image.width() > w) //window is wider than desktop
1180         m_Image = m_Image.scaled(w, int(m_Image.height() * w / m_Image.width()));
1181     else if (m_Image.width() > w && m_Image.height() > h) //window is too tall and too wide
1182     {
1183         //which needs to be shrunk least, width or height?
1184         float fx = float(w) / float(m_Image.width());
1185         float fy = float(h) / float(m_Image.height());
1186         if (fx > fy) //width needs to be shrunk less, so shrink to fit in height
1187             m_Image = m_Image.scaled(int(m_Image.width() * fy), h);
1188         else //vice versa
1189             m_Image = m_Image.scaled(w, int(m_Image.height() * fx));
1190     }
1191     const bool initialLoad = !isVisible();
1192 
1193     show(); // hide is default
1194 
1195     m_View->setImage(m_Image);
1196     w = m_Image.width();
1197 
1198     //If the caption is wider than the image, set the window size
1199     //to fit the caption
1200     if (m_Caption->width() > w)
1201         w = m_Caption->width();
1202     if(initialLoad)
1203         resize(w, m_Image.height());
1204     else
1205     {
1206         m_View->refreshImage();
1207     }
1208 
1209     update();
1210     show();
1211 
1212     return true;
1213 #else
1214     return false;
1215 #endif
1216 }
1217 
saveFileToDisk()1218 void XPlanetImageViewer::saveFileToDisk()
1219 {
1220 #ifndef KSTARS_LITE
1221     QFileDialog saveDialog(KStars::Instance(), i18nc("@title:window", "Save Image"), m_LastFile);
1222     saveDialog.setDefaultSuffix("png");
1223     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
1224     saveDialog.exec();
1225 
1226     if(saveDialog.result() == QFileDialog::Rejected)
1227         return;
1228     if(saveDialog.selectedFiles().isEmpty())
1229         return;
1230     QString newFileName = saveDialog.selectedFiles().first();
1231     if(newFileName.isEmpty())
1232         return;
1233 
1234     m_LastFile = newFileName;
1235 
1236     saveFile(newFileName);
1237 
1238 #endif
1239 }
1240 
saveFile(const QString & fileName)1241 void XPlanetImageViewer::saveFile(const QString &fileName)
1242 {
1243 #ifndef KSTARS_LITE
1244 
1245     if (! m_Image.save(fileName, "png"))
1246     {
1247         KSNotification::error(i18n("Saving of the image to %1 failed.", fileName));
1248     }
1249     else
1250         KStars::Instance()->statusBar()->showMessage(i18n("Saved image to %1", fileName));
1251 #endif
1252 }
1253 
invertColors()1254 void XPlanetImageViewer::invertColors()
1255 {
1256 #ifndef KSTARS_LITE
1257     // Invert colors
1258     m_View->invertPixels();
1259     m_View->update();
1260 #endif
1261 }
1262 
1263