1 /*
2     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kstars.h"
8 
9 #include "config-kstars.h"
10 #include "version.h"
11 
12 #include "fov.h"
13 #include "kactionmenu.h"
14 #include "kstarsadaptor.h"
15 #include "kstarsdata.h"
16 #include "kstarssplash.h"
17 #include "observinglist.h"
18 #include "Options.h"
19 #include "skymap.h"
20 #include "skyqpainter.h"
21 #include "texturemanager.h"
22 #include "dialogs/finddialog.h"
23 #include "dialogs/exportimagedialog.h"
24 #include "skycomponents/starblockfactory.h"
25 #ifdef HAVE_INDI
26 #include "ekos/manager.h"
27 #include "indi/drivermanager.h"
28 #include "indi/guimanager.h"
29 #endif
30 
31 #ifdef HAVE_CFITSIO
32 #include "fitsviewer/fitsviewer.h"
33 #endif
34 
35 #include <KActionCollection>
36 #include <KToolBar>
37 
38 #ifdef Q_OS_WIN
39 #include <QProcess>
40 #endif
41 #include <QStatusBar>
42 #include <QMenu>
43 
44 #include <kstars_debug.h>
45 
46 KStars *KStars::pinstance = nullptr;
47 bool KStars::Closing = false;
48 
KStars(bool doSplash,bool clockrun,const QString & startdate)49 KStars::KStars(bool doSplash, bool clockrun, const QString &startdate)
50     : KXmlGuiWindow(), StartClockRunning(clockrun), StartDateString(startdate)
51 {
52     // FIXME Hack to set RTL direction for Arabic
53     // This is not a solution. It seems qtbase_ar.qm needs to take care of this?
54     // qttranslations5-l10n does not contain qtbase_ar.qm
55     // It seems qtbase_ar.ts does not exist for Qt 5.9 at all and needs to be translated.
56     // https://wiki.qt.io/Qt_Localization
57     if (i18n("Sky") == "السماء")
58         qApp->setLayoutDirection(Qt::RightToLeft);
59 
60     setWindowTitle(i18nc("@title:window", "KStars"));
61 
62     // Set thread stack size to 32MB
63 #if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
64     QThreadPool::globalInstance()->setStackSize(33554432);
65 #endif
66 
67     // Initialize logging settings
68     if (Options::disableLogging())
69         KSUtils::Logging::Disable();
70     else if (Options::logToFile())
71         KSUtils::Logging::UseFile();
72     else
73         KSUtils::Logging::UseDefault();
74 
75     KSUtils::Logging::SyncFilterRules();
76 
77     qCInfo(KSTARS) << "Welcome to KStars" << KSTARS_VERSION << KSTARS_BUILD_RELEASE;
78     qCInfo(KSTARS) << "Build:" << KSTARS_BUILD_TS;
79     qCInfo(KSTARS) << "OS:" << QSysInfo::productType();
80     qCInfo(KSTARS) << "API:" << QSysInfo::buildAbi();
81     qCInfo(KSTARS) << "Arch:" << QSysInfo::currentCpuArchitecture();
82     qCInfo(KSTARS) << "Kernel Type:" << QSysInfo::kernelType();
83     qCInfo(KSTARS) << "Kernel Version:" << QSysInfo::kernelVersion();
84     qCInfo(KSTARS) << "Qt Version:" << QT_VERSION_STR;
85 
86     new KstarsAdaptor(
87         this); // NOTE the weird case convention, which cannot be changed as the file is generated by the moc.
88 
89 #ifdef Q_OS_OSX
90 
91     QString vlcPlugins = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns/vlc").absolutePath();
92     qputenv("VLC_PLUGIN_PATH", vlcPlugins.toLatin1());
93     QString phonon_backend_path = QDir(QCoreApplication::applicationDirPath() +
94                                        "/../PlugIns/phonon4qt5_backend/phonon_vlc.so").absolutePath();
95     qputenv("PHONON_BACKEND", phonon_backend_path.toLatin1());
96 
97     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
98     QString path            = env.value("PATH", "");
99     env.insert("PATH", "/usr/bin:/usr/local/bin:\"" + QCoreApplication::applicationDirPath() + "\":" + path);
100 
101     QProcess dbusCheck;
102     dbusCheck.setProcessEnvironment(env);
103 
104     QString pluginsDir = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns").absolutePath();
105     QString dbusPlist  = pluginsDir + "/dbus/org.freedesktop.dbus-kstars.plist";
106     QFile file(dbusPlist);
107     if (file.open(QIODevice::ReadOnly))
108     {
109         QTextStream in(&file);
110         QString pListText = in.readAll();
111         file.close();
112         int programArgsLeft         = pListText.indexOf("<key>ProgramArguments</key>");
113         int programArgsRight        = pListText.indexOf("</array>", programArgsLeft) + 8 - programArgsLeft;
114         QString currentProgramArgs  = pListText.mid(programArgsLeft, programArgsRight);
115         QString newProgramArguments = ""
116                                       "<key>ProgramArguments</key>\n"
117                                       "    <array>\n"
118                                       "        <string>" +
119                                       QCoreApplication::applicationDirPath() +
120                                       "/dbus-daemon</string>\n"
121                                       "        <string>--nofork</string>\n"
122                                       "        <string>--config-file=" +
123                                       pluginsDir +
124                                       "/dbus/kstars.conf</string>\n"
125                                       "    </array>";
126         pListText.replace(currentProgramArgs, newProgramArguments);
127         if (file.open(QIODevice::WriteOnly))
128         {
129             QTextStream stream(&file);
130             stream << pListText;
131             file.close();
132 
133             dbusCheck.start("chmod 775 " + dbusPlist);
134             dbusCheck.waitForFinished();
135             dbusCheck.start("launchctl load -w \"" + dbusPlist + "\"");
136             dbusCheck.waitForFinished();
137             qDebug("Starting DBus");
138         }
139         else
140         {
141             qDebug("DBus File Write Error");
142         }
143     }
144     else
145     {
146         qDebug("DBus File Read Error");
147     }
148 #endif
149 
150     QDBusConnection::sessionBus().registerObject("/KStars", this);
151     QDBusConnection::sessionBus().registerService("org.kde.kstars");
152 
153 #ifdef HAVE_CFITSIO
154     m_GenericFITSViewer.clear();
155 #endif
156 
157     // Set pinstance to yourself
158     pinstance = this;
159 
160     connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(slotAboutToQuit()));
161 
162     //Initialize QActionGroups
163     projectionGroup = new QActionGroup(this);
164     cschemeGroup    = new QActionGroup(this);
165     hipsGroup       = new QActionGroup(this);
166     telescopeGroup  = new QActionGroup(this);
167     telescopeGroup->setExclusive(false);
168     domeGroup       = new QActionGroup(this);
169     domeGroup->setExclusive(false);
170 
171 
172     m_KStarsData = KStarsData::Create();
173     Q_ASSERT(m_KStarsData);
174     //Set Geographic Location from Options
175     m_KStarsData->setLocationFromOptions();
176 
177     //Initialize Time and Date
178     bool datetimeSet = false;
179     if (StartDateString.isEmpty() == false)
180     {
181         KStarsDateTime startDate = KStarsDateTime::fromString(StartDateString);
182         if (startDate.isValid())
183             data()->changeDateTime(data()->geo()->LTtoUT(startDate));
184         else
185             data()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
186 
187         datetimeSet = true;
188     }
189     // JM 2016-11-15: Not need to set it again as it was initialized in the ctor of SimClock
190     /*
191     else
192         data()->changeDateTime( KStarsDateTime::currentDateTimeUtc() );
193     */
194 
195     // Initialize clock. If --paused is not in the command line, look in options
196     if (clockrun)
197         StartClockRunning = Options::runClock();
198     // If we are starting paused, we need to change datetime in data
199     if (StartClockRunning == false)
200     {
201         qCInfo(KSTARS) << "KStars is started in paused state.";
202         if (datetimeSet == false)
203             data()->changeDateTime(KStarsDateTime::currentDateTimeUtc());
204     }
205 
206     // Setup splash screen
207     KStarsSplash *splash = nullptr;
208     if (doSplash)
209     {
210         splash = new KStarsSplash(nullptr);
211         connect(m_KStarsData, SIGNAL(progressText(QString)), splash, SLOT(setMessage(QString)));
212         splash->show();
213     }
214     else
215     {
216         connect(m_KStarsData, SIGNAL(progressText(QString)), m_KStarsData, SLOT(slotConsoleMessage(QString)));
217     }
218 
219     /*
220     //set up Dark color scheme for application windows
221     DarkPalette = QPalette(QColor("black"), QColor("black"));
222     DarkPalette.setColor(QPalette::Inactive, QPalette::WindowText, QColor("red"));
223     DarkPalette.setColor(QPalette::Normal, QPalette::WindowText, QColor("red"));
224     DarkPalette.setColor(QPalette::Normal, QPalette::Base, QColor("black"));
225     DarkPalette.setColor(QPalette::Normal, QPalette::Text, QColor(238, 0, 0));
226     DarkPalette.setColor(QPalette::Normal, QPalette::Highlight, QColor(238, 0, 0));
227     DarkPalette.setColor(QPalette::Normal, QPalette::HighlightedText, QColor("black"));
228     DarkPalette.setColor(QPalette::Inactive, QPalette::Text, QColor(238, 0, 0));
229     DarkPalette.setColor(QPalette::Inactive, QPalette::Base, QColor(30, 10, 10));
230     //store original color scheme
231     OriginalPalette = QApplication::palette();
232     */
233 
234     //Initialize data.  When initialization is complete, it will run dataInitFinished()
235     if (!m_KStarsData->initialize())
236         return;
237     delete splash;
238     datainitFinished();
239 
240 #if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 && !defined(__UCLIBC__))
241     qDebug() << "glibc >= 2.1 detected.  Using GNU extension sincos()";
242 #else
243     qDebug() << "Did not find glibc >= 2.1.  Will use ANSI-compliant sin()/cos() functions.";
244 #endif
245 }
246 
createInstance(bool doSplash,bool clockrun,const QString & startdate)247 KStars *KStars::createInstance(bool doSplash, bool clockrun, const QString &startdate)
248 {
249     delete pinstance;
250     // pinstance is set directly in constructor.
251     new KStars(doSplash, clockrun, startdate);
252     Q_ASSERT(pinstance && "pinstance must be non NULL");
253     return pinstance;
254 }
255 
~KStars()256 KStars::~KStars()
257 {
258     releaseResources();
259     Q_ASSERT(pinstance);
260     pinstance = nullptr;
261 #ifdef PROFILE_COORDINATE_CONVERSION
262     qDebug() << "Spent " << SkyPoint::cpuTime_EqToHz << " seconds in " << SkyPoint::eqToHzCalls
263              << " calls to SkyPoint::EquatorialToHorizontal, for an average of "
264              << 1000. * (SkyPoint::cpuTime_EqToHz / SkyPoint::eqToHzCalls) << " ms per call";
265 #endif
266 
267 #ifdef COUNT_DMS_SINCOS_CALLS
268     qDebug() << "Constructed " << dms::dms_constructor_calls << " dms objects, of which " << dms::dms_with_sincos_called
269              << " had trigonometric functions called on them = "
270              << (float(dms::dms_with_sincos_called) / float(dms::dms_constructor_calls)) * 100. << "%";
271     qDebug() << "Of the " << dms::trig_function_calls << " calls to sin/cos/sincos on dms objects, "
272              << dms::redundant_trig_function_calls << " were redundant = "
273              << ((float(dms::redundant_trig_function_calls) / float(dms::trig_function_calls)) * 100.) << "%";
274     qDebug() << "We had " << CachingDms::cachingdms_bad_uses << " bad uses of CachingDms in all, compared to "
275              << CachingDms::cachingdms_constructor_calls << " constructed CachingDms objects = "
276              << (float(CachingDms::cachingdms_bad_uses) / float(CachingDms::cachingdms_constructor_calls)) * 100.
277              << "% bad uses";
278 #endif
279 
280     /* BUG 366596: Some KDE applications processes remain as background (zombie) processes after closing
281          * No solution to this bug so far using Qt 5.8 as of 2016-11-24
282          * Therefore, the only way to solve this on Windows is to explicitly kill kstars.exe
283          * Hopefully we do not need this hack once the above bug is resolved.
284          */
285 #ifdef Q_OS_WIN
286     QProcess::execute("taskkill /im kstars.exe /f");
287 #endif
288 }
289 
releaseResources()290 void KStars::releaseResources()
291 {
292     delete m_KStarsData;
293     m_KStarsData = nullptr;
294     delete StarBlockFactory::Instance();
295     TextureManager::Release();
296     SkyQPainter::releaseImageCache();
297     FOVManager::releaseCache();
298 
299 #ifdef HAVE_INDI
300     GUIManager::release();
301     Ekos::Manager::release();
302 #endif
303 
304 #ifdef HAVE_CFITSIO
305     //if (Options::independentWindowFITS())
306     qDeleteAll(m_FITSViewers);
307 #endif
308 
309     QSqlDatabase::removeDatabase("userdb");
310     QSqlDatabase::removeDatabase("skydb");
311 }
312 
clearCachedFindDialog()313 void KStars::clearCachedFindDialog()
314 {
315 #if 0
316     if (m_FindDialog) // dialog is cached
317     {
318         /** Delete findDialog only if it is not opened */
319         if (m_FindDialog->isHidden())
320         {
321             delete m_FindDialog;
322             m_FindDialog     = nullptr;
323             DialogIsObsolete = false;
324         }
325         else
326             DialogIsObsolete = true; // dialog was opened so it could not deleted
327     }
328 #endif
329 }
330 
applyConfig(bool doApplyFocus)331 void KStars::applyConfig(bool doApplyFocus)
332 {
333     if (Options::isTracking())
334     {
335         actionCollection()->action("track_object")->setText(i18n("Stop &Tracking"));
336         actionCollection()
337         ->action("track_object")
338         ->setIcon(QIcon::fromTheme("document-encrypt"));
339     }
340 
341     actionCollection()
342     ->action("coordsys")
343     ->setText(Options::useAltAz() ? i18n("Switch to Star Globe View (Equatorial &Coordinates)") :
344               i18n("Switch to Horizonal View (Horizontal &Coordinates)"));
345 
346     actionCollection()->action("show_time_box")->setChecked(Options::showTimeBox());
347     actionCollection()->action("show_location_box")->setChecked(Options::showGeoBox());
348     actionCollection()->action("show_focus_box")->setChecked(Options::showFocusBox());
349     actionCollection()->action("show_statusBar")->setChecked(Options::showStatusBar());
350     actionCollection()->action("show_sbAzAlt")->setChecked(Options::showAltAzField());
351     actionCollection()->action("show_sbRADec")->setChecked(Options::showRADecField());
352     actionCollection()->action("show_sbJ2000RADec")->setChecked(Options::showJ2000RADecField());
353     actionCollection()->action("show_stars")->setChecked(Options::showStars());
354     actionCollection()->action("show_deepsky")->setChecked(Options::showDeepSky());
355     actionCollection()->action("show_planets")->setChecked(Options::showSolarSystem());
356     actionCollection()->action("show_clines")->setChecked(Options::showCLines());
357     actionCollection()->action("show_constellationart")->setChecked(Options::showConstellationArt());
358     actionCollection()->action("show_cnames")->setChecked(Options::showCNames());
359     actionCollection()->action("show_cbounds")->setChecked(Options::showCBounds());
360     actionCollection()->action("show_mw")->setChecked(Options::showMilkyWay());
361     actionCollection()->action("show_equatorial_grid")->setChecked(Options::showEquatorialGrid());
362     actionCollection()->action("show_horizontal_grid")->setChecked(Options::showHorizontalGrid());
363     actionCollection()->action("show_horizon")->setChecked(Options::showGround());
364     actionCollection()->action("show_flags")->setChecked(Options::showFlags());
365     actionCollection()->action("show_supernovae")->setChecked(Options::showSupernovae());
366     actionCollection()->action("show_satellites")->setChecked(Options::showSatellites());
367     statusBar()->setVisible(Options::showStatusBar());
368 
369     //color scheme
370     m_KStarsData->colorScheme()->loadFromConfig();
371     //QApplication::setPalette(Options::darkAppColors() ? DarkPalette : OriginalPalette);
372     /**
373     //Note:  This uses style sheets to set the dark colors, this should be cross platform.  Palettes have a different behavior on OS X and Windows as opposed to Linux.
374     //It might be a good idea to use stylesheets in the future instead of palettes but this will work for now for OS X.
375     //This is also in KStarsDbus.cpp.  If you change it, change it in BOTH places.
376     @code
377     #ifdef Q_OS_OSX
378         if (Options::darkAppColors())
379             qApp->setStyleSheet(
380                 "QWidget { background-color: black; color:red; "
381                 "selection-background-color:rgb(30,30,30);selection-color:white}"
382                 "QToolBar { border:none }"
383                 "QTabBar::tab:selected { background-color:rgb(50,50,50) }"
384                 "QTabBar::tab:!selected { background-color:rgb(30,30,30) }"
385                 "QPushButton { background-color:rgb(50,50,50);border-width:1px; border-style:solid;border-color:black}"
386                 "QPushButton::disabled { background-color:rgb(10,10,10);border-width:1px; "
387                 "border-style:solid;border-color:black }"
388                 "QToolButton:Checked { background-color:rgb(30,30,30); border:none }"
389                 "QComboBox { background-color:rgb(30,30,30); }"
390                 "QComboBox::disabled { background-color:rgb(10,10,10) }"
391                 "QScrollBar::handle { background: rgb(30,30,30) }"
392                 "QSpinBox { border-width: 1px; border-style:solid; border-color:rgb(30,30,30) }"
393                 "QDoubleSpinBox { border-width:1px; border-style:solid; border-color:rgb(30,30,30) }"
394                 "QLineEdit { border-width: 1px; border-style: solid; border-color:rgb(30,30,30) }"
395                 "QCheckBox::indicator:unchecked { background-color:rgb(30,30,30);border-width:1px; "
396                 "border-style:solid;border-color:black }"
397                 "QCheckBox::indicator:checked { background-color:red;border-width:1px; "
398                 "border-style:solid;border-color:black }"
399                 "QRadioButton::indicator:unchecked { background-color:rgb(30,30,30) }"
400                 "QRadioButton::indicator:checked { background-color:red }"
401                 "QRoundProgressBar { alternate-background-color:black }"
402                 "QDateTimeEdit {background-color:rgb(30,30,30); border-width: 1px; border-style:solid; "
403                 "border-color:rgb(30,30,30) }"
404                 "QHeaderView { color:red;background-color:black }"
405                 "QHeaderView::Section { background-color:rgb(30,30,30) }"
406                 "QTableCornerButton::section{ background-color:rgb(30,30,30) }"
407                 "");
408         else
409             qApp->setStyleSheet("");
410     #endif
411     @endcode
412     **/
413 
414     //Set toolbar options from config file
415     toolBar("kstarsToolBar")->applySettings(KSharedConfig::openConfig()->group("MainToolBar"));
416     toolBar("viewToolBar")->applySettings(KSharedConfig::openConfig()->group("ViewToolBar"));
417 
418     //Geographic location
419     data()->setLocationFromOptions();
420 
421     //Focus
422     if (doApplyFocus)
423     {
424         SkyObject *fo = data()->objectNamed(Options::focusObject());
425         if (fo && fo != map()->focusObject())
426         {
427             map()->setClickedObject(fo);
428             map()->setClickedPoint(fo);
429             map()->slotCenter();
430         }
431 
432         if (!fo)
433         {
434             SkyPoint fp(Options::focusRA(), Options::focusDec());
435             if (fp.ra().Degrees() != map()->focus()->ra().Degrees() ||
436                     fp.dec().Degrees() != map()->focus()->dec().Degrees())
437             {
438                 map()->setClickedPoint(&fp);
439                 map()->slotCenter();
440             }
441         }
442     }
443 }
444 
showImgExportDialog()445 void KStars::showImgExportDialog()
446 {
447     if (m_ExportImageDialog)
448         m_ExportImageDialog->show();
449 }
450 
syncFOVActions()451 void KStars::syncFOVActions()
452 {
453     foreach (QAction *action, fovActionMenu->menu()->actions())
454     {
455         if (action->text().isEmpty())
456         {
457             continue;
458         }
459 
460         if (Options::fOVNames().contains(action->text().remove(0, 1)))
461         {
462             action->setChecked(true);
463         }
464         else
465         {
466             action->setChecked(false);
467         }
468     }
469 }
470 
hideAllFovExceptFirst()471 void KStars::hideAllFovExceptFirst()
472 {
473     // When there is only one visible FOV symbol, we don't need to do anything
474     // Also, don't do anything if there are no available FOV symbols.
475     if (data()->visibleFOVs.size() == 1 || data()->availFOVs.isEmpty())
476     {
477         return;
478     }
479     else
480     {
481         // If there are no visible FOVs, select first available
482         if (data()->visibleFOVs.isEmpty())
483         {
484             Q_ASSERT(!data()->availFOVs.isEmpty());
485             Options::setFOVNames(QStringList(data()->availFOVs.first()->name()));
486         }
487         else
488         {
489             Options::setFOVNames(QStringList(data()->visibleFOVs.first()->name()));
490         }
491 
492         // Sync FOV and update skymap
493         data()->syncFOV();
494         syncFOVActions();
495         map()->update(); // SkyMap::forceUpdate() is not required, as FOVs are drawn as overlays
496     }
497 }
498 
selectNextFov()499 void KStars::selectNextFov()
500 {
501     if (data()->getVisibleFOVs().isEmpty())
502         return;
503 
504     Q_ASSERT(!data()
505              ->getAvailableFOVs()
506              .isEmpty()); // The available FOVs had better not be empty if the visible ones are not.
507 
508     FOV *currentFov = data()->getVisibleFOVs().first();
509     int currentIdx  = data()->availFOVs.indexOf(currentFov);
510 
511     // If current FOV is not the available FOV list or there is only 1 FOV available, then return
512     if (currentIdx == -1 || data()->availFOVs.size() < 2)
513     {
514         return;
515     }
516 
517     QStringList nextFovName;
518     if (currentIdx == data()->availFOVs.size() - 1)
519     {
520         nextFovName << data()->availFOVs.first()->name();
521     }
522     else
523     {
524         nextFovName << data()->availFOVs.at(currentIdx + 1)->name();
525     }
526 
527     Options::setFOVNames(nextFovName);
528     data()->syncFOV();
529     syncFOVActions();
530     map()->update();
531 }
532 
selectPreviousFov()533 void KStars::selectPreviousFov()
534 {
535     if (data()->getVisibleFOVs().isEmpty())
536         return;
537 
538     Q_ASSERT(!data()
539              ->getAvailableFOVs()
540              .isEmpty()); // The available FOVs had better not be empty if the visible ones are not.
541 
542     FOV *currentFov = data()->getVisibleFOVs().first();
543     int currentIdx  = data()->availFOVs.indexOf(currentFov);
544 
545     // If current FOV is not the available FOV list or there is only 1 FOV available, then return
546     if (currentIdx == -1 || data()->availFOVs.size() < 2)
547     {
548         return;
549     }
550 
551     QStringList prevFovName;
552     if (currentIdx == 0)
553     {
554         prevFovName << data()->availFOVs.last()->name();
555     }
556     else
557     {
558         prevFovName << data()->availFOVs.at(currentIdx - 1)->name();
559     }
560 
561     Options::setFOVNames(prevFovName);
562     data()->syncFOV();
563     syncFOVActions();
564     map()->update();
565 }
566 
567 //FIXME Port to QML2
568 //#if 0
showWISettingsUI()569 void KStars::showWISettingsUI()
570 {
571     slotWISettings();
572 }
573 //#endif
574 
updateTime(const bool automaticDSTchange)575 void KStars::updateTime(const bool automaticDSTchange)
576 {
577     // Due to frequently use of this function save data and map pointers for speedup.
578     // Save options and geo() to a pointer would not speedup because most of time options
579     // and geo will accessed only one time.
580     KStarsData *Data = data();
581     // dms oldLST( Data->lst()->Degrees() );
582 
583     Data->updateTime(Data->geo(), automaticDSTchange);
584 
585     //We do this outside of kstarsdata just to get the coordinates
586     //displayed in the infobox to update every second.
587     //	if ( !Options::isTracking() && LST()->Degrees() > oldLST.Degrees() ) {
588     //		int nSec = int( 3600.*( LST()->Hours() - oldLST.Hours() ) );
589     //		Map->focus()->setRA( Map->focus()->ra().Hours() + double( nSec )/3600. );
590     //		if ( Options::useAltAz() ) Map->focus()->EquatorialToHorizontal( LST(), geo()->lat() );
591     //		Map->showFocusCoords();
592     //	}
593 
594     //If time is accelerated beyond slewTimescale, then the clock's timer is stopped,
595     //so that it can be ticked manually after each update, in order to make each time
596     //step exactly equal to the timeScale setting.
597     //Wrap the call to manualTick() in a singleshot timer so that it doesn't get called until
598     //the skymap has been completely updated.
599     if (Data->clock()->isManualMode() && Data->clock()->isActive())
600     {
601         // Jasem 2017-11-13: Time for each update varies.
602         // Ideally we want to advance the simulation clock by
603         // the current clock scale (e.g. 1 hour) every 1 second
604         // of real time. However, the sky map update, depending on calculations and
605         // drawing of objects, takes variable time to complete.
606         //QTimer::singleShot(0, Data->clock(), SLOT(manualTick()));
607         QTimer::singleShot(1000, Data->clock(), SLOT(manualTick()));
608     }
609 }
610 
611 #ifdef HAVE_CFITSIO
createFITSViewer()612 const QPointer<FITSViewer> &KStars::createFITSViewer()
613 {
614     if (Options::singleWindowCapturedFITS())
615         return KStars::Instance()->genericFITSViewer();
616     else
617     {
618         QPointer<FITSViewer> newFITSViewer = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance());
619 
620         connect(newFITSViewer.data(), &FITSViewer::destroyed, [ = ]()
621         {
622             m_FITSViewers.removeOne(newFITSViewer);
623         });
624 
625         m_FITSViewers.append(newFITSViewer);
626         return m_FITSViewers.constLast();
627     }
628 }
629 
genericFITSViewer()630 const QPointer<FITSViewer> &KStars::genericFITSViewer()
631 {
632     if (m_GenericFITSViewer.isNull())
633     {
634         m_GenericFITSViewer = new FITSViewer(Options::independentWindowFITS() ? nullptr : this);
635         //m_GenericFITSViewer->setAttribute(Qt::WA_DeleteOnClose);
636         m_FITSViewers.append(m_GenericFITSViewer);
637     }
638 
639     return m_GenericFITSViewer;
640 }
641 
642 //void KStars::addFITSViewer(const QSharedPointer<FITSViewer> &fv)
643 //{
644 //    m_FITSViewers.append(fv);
645 //    connect(fv.data(), &FITSViewer::terminated, [ = ]()
646 //    {
647 //        m_FITSViewers.removeOne(fv);
648 //    });
649 //}
650 
clearAllViewers()651 void KStars::clearAllViewers()
652 {
653     for (auto &fv : m_FITSViewers)
654         fv->close();
655 
656     m_FITSViewers.clear();
657 }
658 #endif
659 
closeEvent(QCloseEvent * event)660 void KStars::closeEvent(QCloseEvent *event)
661 {
662     KStars::Closing = true;
663     QWidget::closeEvent(event);
664 }
665