1 /*
2  * Stellarium
3  * Copyright (C) 2015 Alexander Wolf
4  * Copyright (C) 2016 Nick Fedoseev (visualization of ephemeris)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
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 "StelApp.hpp"
21 #include "StelCore.hpp"
22 #include "StelModuleMgr.hpp"
23 #include "StelMovementMgr.hpp"
24 #include "StelObjectMgr.hpp"
25 #include "StelUtils.hpp"
26 #include "StelTranslator.hpp"
27 #include "StelLocaleMgr.hpp"
28 #include "StelFileMgr.hpp"
29 #include "AngleSpinBox.hpp"
30 #include "SolarSystem.hpp"
31 #include "Planet.hpp"
32 #include "NebulaMgr.hpp"
33 #include "Nebula.hpp"
34 #include "StelActionMgr.hpp"
35 #include "StelSkyCultureMgr.hpp"
36 #include "StelJsonParser.hpp"
37 
38 #ifdef USE_STATIC_PLUGIN_SATELLITES
39 #include "../plugins/Satellites/src/Satellites.hpp"
40 #endif
41 #ifdef USE_STATIC_PLUGIN_QUASARS
42 #include "../plugins/Quasars/src/Quasars.hpp"
43 #endif
44 #ifdef USE_STATIC_PLUGIN_PULSARS
45 #include "../plugins/Pulsars/src/Pulsars.hpp"
46 #endif
47 #ifdef USE_STATIC_PLUGIN_EXOPLANETS
48 #include "../plugins/Exoplanets/src/Exoplanets.hpp"
49 #endif
50 #ifdef USE_STATIC_PLUGIN_NOVAE
51 #include "../plugins/Novae/src/Novae.hpp"
52 #endif
53 #ifdef USE_STATIC_PLUGIN_SUPERNOVAE
54 #include "../plugins/Supernovae/src/Supernovae.hpp"
55 #endif
56 
57 #include <QFileDialog>
58 #include <QDir>
59 #include <QSortFilterProxyModel>
60 #include <QStringListModel>
61 
62 #include "AstroCalcDialog.hpp"
63 #include "AstroCalcExtraEphemerisDialog.hpp"
64 #include "AstroCalcCustomStepsDialog.hpp"
65 #include "ui_astroCalcDialog.h"
66 
67 #include "external/qcustomplot/qcustomplot.h"
68 #include "external/qxlsx/xlsxdocument.h"
69 #include "external/qxlsx/xlsxchartsheet.h"
70 #include "external/qxlsx/xlsxcellrange.h"
71 #include "external/qxlsx/xlsxchart.h"
72 #include "external/qxlsx/xlsxrichstring.h"
73 #include "external/qxlsx/xlsxworkbook.h"
74 using namespace QXlsx;
75 
76 QVector<Ephemeris> AstroCalcDialog::EphemerisList;
77 int AstroCalcDialog::DisplayedPositionIndex = -1;
78 double AstroCalcDialog::brightLimit = 10.;
79 double AstroCalcDialog::minY = -90.;
80 double AstroCalcDialog::maxY = 90.;
81 double AstroCalcDialog::minYme = -90.;
82 double AstroCalcDialog::maxYme = 90.;
83 double AstroCalcDialog::minYsun = -90.;
84 double AstroCalcDialog::maxYsun = 90.;
85 double AstroCalcDialog::minYmoon = -90.;
86 double AstroCalcDialog::maxYmoon = 90.;
87 double AstroCalcDialog::minY1 = -1001.;
88 double AstroCalcDialog::maxY1 = 1001.;
89 double AstroCalcDialog::minY2 = -1001.;
90 double AstroCalcDialog::maxY2 = 1001.;
91 double AstroCalcDialog::transitX = -1.;
92 double AstroCalcDialog::minYld = 0.;
93 double AstroCalcDialog::maxYld = 90.;
94 double AstroCalcDialog::minYad = 0.;
95 double AstroCalcDialog::maxYad = 180.;
96 double AstroCalcDialog::minYadm = 0.;
97 double AstroCalcDialog::maxYadm = 180.;
98 double AstroCalcDialog::minYaz = 0.;
99 double AstroCalcDialog::maxYaz = 360.;
100 QString AstroCalcDialog::yAxis1Legend = "";
101 QString AstroCalcDialog::yAxis2Legend = "";
102 const QString AstroCalcDialog::dash = QChar(0x2014);
103 const QString AstroCalcDialog::delimiter(", ");
104 
AstroCalcDialog(QObject * parent)105 AstroCalcDialog::AstroCalcDialog(QObject* parent)
106 	: StelDialog("AstroCalc", parent)
107 	, extraEphemerisDialog(Q_NULLPTR)
108 	, customStepsDialog(Q_NULLPTR)
109 	//, wutModel(Q_NULLPTR)
110 	//, proxyModel(Q_NULLPTR)
111 	, currentTimeLine(Q_NULLPTR)
112 	, plotAltVsTime(false)
113 	, plotAltVsTimeSun(false)
114 	, plotAltVsTimeMoon(false)
115 	, plotAltVsTimePositive(false)
116 	, plotMonthlyElevation(false)
117 	, plotMonthlyElevationPositive(false)
118 	, plotDistanceGraph(false)
119 	, plotAngularDistanceGraph(false)
120 	, plotAziVsTime(false)
121 	, altVsTimePositiveLimit(0)
122 	, monthlyElevationPositiveLimit(0)
123 	, graphsDuration(1)
124 	, oldGraphJD(0)
125 	, graphPlotNeedsRefresh(false)
126 {
127 	ui = new Ui_astroCalcDialogForm;
128 	core = StelApp::getInstance().getCore();
129 	solarSystem = GETSTELMODULE(SolarSystem);
130 	dsoMgr = GETSTELMODULE(NebulaMgr);
131 	objectMgr = GETSTELMODULE(StelObjectMgr);
132 	starMgr = GETSTELMODULE(StarMgr);
133 	mvMgr = GETSTELMODULE(StelMovementMgr);
134 	propMgr = StelApp::getInstance().getStelPropertyManager();
135 	localeMgr = &StelApp::getInstance().getLocaleMgr();
136 	conf = StelApp::getInstance().getSettings();
137 	Q_ASSERT(ephemerisHeader.isEmpty());
138 	Q_ASSERT(phenomenaHeader.isEmpty());
139 	Q_ASSERT(positionsHeader.isEmpty());
140 	Q_ASSERT(wutHeader.isEmpty());
141 	Q_ASSERT(transitHeader.isEmpty());
142 }
143 
~AstroCalcDialog()144 AstroCalcDialog::~AstroCalcDialog()
145 {
146 	if (currentTimeLine)
147 	{
148 		currentTimeLine->stop();
149 		delete currentTimeLine;
150 		currentTimeLine = Q_NULLPTR;
151 	}
152 	delete ui;
153 	delete extraEphemerisDialog;
154 	delete customStepsDialog;
155 }
156 
retranslate()157 void AstroCalcDialog::retranslate()
158 {
159 	if (dialog)
160 	{
161 		ui->retranslateUi(dialog);
162 		setCelestialPositionsHeaderNames();
163 		setEphemerisHeaderNames();
164 		setTransitHeaderNames();
165 		setPhenomenaHeaderNames();
166 		populateCelestialBodyList();
167 		populateCelestialCategoryList();
168 		populateEphemerisTimeStepsList();
169 		populatePlanetList();
170 		populateGroupCelestialBodyList();
171 		currentCelestialPositions();
172 		prepareAxesAndGraph();
173 		prepareAziVsTimeAxesAndGraph();
174 		populateFunctionsList();
175 		prepareXVsTimeAxesAndGraph();
176 		prepareMonthlyElevationAxesAndGraph();
177 		prepareDistanceAxesAndGraph();
178 		prepareAngularDistanceAxesAndGraph();
179 		drawAltVsTimeDiagram();
180 		drawAziVsTimeDiagram();
181 		populateTimeIntervalsList();
182 		populateWutGroups();
183 		// Hack to shrink the tabs to optimal size after language change
184 		// by causing the list items to be laid out again.
185 		updateTabBarListWidgetWidth();
186 		// TODO: make a new private function and call that here and in createDialogContent?
187 		QString validDates = QString("%1 1582/10/15 - 9999/12/31").arg(q_("Gregorian dates. Valid range:"));
188 		ui->dateFromDateTimeEdit->setToolTip(validDates);
189 		ui->dateToDateTimeEdit->setToolTip(validDates);
190 		ui->phenomenFromDateEdit->setToolTip(validDates);
191 		ui->phenomenToDateEdit->setToolTip(validDates);
192 		ui->transitFromDateEdit->setToolTip(validDates);
193 		ui->transitToDateEdit->setToolTip(validDates);
194 	}
195 }
196 
createDialogContent()197 void AstroCalcDialog::createDialogContent()
198 {
199 	ui->setupUi(dialog);
200 
201 	// Kinetic scrolling
202 	kineticScrollingList << ui->celestialPositionsTreeWidget << ui->ephemerisTreeWidget << ui->phenomenaTreeWidget << ui->wutCategoryListWidget;
203 	StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
204 	if (gui)
205 	{
206 		enableKineticScrolling(gui->getFlagUseKineticScrolling());
207 		connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
208 	}
209 
210 	// Signals and slots
211 	connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
212 	connect(&StelApp::getInstance().getSkyCultureMgr(), SIGNAL(currentSkyCultureChanged(QString)), this, SLOT(populateCelestialNames(QString)));
213 	ui->stackedWidget->setCurrentIndex(0);
214 	ui->stackListWidget->setCurrentRow(0);
215 	connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
216 	connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
217 
218 	initListCelestialPositions();
219 	initListPhenomena();
220 	populateCelestialBodyList();
221 	populateCelestialCategoryList();
222 	populateEphemerisTimeStepsList();
223 	populatePlanetList();
224 	populateGroupCelestialBodyList();
225 	// Altitude vs. Time feature
226 	prepareAxesAndGraph();
227 	drawCurrentTimeDiagram();
228 	// Azimuth vs. Time feature
229 	prepareAziVsTimeAxesAndGraph();
230 	// Graphs feature
231 	populateFunctionsList();
232 	prepareXVsTimeAxesAndGraph();
233 	// Monthly Elevation
234 	prepareMonthlyElevationAxesAndGraph();
235 	// WUT
236 	initListWUT();
237 	populateTimeIntervalsList();
238 	populateWutGroups();
239 	// PC
240 	prepareDistanceAxesAndGraph();
241 	prepareAngularDistanceAxesAndGraph();
242 
243 	ui->genericMarkerColor->setText("1");
244 	ui->secondaryMarkerColor->setText("2");
245 	ui->mercuryMarkerColor->setText(QChar(0x263F));
246 	ui->venusMarkerColor->setText(QChar(0x2640));
247 	ui->marsMarkerColor->setText(QChar(0x2642));
248 	ui->jupiterMarkerColor->setText(QChar(0x2643));
249 	ui->saturnMarkerColor->setText(QChar(0x2644));
250 
251 	const double JD = core->getJD() + core->getUTCOffset(core->getJD()) / 24;
252 	QDateTime currentDT = StelUtils::jdToQDateTime(JD);
253 	ui->dateFromDateTimeEdit->setDateTime(currentDT);
254 	ui->dateToDateTimeEdit->setDateTime(currentDT.addMonths(1));
255 	ui->phenomenFromDateEdit->setDateTime(currentDT);
256 	ui->phenomenToDateEdit->setDateTime(currentDT.addMonths(1));
257 	ui->transitFromDateEdit->setDateTime(currentDT);
258 	ui->transitToDateEdit->setDateTime(currentDT.addMonths(1));
259 	ui->monthlyElevationTimeInfo->setStyleSheet("font-size: 18pt; color: rgb(238, 238, 238);");
260 
261 	// TODO: Replace QDateTimeEdit by a new StelDateTimeEdit widget to apply full range of dates
262 	// NOTE: https://github.com/Stellarium/stellarium/issues/711
263 	const QDate minDate = QDate(1582, 10, 15); // QtDateTime's minimum date is 1.1.100AD, but appears to be always Gregorian.
264 	QString validDates = QString("%1 1582/10/15 - 9999/12/31").arg(q_("Gregorian dates. Valid range:"));
265 	ui->dateFromDateTimeEdit->setMinimumDate(minDate);
266 	ui->dateFromDateTimeEdit->setToolTip(validDates);
267 	ui->dateToDateTimeEdit->setMinimumDate(minDate);
268 	ui->dateToDateTimeEdit->setToolTip(validDates);
269 	ui->phenomenFromDateEdit->setMinimumDate(minDate);
270 	ui->phenomenFromDateEdit->setToolTip(validDates);
271 	ui->phenomenToDateEdit->setMinimumDate(minDate);
272 	ui->phenomenToDateEdit->setToolTip(validDates);
273 	ui->transitFromDateEdit->setMinimumDate(minDate);
274 	ui->transitFromDateEdit->setToolTip(validDates);
275 	ui->transitToDateEdit->setMinimumDate(minDate);
276 	ui->transitToDateEdit->setToolTip(validDates);
277 	ui->pushButtonExtraEphemerisDialog->setFixedSize(QSize(20, 20));
278 	ui->pushButtonCustomStepsDialog->setFixedSize(QSize(26, 26));
279 
280 	// bug #1350669 (https://bugs.launchpad.net/stellarium/+bug/1350669)
281 	connect(ui->celestialPositionsTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), ui->celestialPositionsTreeWidget, SLOT(repaint()));
282 
283 	ui->celestialMagnitudeDoubleSpinBox->setValue(conf->value("astrocalc/celestial_magnitude_limit", 6.0).toDouble());
284 	connect(ui->celestialMagnitudeDoubleSpinBox, SIGNAL(valueChanged(double)), this,  SLOT(saveCelestialPositionsMagnitudeLimit(double)));
285 
286 	ui->horizontalCoordinatesCheckBox->setChecked(conf->value("astrocalc/flag_horizontal_coordinates", false).toBool());
287 	connect(ui->horizontalCoordinatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveCelestialPositionsHorizontalCoordinatesFlag(bool)));
288 
289 	connect(ui->celestialPositionsTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentCelestialPosition(QModelIndex)));
290 	connect(ui->celestialPositionsUpdateButton, SIGNAL(clicked()), this, SLOT(currentCelestialPositions()));
291 	connect(ui->celestialPositionsSaveButton, SIGNAL(clicked()), this, SLOT(saveCelestialPositions()));
292 	connect(ui->celestialCategoryComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveCelestialPositionsCategory(int)));
293 	connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(populateCelestialCategoryList()));
294 	connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(currentCelestialPositions()));
295 	connect(dsoMgr, SIGNAL(flagSizeLimitsUsageChanged(bool)), this, SLOT(currentCelestialPositions()));
296 	connect(dsoMgr, SIGNAL(minSizeLimitChanged(double)), this, SLOT(currentCelestialPositions()));
297 	connect(dsoMgr, SIGNAL(maxSizeLimitChanged(double)), this, SLOT(currentCelestialPositions()));
298 
299 	connectBoolProperty(ui->ephemerisShowLineCheckBox, "SolarSystem.ephemerisLineDisplayed");
300 	connectBoolProperty(ui->ephemerisShowMarkersCheckBox, "SolarSystem.ephemerisMarkersDisplayed");
301 	connectBoolProperty(ui->ephemerisShowDatesCheckBox, "SolarSystem.ephemerisDatesDisplayed");
302 	connectBoolProperty(ui->ephemerisShowMagnitudesCheckBox, "SolarSystem.ephemerisMagnitudesDisplayed");
303 	connectBoolProperty(ui->ephemerisHorizontalCoordinatesCheckBox, "SolarSystem.ephemerisHorizontalCoordinates");
304 	initListEphemeris();
305 	initEphemerisFlagNakedEyePlanets();
306 	connect(ui->ephemerisHorizontalCoordinatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(reGenerateEphemeris()));
307 	connect(ui->allNakedEyePlanetsCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveEphemerisFlagNakedEyePlanets(bool)));
308 	connect(ui->ephemerisPushButton, SIGNAL(clicked()), this, SLOT(generateEphemeris()));
309 	connect(ui->ephemerisCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupEphemeris()));
310 	connect(ui->ephemerisSaveButton, SIGNAL(clicked()), this, SLOT(saveEphemeris()));
311 	connect(ui->ephemerisTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentEphemeride(QModelIndex)));
312 	connect(ui->ephemerisTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onChangedEphemerisPosition()));
313 	connect(ui->ephemerisStepComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisTimeStep(int)));
314 	connect(ui->celestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisCelestialBody(int)));
315 	connect(ui->secondaryCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisSecondaryCelestialBody(int)));
316 
317 	connectColorButton(ui->genericMarkerColor, "SolarSystem.ephemerisGenericMarkerColor", "color/ephemeris_generic_marker_color");
318 	connectColorButton(ui->secondaryMarkerColor, "SolarSystem.ephemerisSecondaryMarkerColor", "color/ephemeris_secondary_marker_color");
319 	connectColorButton(ui->selectedMarkerColor, "SolarSystem.ephemerisSelectedMarkerColor", "color/ephemeris_selected_marker_color");
320 	connectColorButton(ui->mercuryMarkerColor, "SolarSystem.ephemerisMercuryMarkerColor", "color/ephemeris_mercury_marker_color");
321 	connectColorButton(ui->venusMarkerColor, "SolarSystem.ephemerisVenusMarkerColor", "color/ephemeris_venus_marker_color");
322 	connectColorButton(ui->marsMarkerColor, "SolarSystem.ephemerisMarsMarkerColor", "color/ephemeris_mars_marker_color");
323 	connectColorButton(ui->jupiterMarkerColor, "SolarSystem.ephemerisJupiterMarkerColor", "color/ephemeris_jupiter_marker_color");
324 	connectColorButton(ui->saturnMarkerColor, "SolarSystem.ephemerisSaturnMarkerColor", "color/ephemeris_saturn_marker_color");
325 
326 	initListTransit();
327 	connect(ui->transitsCalculateButton, SIGNAL(clicked()), this, SLOT(generateTransits()));
328 	connect(ui->transitsCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupTransits()));
329 	connect(ui->transitsSaveButton, SIGNAL(clicked()), this, SLOT(saveTransits()));
330 	connect(ui->transitTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentTransit(QModelIndex)));
331 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(setTransitCelestialBodyName()));
332 
333 	// Let's use DMS and decimal degrees as acceptable values for "Maximum allowed separation" input box
334 	ui->allowedSeparationSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
335 	ui->allowedSeparationSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
336 	ui->allowedSeparationSpinBox->setMinimum(0.0, true);
337 	ui->allowedSeparationSpinBox->setMaximum(20.0, true);
338 	ui->allowedSeparationSpinBox->setWrapping(false);
339 	ui->allowedSeparationSpinBox->setToolTip(QString("%1: %2 - %3").arg(q_("Valid range"), StelUtils::decDegToDmsStr(ui->allowedSeparationSpinBox->getMinimum(true)), StelUtils::decDegToDmsStr(ui->allowedSeparationSpinBox->getMaximum(true))));
340 
341 	ui->phenomenaOppositionCheckBox->setChecked(conf->value("astrocalc/flag_phenomena_opposition", false).toBool());
342 	connect(ui->phenomenaOppositionCheckBox, SIGNAL(toggled(bool)), this, SLOT(savePhenomenaOppositionFlag(bool)));
343 	ui->phenomenaPerihelionAphelionCheckBox->setChecked(conf->value("astrocalc/flag_phenomena_perihelion", false).toBool());
344 	connect(ui->phenomenaPerihelionAphelionCheckBox, SIGNAL(toggled(bool)), this, SLOT(savePhenomenaPerihelionAphelionFlag(bool)));
345 	ui->allowedSeparationSpinBox->setDegrees(conf->value("astrocalc/phenomena_angular_separation", 1.0).toDouble());
346 	connect(ui->allowedSeparationSpinBox, SIGNAL(valueChanged()), this, SLOT(savePhenomenaAngularSeparation()));
347 
348 	connect(ui->phenomenaPushButton, SIGNAL(clicked()), this, SLOT(calculatePhenomena()));
349 	connect(ui->phenomenaCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupPhenomena()));
350 	connect(ui->phenomenaTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentPhenomen(QModelIndex)));
351 	connect(ui->phenomenaSaveButton, SIGNAL(clicked()), this, SLOT(savePhenomena()));
352 	connect(ui->object1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(savePhenomenaCelestialBody(int)));
353 	connect(ui->object2ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(savePhenomenaCelestialGroup(int)));
354 
355 	plotAltVsTimeSun = conf->value("astrocalc/altvstime_sun", false).toBool();
356 	plotAltVsTimeMoon = conf->value("astrocalc/altvstime_moon", false).toBool();
357 	plotAltVsTimePositive = conf->value("astrocalc/altvstime_positive_only", false).toBool();
358 	ui->sunAltitudeCheckBox->setChecked(plotAltVsTimeSun);
359 	ui->moonAltitudeCheckBox->setChecked(plotAltVsTimeMoon);
360 	ui->positiveAltitudeOnlyCheckBox->setChecked(plotAltVsTimePositive);
361 	ui->positiveAltitudeLimitSpinBox->setValue(conf->value("astrocalc/altvstime_positive_limit", 0).toInt());
362 	connect(ui->sunAltitudeCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimeSunFlag(bool)));
363 	connect(ui->moonAltitudeCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimeMoonFlag(bool)));
364 	connect(ui->positiveAltitudeOnlyCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimePositiveFlag(bool)));
365 	connect(ui->positiveAltitudeLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveAltVsTimePositiveLimit(int)));
366 	connect(ui->altVsTimePlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverLine(QMouseEvent*)));
367 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAltVsTimeDiagram()));
368 
369 	connect(ui->altVsTimePlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(altTimeClick(QMouseEvent*)));
370 	connect(ui->aziVsTimePlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(aziTimeClick(QMouseEvent*)));
371 
372 	connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(handleVisibleEnabled()));
373 
374 	connect(ui->aziVsTimePlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverAziLine(QMouseEvent*)));
375 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAziVsTimeDiagram()));
376 
377 	connect(core, SIGNAL(dateChanged()), this, SLOT(drawCurrentTimeDiagram()));
378 	connect(this, SIGNAL(graphDayChanged()), this, SLOT(drawAltVsTimeDiagram()));
379 	connect(this, SIGNAL(graphDayChanged()), this, SLOT(drawAziVsTimeDiagram()));
380 
381 	// Monthly Elevation
382 	plotMonthlyElevationPositive = conf->value("astrocalc/me_positive_only", false).toBool();
383 	ui->monthlyElevationPositiveCheckBox->setChecked(plotMonthlyElevationPositive);
384 	ui->monthlyElevationPositiveLimitSpinBox->setValue(conf->value("astrocalc/me_positive_limit", 0).toInt());
385 	ui->monthlyElevationTime->setValue(conf->value("astrocalc/me_time", 0).toInt());
386 	syncMonthlyElevationTime();
387 	connect(ui->monthlyElevationTime, SIGNAL(valueChanged(int)), this, SLOT(updateMonthlyElevationTime()));
388 	connect(ui->monthlyElevationPositiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveMonthlyElevationPositiveFlag(bool)));
389 	connect(ui->monthlyElevationPositiveLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveMonthlyElevationPositiveLimit(int)));
390 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawMonthlyElevationGraph()));
391 	connect(core, SIGNAL(dateChangedByYear()), this, SLOT(drawMonthlyElevationGraph()));
392 
393 	connect(ui->graphsCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsCelestialBody(int)));
394 	connect(ui->graphsFirstComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsFirstId(int)));
395 	connect(ui->graphsSecondComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsSecondId(int)));
396 	graphsDuration = qBound(1,conf->value("astrocalc/graphs_duration",1).toInt() ,30);
397 	ui->graphsDurationSpinBox->setValue(graphsDuration);
398 	connect(ui->graphsDurationSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateGraphsDuration(int)));
399 	connect(ui->drawGraphsPushButton, SIGNAL(clicked()), this, SLOT(drawXVsTimeGraphs()));
400 	connect(ui->graphsPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverGraphs(QMouseEvent*)));
401 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(updateXVsTimeGraphs()));
402 
403 	ui->angularDistanceLimitSpinBox->setValue(conf->value("astrocalc/angular_distance_limit", 40).toInt());
404 	connect(ui->angularDistanceLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveAngularDistanceLimit(int)));
405 	connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAngularDistanceGraph()));
406 	connect(core, SIGNAL(dateChanged()), this, SLOT(drawAngularDistanceGraph()));
407 
408 	connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(handleVisibleEnabled()));
409 
410 	/*
411 	wutModel = new QStringListModel(this);
412 	proxyModel = new QSortFilterProxyModel(ui->wutMatchingObjectsListView);
413 	proxyModel->setSourceModel(wutModel);
414 	proxyModel->sort(0, Qt::AscendingOrder);
415 	proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
416 	ui->wutMatchingObjectsListView->setModel(proxyModel);
417 	*/
418 
419 	ui->wutAngularSizeLimitCheckBox->setChecked(conf->value("astrocalc/wut_angular_limit_flag", false).toBool());
420 	// Let's use DMS and decimal degrees as acceptable values for angular size limits
421 	ui->wutAngularSizeLimitMinSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
422 	ui->wutAngularSizeLimitMinSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
423 	ui->wutAngularSizeLimitMinSpinBox->setMinimum(0.0, true);
424 	ui->wutAngularSizeLimitMinSpinBox->setMaximum(10.0, true);
425 	ui->wutAngularSizeLimitMinSpinBox->setWrapping(false);
426 	ui->wutAngularSizeLimitMaxSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
427 	ui->wutAngularSizeLimitMaxSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
428 	ui->wutAngularSizeLimitMaxSpinBox->setMinimum(0.0, true);
429 	ui->wutAngularSizeLimitMaxSpinBox->setMaximum(10.0, true);
430 	ui->wutAngularSizeLimitMaxSpinBox->setWrapping(false);
431 	ui->wutAltitudeMinSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
432 	ui->wutAltitudeMinSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
433 	ui->wutAltitudeMinSpinBox->setMinimum(0.0, true);
434 	ui->wutAltitudeMinSpinBox->setMaximum(90.0, true);
435 	ui->wutAltitudeMinSpinBox->setWrapping(false);
436 
437 	// Convert from angular minutes
438 	ui->wutAngularSizeLimitMinSpinBox->setDegrees(conf->value("astrocalc/wut_angular_limit_min", 10.0).toDouble()/60.0);
439 	ui->wutAngularSizeLimitMaxSpinBox->setDegrees(conf->value("astrocalc/wut_angular_limit_max", 600.0).toDouble()/60.0);
440 	ui->wutAltitudeMinSpinBox->setDegrees(conf->value("astrocalc/wut_altitude_min", 0.0).toDouble());
441 	connect(ui->wutAngularSizeLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveWutAngularSizeFlag(bool)));
442 	connect(ui->wutAngularSizeLimitMinSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMinAngularSizeLimit()));
443 	connect(ui->wutAngularSizeLimitMaxSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMaxAngularSizeLimit()));
444 	connect(ui->wutAltitudeMinSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMinAltitude()));
445 
446 	ui->wutMagnitudeDoubleSpinBox->setValue(conf->value("astrocalc/wut_magnitude_limit", 10.0).toDouble());
447 	connect(ui->wutMagnitudeDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(saveWutMagnitudeLimit(double)));
448 	connect(ui->wutComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveWutTimeInterval(int)));
449 	connect(ui->wutCategoryListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(calculateWutObjects()));
450 	//connect(ui->wutMatchingObjectsTreeWidget->selectionModel() , SIGNAL(currentRowChanged(const QModelIndex&, const QModelIndex&)),
451 	//	this, SLOT(selectWutObject(const QModelIndex&)));
452 	connect(ui->wutMatchingObjectsTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectWutObject(QModelIndex)));
453 	connect(ui->saveObjectsButton, SIGNAL(clicked()), this, SLOT(saveWutObjects()));
454 	//connect(ui->wutMatchingObjectsLineEdit, SIGNAL(textChanged(const QString&)), proxyModel, SLOT(setFilterWildcard(const QString&)));
455 	ui->wutMatchingObjectsLineEdit->setVisible(false);
456 	connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(calculateWutObjects()));
457 	connect(dsoMgr, SIGNAL(typeFiltersChanged(Nebula::TypeGroup)), this, SLOT(calculateWutObjects()));
458 	connect(dsoMgr, SIGNAL(flagSizeLimitsUsageChanged(bool)), this, SLOT(calculateWutObjects()));
459 	connect(dsoMgr, SIGNAL(minSizeLimitChanged(double)), this, SLOT(calculateWutObjects()));
460 	connect(dsoMgr, SIGNAL(maxSizeLimitChanged(double)), this, SLOT(calculateWutObjects()));
461 	connect(core, SIGNAL(dateChanged()), this, SLOT(calculateWutObjects()));
462 
463 	QAction *clearAction = ui->wutMatchingObjectsLineEdit->addAction(QIcon(":/graphicGui/uieBackspaceInputButton.png"), QLineEdit::ActionPosition::TrailingPosition);
464 	connect(clearAction, SIGNAL(triggered()), this, SLOT(searchWutClear()));
465 	StelModuleMgr& moduleMgr = StelApp::getInstance().getModuleMgr();
466 	if (moduleMgr.isPluginLoaded("Quasars"))
467 	{
468 		#ifdef USE_STATIC_PLUGIN_QUASARS
469 		Quasars* qsoMgr = GETSTELMODULE(Quasars);
470 		connect(qsoMgr, SIGNAL(flagQuasarsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
471 		#endif
472 	}
473 	if (moduleMgr.isPluginLoaded("Pulsars"))
474 	{
475 		#ifdef USE_STATIC_PLUGIN_PULSARS
476 		Pulsars* psrMgr = GETSTELMODULE(Pulsars);
477 		connect(psrMgr, SIGNAL(flagPulsarsVisibilityChanged(bool)), this, SLOT(populateWutGroups()));
478 		//connect(psrMgr, SIGNAL(flagPulsarsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
479 		#endif
480 	}
481 	if (moduleMgr.isPluginLoaded("Exoplanets"))
482 	{
483 		#ifdef USE_STATIC_PLUGIN_EXOPLANETS
484 		Exoplanets* epMgr = GETSTELMODULE(Exoplanets);
485 		connect(epMgr, SIGNAL(flagExoplanetsVisibilityChanged(bool)), this, SLOT(populateWutGroups()));
486 		//connect(epMgr, SIGNAL(flagExoplanetsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
487 		#endif
488 	}
489 
490 	currentCelestialPositions();
491 
492 	currentTimeLine = new QTimer(this);
493 	connect(currentTimeLine, SIGNAL(timeout()), this, SLOT(drawCurrentTimeDiagram()));
494 	connect(currentTimeLine, SIGNAL(timeout()), this, SLOT(computePlanetaryData()));
495 	currentTimeLine->start(1000); // Update 'now' line position every second
496 
497 	connect(ui->firstCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveFirstCelestialBody(int)));
498 	connect(ui->secondCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveSecondCelestialBody(int)));
499 	connect(ui->pcDistanceGraphPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverDistanceGraph(QMouseEvent*)));
500 	connect(core, SIGNAL(dateChanged()), this, SLOT(drawDistanceGraph()));
501 
502 	connect(solarSystem, SIGNAL(solarSystemDataReloaded()), this, SLOT(updateSolarSystemData()));
503 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(updateAstroCalcData()));
504 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawAltVsTimeDiagram()));
505 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawMonthlyElevationGraph()));
506 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawDistanceGraph()));
507 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawAngularDistanceGraph()));
508 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(initEphemerisFlagNakedEyePlanets()));
509 
510 	connect(ui->stackListWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*, QListWidgetItem*)));
511 	connect(ui->tabWidgetGraphs, SIGNAL(currentChanged(int)), this, SLOT(changeGraphsTab(int)));
512 	connect(ui->tabWidgetPC, SIGNAL(currentChanged(int)), this, SLOT(changePCTab(int)));
513 
514 	connect(ui->pushButtonExtraEphemerisDialog, SIGNAL(clicked()), this, SLOT(showExtraEphemerisDialog()));
515 	connect(ui->pushButtonCustomStepsDialog, SIGNAL(clicked()), this, SLOT(showCustomStepsDialog()));
516 
517 	updateTabBarListWidgetWidth();
518 
519 	ui->celestialPositionsUpdateButton->setShortcut(QKeySequence("Shift+F10"));
520 	ui->ephemerisPushButton->setShortcut(QKeySequence("Shift+F10"));
521 	ui->transitsCalculateButton->setShortcut(QKeySequence("Shift+F10"));
522 	ui->phenomenaPushButton->setShortcut(QKeySequence("Shift+F10"));
523 
524 	// Let's improve visibility of the text
525 	QString style = "QLabel { color: rgb(238, 238, 238); }";
526 	ui->celestialPositionsTimeLabel->setStyleSheet(style);
527 	ui->altVsTimeLabel->setStyleSheet(style);
528 	//ui->altVsTimeTitle->setStyleSheet(style);
529 	ui->aziVsTimeLabel->setStyleSheet(style);
530 	//ui->aziVsTimeTitle->setStyleSheet(style);
531 	ui->monthlyElevationLabel->setStyleSheet(style);
532 	//ui->monthlyElevationTitle->setStyleSheet(style);
533 	ui->graphsFirstLabel->setStyleSheet(style);
534 	ui->graphsSecondLabel->setStyleSheet(style);
535 	ui->graphsDurationLabel->setStyleSheet(style);
536 	ui->graphsYearsLabel->setStyleSheet(style);
537 	ui->angularDistanceNote->setStyleSheet(style);
538 	ui->angularDistanceLimitLabel->setStyleSheet(style);
539 	//ui->angularDistanceTitle->setStyleSheet(style);
540 	ui->graphsNoteLabel->setStyleSheet(style);
541 	ui->transitNotificationLabel->setStyleSheet(style);
542 	style = "QCheckBox { color: rgb(238, 238, 238); }";
543 	ui->sunAltitudeCheckBox->setStyleSheet(style);
544 	ui->moonAltitudeCheckBox->setStyleSheet(style);
545 	ui->positiveAltitudeOnlyCheckBox->setStyleSheet(style);
546 	ui->monthlyElevationPositiveCheckBox->setStyleSheet(style);
547 }
548 
populateCelestialNames(QString)549 void AstroCalcDialog::populateCelestialNames(QString)
550 {
551 	populateCelestialBodyList();
552 	populatePlanetList();
553 }
554 
showExtraEphemerisDialog()555 void AstroCalcDialog::showExtraEphemerisDialog()
556 {
557 	if (extraEphemerisDialog == Q_NULLPTR)
558 		extraEphemerisDialog = new AstroCalcExtraEphemerisDialog();
559 
560 	extraEphemerisDialog->setVisible(true);
561 }
562 
showCustomStepsDialog()563 void AstroCalcDialog::showCustomStepsDialog()
564 {
565 	if (customStepsDialog == Q_NULLPTR)
566 		customStepsDialog = new AstroCalcCustomStepsDialog();
567 
568 	customStepsDialog->setVisible(true);
569 }
570 
searchWutClear()571 void AstroCalcDialog::searchWutClear()
572 {
573 	ui->wutMatchingObjectsLineEdit->clear();
574 }
575 
updateAstroCalcData()576 void AstroCalcDialog::updateAstroCalcData()
577 {
578 	drawAltVsTimeDiagram();
579 	populateCelestialBodyList();
580 	populatePlanetList();
581 }
582 
saveAltVsTimeSunFlag(bool state)583 void AstroCalcDialog::saveAltVsTimeSunFlag(bool state)
584 {
585 	if (plotAltVsTimeSun!=state)
586 	{
587 		plotAltVsTimeSun = state;
588 		conf->setValue("astrocalc/altvstime_sun", plotAltVsTimeSun);
589 
590 		drawAltVsTimeDiagram();
591 	}
592 }
593 
saveAltVsTimeMoonFlag(bool state)594 void AstroCalcDialog::saveAltVsTimeMoonFlag(bool state)
595 {
596 	if (plotAltVsTimeMoon!=state)
597 	{
598 		plotAltVsTimeMoon = state;
599 		conf->setValue("astrocalc/altvstime_moon", plotAltVsTimeMoon);
600 
601 		drawAltVsTimeDiagram();
602 	}
603 }
604 
saveAltVsTimePositiveFlag(bool state)605 void AstroCalcDialog::saveAltVsTimePositiveFlag(bool state)
606 {
607 	if (plotAltVsTimePositive!=state)
608 	{
609 		plotAltVsTimePositive = state;
610 		conf->setValue("astrocalc/altvstime_positive_only", plotAltVsTimePositive);
611 
612 		drawAltVsTimeDiagram();
613 	}
614 }
615 
saveAltVsTimePositiveLimit(int limit)616 void AstroCalcDialog::saveAltVsTimePositiveLimit(int limit)
617 {
618 	if (altVsTimePositiveLimit!=limit)
619 	{
620 		altVsTimePositiveLimit = limit;
621 		conf->setValue("astrocalc/altvstime_positive_limit", altVsTimePositiveLimit);
622 		drawAltVsTimeDiagram();
623 	}
624 }
625 
prepareAziVsTimeAxesAndGraph()626 void AstroCalcDialog::prepareAziVsTimeAxesAndGraph()
627 {
628 	QString xAxisStr = q_("Local Time");
629 	QString yAxisStr = QString("%1, %2").arg(q_("Azimuth"), QChar(0x00B0));
630 
631 	QColor axisColor(Qt::white);
632 	QPen axisPen(axisColor, 1);
633 
634 	ui->aziVsTimePlot->clearGraphs();
635 
636 	// main data: Azimuth vs. Time graph
637 	ui->aziVsTimePlot->addGraph();
638 	ui->aziVsTimePlot->setBackground(QBrush(QColor(86, 87, 90)));
639 	ui->aziVsTimePlot->graph(0)->setPen(QPen(Qt::red, 1));
640 	ui->aziVsTimePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
641 	ui->aziVsTimePlot->graph(0)->rescaleAxes(true);
642 
643 	// additional data: Current Time Diagram
644 	ui->aziVsTimePlot->addGraph();
645 	ui->aziVsTimePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
646 	ui->aziVsTimePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
647 	ui->aziVsTimePlot->graph(1)->setName("[Now]");
648 
649 	ui->aziVsTimePlot->xAxis->setLabel(xAxisStr);
650 	ui->aziVsTimePlot->yAxis->setLabel(yAxisStr);
651 
652 	ui->aziVsTimePlot->xAxis->setRange(43200, 129600); // 24 hours since 12h00m (range in seconds)
653 	ui->aziVsTimePlot->xAxis->setScaleType(QCPAxis::stLinear);
654 	ui->aziVsTimePlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
655 	ui->aziVsTimePlot->xAxis->setLabelColor(axisColor);
656 	ui->aziVsTimePlot->xAxis->setTickLabelColor(axisColor);
657 	ui->aziVsTimePlot->xAxis->setBasePen(axisPen);
658 	ui->aziVsTimePlot->xAxis->setTickPen(axisPen);
659 	ui->aziVsTimePlot->xAxis->setSubTickPen(axisPen);
660 	ui->aziVsTimePlot->xAxis->setDateTimeFormat("H:mm");
661 	ui->aziVsTimePlot->xAxis->setDateTimeSpec(Qt::UTC); // Qt::UTC + core->getUTCOffset() give local time
662 	ui->aziVsTimePlot->xAxis->setAutoTickStep(false);
663 	ui->aziVsTimePlot->xAxis->setTickStep(7200); // step is 2 hours (in seconds)
664 	ui->aziVsTimePlot->xAxis->setAutoSubTicks(false);
665 	ui->aziVsTimePlot->xAxis->setSubTickCount(7);
666 
667 	ui->aziVsTimePlot->yAxis->setRange(minYaz, maxYaz);
668 	ui->aziVsTimePlot->yAxis->setScaleType(QCPAxis::stLinear);
669 	ui->aziVsTimePlot->yAxis->setLabelColor(axisColor);
670 	ui->aziVsTimePlot->yAxis->setTickLabelColor(axisColor);
671 	ui->aziVsTimePlot->yAxis->setBasePen(axisPen);
672 	ui->aziVsTimePlot->yAxis->setTickPen(axisPen);
673 	ui->aziVsTimePlot->yAxis->setSubTickPen(axisPen);
674 }
675 
drawAziVsTimeDiagram()676 void AstroCalcDialog::drawAziVsTimeDiagram()
677 {
678 	// Avoid crash!
679 	if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
680 		return;
681 
682 	// special case - plot the graph when tab is visible
683 	//..
684 	// we got notified about a reason to redraw the plot, but dialog was
685 	// not visible. which means we must redraw when becoming visible again!
686 	if (!dialog->isVisible() && plotAziVsTime)
687 	{
688 		graphPlotNeedsRefresh = true;
689 		return;
690 	}
691 
692 	if (!plotAziVsTime) return;
693 
694 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
695 
696 	if (!selectedObjects.isEmpty())
697 	{
698 		bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
699 		// X axis - time; Y axis - azimuth
700 		QList<double> aX, aY;
701 
702 		StelObjectP selectedObject = selectedObjects[0];
703 		ui->aziVsTimeTitle->setText(selectedObject->getNameI18n());
704 		double currentJD = core->getJD();
705 		double shift = core->getUTCOffset(currentJD) / 24.0;
706 		double noon = static_cast<int>(currentJD + shift);
707 		double az, alt, deg;
708 		bool sign;
709 
710 		int step = 180;
711 		int limit = 485;
712 		bool isSatellite = false;
713 
714 #ifdef USE_STATIC_PLUGIN_SATELLITES
715 		SatelliteP sat;
716 		if (selectedObject->getType() == "Satellite")
717 		{
718 			// get reference to satellite
719 			isSatellite = true;
720 			sat = GETSTELMODULE(Satellites)->getById(selectedObject->getInfoMap(core)["catalog"].toString());
721 		}
722 #endif
723 
724 		for (int i = -5; i <= limit; i++) // 24 hours + 15 minutes in both directions
725 		{
726 			// A new point on the graph every 3 minutes with shift to right 12 hours
727 			// to get midnight at the center of diagram (i.e. accuracy is 3 minutes)
728 			double ltime = i * step + 43200;
729 			aX.append(ltime);
730 			double JD = noon + ltime / 86400 - shift - 0.5;
731 			core->setJD(JD);
732 
733 			if (isSatellite)
734 			{
735 #ifdef USE_STATIC_PLUGIN_SATELLITES
736 				sat->update(0.0);
737 #endif
738 			}
739 			else
740 				core->update(0.0);
741 
742 			StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
743 			double direction = 3.; // N is zero, E is 90 degrees
744 			if (useSouthAzimuth)
745 				direction = 2.;
746 			az = direction*M_PI - az;
747 			if (az > M_PI*2)
748 				az -= M_PI*2;
749 			StelUtils::radToDecDeg(az, sign, deg);
750 			aY.append(deg);
751 		}
752 		core->setJD(currentJD);
753 
754 		QVector<double> x = aX.toVector(), y = aY.toVector();
755 		minYaz = *std::min_element(aY.begin(), aY.end()) - 2.0;
756 		maxYaz = *std::max_element(aY.begin(), aY.end()) + 2.0;
757 
758 		prepareAziVsTimeAxesAndGraph();
759 		drawCurrentTimeDiagram();
760 
761 		QString name = selectedObject->getNameI18n();
762 		if (name.isEmpty())
763 		{
764 			QString otype = selectedObject->getType();
765 			if (otype == "Nebula")
766 			{
767 				name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
768 				if (name.isEmpty())
769 					name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
770 			}
771 
772 			if (otype == "Star" || otype=="Pulsar")
773 				selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
774 		}
775 
776 		drawTransitTimeDiagram();
777 
778 		ui->aziVsTimePlot->graph(0)->setData(x, y);
779 		ui->aziVsTimePlot->graph(0)->setName(name);
780 		ui->aziVsTimePlot->replot();
781 	}
782 
783 	// clean up the data when selection is removed
784 	if (!objectMgr->getWasSelected())
785 	{
786 		ui->aziVsTimePlot->graph(0)->data()->clear(); // main data: Azimuth vs. Time graph
787 		ui->aziVsTimePlot->replot();
788 	}
789 }
790 
mouseOverAziLine(QMouseEvent * event)791 void AstroCalcDialog::mouseOverAziLine(QMouseEvent* event)
792 {
793 	double x = ui->aziVsTimePlot->xAxis->pixelToCoord(event->pos().x());
794 	double y = ui->aziVsTimePlot->yAxis->pixelToCoord(event->pos().y());
795 
796 	QCPAbstractPlottable* abstractGraph = ui->aziVsTimePlot->plottableAt(event->pos(), false);
797 	QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
798 
799 	if (ui->aziVsTimePlot->xAxis->range().contains(x) && ui->aziVsTimePlot->yAxis->range().contains(y))
800 	{
801 		QString info = "";
802 		if (graph)
803 		{
804 			double JD;
805 			if (graph->name() == "[Now]")
806 			{
807 				JD = core->getJD();
808 				info = q_("Now about %1").arg(StelUtils::jdToQDateTime(JD + core->getUTCOffset(JD)/24).toString("H:mm"));
809 			}
810 			else
811 			{
812 				JD = x / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
813 				QString LT = StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm");
814 				if (StelApp::getInstance().getFlagShowDecimalDegrees())
815 					info = QString("%1<br />%2: %3<br />%4: %5%6").arg(ui->aziVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Azimuth"), QString::number(y, 'f', 2), QChar(0x00B0));
816 				else
817 					info = QString("%1<br />%2: %3<br />%4: %5").arg(ui->aziVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Azimuth"), StelUtils::decDegToDmsStr(y));
818 			}
819 		}
820 		ui->aziVsTimePlot->setToolTip(info);
821 	}
822 
823 	ui->aziVsTimePlot->update();
824 	ui->aziVsTimePlot->replot();
825 }
826 
initListCelestialPositions()827 void AstroCalcDialog::initListCelestialPositions()
828 {
829 	ui->celestialPositionsTreeWidget->clear();
830 	ui->celestialPositionsTreeWidget->setColumnCount(CColumnCount);
831 	setCelestialPositionsHeaderNames();
832 	ui->celestialPositionsTreeWidget->header()->setSectionsMovable(false);
833 	ui->celestialPositionsTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
834 }
835 
setCelestialPositionsHeaderNames()836 void AstroCalcDialog::setCelestialPositionsHeaderNames()
837 {
838 	Q_ASSERT(ui->celestialCategoryComboBox);
839 	QComboBox* category = ui->celestialCategoryComboBox;
840 	int celType = category->itemData(category->currentIndex()).toInt();
841 
842 	bool horizon = ui->horizontalCoordinatesCheckBox->isChecked();
843 
844 	positionsHeader.clear();
845 	// TRANSLATORS: name of object
846 	positionsHeader << q_("Name");
847 	if (horizon)
848 	{
849 		// TRANSLATORS: azimuth
850 		positionsHeader << q_("Azimuth");
851 		// TRANSLATORS: altitude
852 		positionsHeader << q_("Altitude");
853 	}
854 	else
855 	{
856 		// TRANSLATORS: right ascension
857 		positionsHeader << q_("RA (J2000)");
858 		// TRANSLATORS: declination
859 		positionsHeader << q_("Dec (J2000)");
860 	}
861 	if (celType == 12 || celType == 102 || celType == 111) // check for dark nebulae
862 	{
863 		// TRANSLATORS: opacity
864 		positionsHeader << q_("Opac.");
865 	}
866 	else
867 	{
868 		// TRANSLATORS: magnitude
869 		positionsHeader << q_("Mag.");
870 	}
871 	// TRANSLATORS: angular size, arc-minutes
872 	positionsHeader << QString("%1, '").arg(q_("A.S."));
873 	if (celType == 170)
874 	{
875 		// TRANSLATORS: separation, arc-seconds
876 		positionsHeader << QString("%1, \"").arg(q_("Sep."));
877 	}
878 	else if (celType == 171 || celType == 173 || celType == 174)
879 	{
880 		// TRANSLATORS: period, days
881 		positionsHeader << QString("%1, %2").arg(q_("Per."), qc_("d", "days"));
882 	}
883 	else if (celType >= 200)
884 	{
885 		// TRANSLATORS: distance, AU
886 		positionsHeader << QString("%1, %2").arg(q_("Dist."), qc_("AU", "distance, astronomical unit"));
887 	}
888 	else if (celType == 172)
889 	{
890 		// TRANSLATORS: proper motion, arc-second per year
891 		positionsHeader << QString("%1, %2").arg(q_("P.M."), qc_("\"/yr", "arc-second per year"));
892 	}
893 	else
894 	{
895 		// TRANSLATORS: surface brightness
896 		positionsHeader << q_("S.B.");
897 	}
898 	// TRANSLATORS: time of transit
899 	positionsHeader << qc_("Transit", "celestial event; passage across a meridian");
900 	// TRANSLATORS: elevation
901 	positionsHeader << q_("Elev.");
902 	// TRANSLATORS: elongation
903 	positionsHeader << q_("Elong.");
904 	// TRANSLATORS: type of object
905 	positionsHeader << q_("Type");
906 
907 	ui->celestialPositionsTreeWidget->setHeaderLabels(positionsHeader);
908 	adjustCelestialPositionsColumns();
909 }
910 
adjustCelestialPositionsColumns()911 void AstroCalcDialog::adjustCelestialPositionsColumns()
912 {
913 	// adjust the column width
914 	for (int i = 0; i < CColumnCount; ++i)
915 	{
916 		ui->celestialPositionsTreeWidget->resizeColumnToContents(i);
917 	}
918 }
919 
onChangedEphemerisPosition()920 void AstroCalcDialog::onChangedEphemerisPosition()
921 {
922 	DisplayedPositionIndex =ui->ephemerisTreeWidget->currentItem()->data(EphemerisRA, Qt::UserRole).toInt();
923 }
924 
computeMaxElevation(StelObjectP obj)925 double AstroCalcDialog::computeMaxElevation(StelObjectP obj)
926 {
927 	// NOTE: Without refraction!
928 	double ra, dec, elevation;
929 	const double lat = static_cast<double>(core->getCurrentLocation().latitude);
930 	StelUtils::rectToSphe(&ra, &dec, obj->getEquinoxEquatorialPos(core));
931 	dec *= M_180_PI;
932 	if (lat>=0.)
933 	{
934 		// star between zenith and southern horizon, or between N pole and zenith
935 		elevation = (dec<=lat) ? (90. - lat + dec) : (90. + lat - dec);
936 	}
937 	else
938 	{
939 		// star between zenith and north horizon, or between S pole and zenith
940 		elevation = (dec>=lat) ? (90. + lat - dec) : (90. - lat + dec);
941 	}
942 	return elevation * M_PI_180;
943 }
944 
populateCelestialCategoryList()945 void AstroCalcDialog::populateCelestialCategoryList()
946 {
947 	Q_ASSERT(ui->celestialCategoryComboBox);
948 
949 	QComboBox* category = ui->celestialCategoryComboBox;
950 
951 	category->blockSignals(true);
952 	int index = category->currentIndex();
953 	QVariant selectedCategoryId = category->itemData(index);
954 	category->clear();
955 
956 	QMap<QString,QString> map = objectMgr->objectModulesMap();
957 	QMapIterator<QString,QString> it(map);
958 	while(it.hasNext())
959 	{
960 		it.next();
961 		QString key = it.key();
962 		if (key.startsWith("NebulaMgr") && key.contains(":"))
963 			category->addItem(q_(it.value()), key.remove("NebulaMgr:"));
964 
965 		if (key.startsWith("StarMgr") && key.contains(":"))
966 		{
967 			int kn = key.remove("StarMgr:").toInt();
968 			if (kn>1 && kn<=6) // Original IDs: 2, 3, 4, 5, 6
969 				category->addItem(q_(it.value()), QString::number(kn + 168)); // AstroCalc IDs: 170, 171, 172, 173, 174
970 		}
971 	}
972 	category->addItem(q_("Deep-sky objects"), "169");
973 	category->addItem(q_("Solar system objects"), "200");
974 	category->addItem(q_("Solar system objects: comets"), "201");
975 	category->addItem(q_("Solar system objects: minor bodies"), "202");
976 	category->addItem(q_("Solar system objects: planets"), "203");
977 
978 	index = category->findData(selectedCategoryId, Qt::UserRole, Qt::MatchCaseSensitive);
979 	if (index < 0) // read config data
980 		index = category->findData(conf->value("astrocalc/celestial_category", "200").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
981 
982 	if (index < 0) // Unknown yet? Default step: Solar system objects
983 		index = category->findData("200", Qt::UserRole, Qt::MatchCaseSensitive);
984 
985 	category->setCurrentIndex(index);
986 	category->model()->sort(0);
987 	category->blockSignals(false);
988 }
989 
saveCelestialPositionsMagnitudeLimit(double mag)990 void AstroCalcDialog::saveCelestialPositionsMagnitudeLimit(double mag)
991 {
992 	conf->setValue("astrocalc/celestial_magnitude_limit", QString::number(mag, 'f', 2));
993 	// Refresh the celestial bodies positions table
994 	currentCelestialPositions();
995 }
996 
saveCelestialPositionsHorizontalCoordinatesFlag(bool b)997 void AstroCalcDialog::saveCelestialPositionsHorizontalCoordinatesFlag(bool b)
998 {
999 	conf->setValue("astrocalc/flag_horizontal_coordinates", b);
1000 	// Refresh the celestial bodies positions table
1001 	currentCelestialPositions();
1002 }
1003 
saveCelestialPositionsCategory(int index)1004 void AstroCalcDialog::saveCelestialPositionsCategory(int index)
1005 {
1006 	Q_ASSERT(ui->celestialCategoryComboBox);
1007 	QComboBox* category = ui->celestialCategoryComboBox;
1008 	conf->setValue("astrocalc/celestial_category", category->itemData(index).toInt());
1009 	// Refresh the celestial bodies positions table
1010 	currentCelestialPositions();
1011 }
1012 
fillCelestialPositionTable(QString objectName,QString RA,QString Dec,double magnitude,QString angularSize,QString angularSizeToolTip,QString extraData,QString extraDataToolTip,QString transitTime,QString maxElevation,QString sElongation,QString objectType)1013 void AstroCalcDialog::fillCelestialPositionTable(QString objectName, QString RA, QString Dec, double magnitude,
1014 						 QString angularSize, QString angularSizeToolTip, QString extraData,
1015 						 QString extraDataToolTip, QString transitTime, QString maxElevation,
1016 						 QString sElongation, QString objectType)
1017 {
1018 	ACCelPosTreeWidgetItem* treeItem = new ACCelPosTreeWidgetItem(ui->celestialPositionsTreeWidget);
1019 	treeItem->setText(CColumnName, objectName);
1020 	treeItem->setText(CColumnRA, RA);
1021 	treeItem->setTextAlignment(CColumnRA, Qt::AlignRight);
1022 	treeItem->setText(CColumnDec, Dec);
1023 	treeItem->setTextAlignment(CColumnDec, Qt::AlignRight);
1024 	if (magnitude>90.)
1025 		treeItem->setText(CColumnMagnitude, dash);
1026 	else
1027 		treeItem->setText(CColumnMagnitude, QString::number(magnitude, 'f', 2));
1028 	treeItem->setTextAlignment(CColumnMagnitude, Qt::AlignRight);
1029 	treeItem->setText(CColumnAngularSize, angularSize);
1030 	treeItem->setTextAlignment(CColumnAngularSize, Qt::AlignRight);
1031 	treeItem->setToolTip(CColumnAngularSize, angularSizeToolTip);
1032 	treeItem->setText(CColumnExtra, extraData);
1033 	treeItem->setTextAlignment(CColumnExtra, Qt::AlignRight);
1034 	treeItem->setToolTip(CColumnExtra, extraDataToolTip);
1035 	treeItem->setText(CColumnTransit, transitTime);
1036 	treeItem->setTextAlignment(CColumnTransit, Qt::AlignRight);
1037 	treeItem->setText(CColumnMaxElevation, maxElevation);
1038 	treeItem->setTextAlignment(CColumnMaxElevation, Qt::AlignRight);
1039 	treeItem->setToolTip(CColumnMaxElevation, q_("Elevation of object at moment of upper culmination"));
1040 	treeItem->setText(CColumnElongation, sElongation);
1041 	treeItem->setTextAlignment(CColumnElongation, Qt::AlignRight);
1042 	treeItem->setToolTip(CColumnElongation, q_("Angular distance from the Sun at the moment of computation of position"));
1043 	treeItem->setText(CColumnType, objectType);
1044 }
1045 
currentCelestialPositions()1046 void AstroCalcDialog::currentCelestialPositions()
1047 {
1048 	QPair<QString, QString> coordStrings;
1049 
1050 	initListCelestialPositions();
1051 	ui->celestialPositionsTreeWidget->showColumn(CColumnAngularSize);
1052 
1053 	const double mag = ui->celestialMagnitudeDoubleSpinBox->value();
1054 	const bool horizon = ui->horizontalCoordinatesCheckBox->isChecked();
1055 	const bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
1056 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1057 
1058 	const double JD = core->getJD();
1059 	const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
1060 	ui->celestialPositionsTimeLabel->setText(q_("Positions on %1").arg(QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))));
1061 	Vec4d rts;
1062 	Vec3d observerHelioPos;
1063 	double angularDistance;
1064 	PlanetP sun = solarSystem->getSun();
1065 
1066 	Q_ASSERT(ui->celestialCategoryComboBox);
1067 	QComboBox* category = ui->celestialCategoryComboBox;
1068 	QString celType = category->itemData(category->currentIndex()).toString();
1069 	const int celTypeId = celType.toInt();
1070 
1071 	if (celTypeId < 170)
1072 	{
1073 		QString mu;
1074 		if (dsoMgr->getFlagSurfaceBrightnessShortNotationUsage())
1075 		{
1076 			mu = QString("<sup>m</sup>/%1'").arg(QChar(0x2B1C));
1077 			if (dsoMgr->getFlagSurfaceBrightnessArcsecUsage())
1078 				mu = QString("<sup>m</sup>/%1\"").arg(QChar(0x2B1C));
1079 		}
1080 		else
1081 		{
1082 			mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-min"));
1083 			if (dsoMgr->getFlagSurfaceBrightnessArcsecUsage())
1084 				mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-sec"));
1085 		}
1086 		double magOp;
1087 		bool passByBrightness;
1088 		QString dsoName;
1089 		QString asToolTip = QString("%1, %2").arg(q_("Average angular size"), q_("arc-min"));
1090 		// Deep-sky objects
1091 		QList<NebulaP> celestialObjects;
1092 		if (celTypeId==169)
1093 			celestialObjects = dsoMgr->getAllDeepSkyObjects().toList();
1094 		else
1095 			celestialObjects = dsoMgr->getDeepSkyObjectsByType(celType);
1096 		for (const auto& obj : qAsConst(celestialObjects))
1097 		{
1098 			if (celTypeId == 12 || celTypeId == 102 || celTypeId == 111) // opacity cannot be extincted
1099 				magOp = static_cast<double>(obj->getVMagnitude(core));
1100 			else
1101 				magOp = static_cast<double>(obj->getVMagnitudeWithExtinction(core));
1102 
1103 			if (celTypeId==35 || (celTypeId==169 && obj->getDSOType()==Nebula::NebDn)) // Regions of the sky
1104 			{
1105 				passByBrightness = true;
1106 				magOp = 99.;
1107 			}
1108 			else
1109 				passByBrightness = (magOp <= mag);
1110 
1111 			if (obj->objectInDisplayedCatalog() && obj->objectInAllowedSizeRangeLimits() && passByBrightness && obj->isAboveRealHorizon(core))
1112 			{
1113 				coordStrings = getStringCoordinates(horizon ? obj->getAltAzPosAuto(core) : obj->getJ2000EquatorialPos(core), horizon, useSouthAzimuth, withDecimalDegree);
1114 
1115 				QString celObjName = obj->getNameI18n();
1116 				QString celObjId = obj->getDSODesignation();
1117 				QString elongStr;
1118 				if (celObjId.isEmpty())
1119 					dsoName = celObjName;
1120 				else if (celObjName.isEmpty())
1121 					dsoName = celObjId;
1122 				else
1123 					dsoName = QString("%1 (%2)").arg(celObjId, celObjName);
1124 
1125 				QString extra = QString::number(static_cast<double>(obj->getSurfaceBrightnessWithExtinction(core)), 'f', 2);
1126 				if (extra.toFloat() > 90.f)
1127 					extra = dash;
1128 
1129 				// Convert to arc-minutes the average angular size of deep-sky object
1130 				QString angularSize = QString::number(obj->getAngularSize(core) * 120., 'f', 3);
1131 				if (angularSize.toFloat() < 0.01f)
1132 					angularSize = dash;
1133 
1134 				QString sTransit = dash;
1135 				QString sMaxElevation = dash;
1136 				rts = obj->getRTSTime(core);
1137 				if (rts[1]>=0.)
1138 				{
1139 					sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1140 					if (withDecimalDegree)
1141 						sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(qSharedPointerCast<StelObject>(obj)), 5, false, true);
1142 					else
1143 						sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(qSharedPointerCast<StelObject>(obj)), 2);
1144 				}
1145 
1146 				angularDistance = obj->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core));
1147 				if (withDecimalDegree)
1148 					elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1149 				else
1150 					elongStr = StelUtils::radToDmsStr(angularDistance, true);
1151 
1152 				fillCelestialPositionTable(dsoName, coordStrings.first, coordStrings.second, magOp, angularSize, asToolTip, extra, mu, sTransit, sMaxElevation, elongStr, q_(obj->getTypeString()));
1153 			}
1154 		}
1155 	}
1156 	else if (celTypeId >= 200 && celTypeId <= 203)
1157 	{
1158 		QString distanceInfo = q_("Planetocentric distance");
1159 		if (core->getUseTopocentricCoordinates())
1160 			distanceInfo = q_("Topocentric distance");
1161 		QString distanceUM = qc_("AU", "distance, astronomical unit");
1162 		QString sToolTip = QString("%1, %2").arg(distanceInfo, distanceUM);
1163 		QString asToolTip = QString("%1, %2").arg(q_("Angular size (with rings, if any)"), q_("arc-min"));
1164 		Vec3d pos;
1165 		bool passByType;
1166 
1167 		QList<PlanetP> planets;
1168 		switch (celTypeId)
1169 		{
1170 			case 200:
1171 			case 203:
1172 				planets = solarSystem->getAllPlanets();
1173 				break;
1174 			case 201:
1175 			case 202:
1176 				planets = solarSystem->getAllMinorBodies();
1177 				break;
1178 		}
1179 
1180 		for (const auto& planet : qAsConst(planets))
1181 		{
1182 			passByType = false;
1183 
1184 			switch (celTypeId)
1185 			{
1186 				case 200:
1187 					if (planet->getPlanetType() != Planet::isUNDEFINED)
1188 						passByType = true;
1189 					break;
1190 				case 201:
1191 					if (planet->getPlanetType() == Planet::isComet)
1192 						passByType = true;
1193 					break;
1194 				case 202:
1195 				{
1196 					Planet::PlanetType ptype = planet->getPlanetType();
1197 					if (ptype == Planet::isAsteroid || ptype == Planet::isCubewano || ptype == Planet::isDwarfPlanet || ptype == Planet::isOCO || ptype == Planet::isPlutino || ptype == Planet::isSDO || ptype == Planet::isSednoid || ptype==Planet::isInterstellar)
1198 						passByType = true;
1199 					break;
1200 				}
1201 				case 203:
1202 					if (planet->getPlanetType() == Planet::isPlanet)
1203 						passByType = true;
1204 					break;
1205 			}
1206 
1207 			if (!planet->hasValidPositionalData(JD, Planet::PositionQuality::OrbitPlotting))
1208 				passByType = false;
1209 
1210 			if (passByType && planet != core->getCurrentPlanet() && static_cast<double>(planet->getVMagnitudeWithExtinction(core)) <= mag && planet->isAboveRealHorizon(core))
1211 			{
1212 				pos = planet->getJ2000EquatorialPos(core);
1213 
1214 				if (horizon)
1215 					coordStrings = getStringCoordinates(planet->getAltAzPosAuto(core), horizon, useSouthAzimuth, withDecimalDegree);
1216 				else
1217 					coordStrings = getStringCoordinates(pos, horizon, useSouthAzimuth, withDecimalDegree);
1218 
1219 				QString extra = QString::number(pos.length(), 'f', 5); // A.U.
1220 
1221 				// Convert to arc-seconds the angular size of Solar system object (with rings, if any)
1222 				QString angularSize = QString::number(planet->getAngularSize(core) * 120., 'f', 4);
1223 				if (angularSize.toFloat() < 1e-4f || planet->getPlanetType() == Planet::isComet)
1224 					angularSize = dash;
1225 
1226 				QString sTransit = dash;
1227 				QString sMaxElevation = dash;
1228 				QString elongStr;
1229 				rts = planet->getRTSTime(core);
1230 				if (rts[1]>=0.)
1231 				{
1232 					sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1233 					if (withDecimalDegree)
1234 						sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(qSharedPointerCast<StelObject>(planet)), 5, false, true);
1235 					else
1236 						sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(qSharedPointerCast<StelObject>(planet)), 2);
1237 				}
1238 
1239 				if (planet!=sun)
1240 				{
1241 					angularDistance = planet->getElongation(core->getObserverHeliocentricEclipticPos());
1242 					if (withDecimalDegree)
1243 						elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1244 					else
1245 						elongStr = StelUtils::radToDmsStr(angularDistance, true);
1246 				}
1247 				else
1248 					elongStr = dash;
1249 
1250 				fillCelestialPositionTable(planet->getNameI18n(), coordStrings.first, coordStrings.second, planet->getVMagnitudeWithExtinction(core), angularSize, asToolTip, extra, sToolTip, sTransit, sMaxElevation, elongStr, q_(planet->getPlanetTypeString()));
1251 			}
1252 		}
1253 	}
1254 	else
1255 	{
1256 		// stars
1257 		QString sType = q_("star");
1258 		QString commonName, sToolTip = "";
1259 		QList<StelACStarData> celestialObjects;
1260 		if (celTypeId == 170)
1261 		{
1262 			// double stars
1263 			celestialObjects = starMgr->getHipparcosDoubleStars();
1264 			sType = q_("double star");
1265 		}
1266 		else if (celTypeId == 171 || celTypeId == 173 || celTypeId == 174)
1267 		{
1268 			// variable stars
1269 			switch (celTypeId)
1270 			{
1271 				case 171:
1272 					celestialObjects = starMgr->getHipparcosVariableStars();
1273 					break;
1274 				case 173:
1275 					celestialObjects = starMgr->getHipparcosAlgolTypeStars();
1276 					break;
1277 				case 174:
1278 					celestialObjects = starMgr->getHipparcosClassicalCepheidsTypeStars();
1279 					break;
1280 			}
1281 			sType = q_("variable star");
1282 		}
1283 		else
1284 		{
1285 			// stars with high proper motion
1286 			celestialObjects = starMgr->getHipparcosHighPMStars();
1287 			sType = q_("star with high proper motion");
1288 		}
1289 
1290 		for (const auto& star : qAsConst(celestialObjects))
1291 		{
1292 			StelObjectP obj = star.firstKey();
1293 			QString extra, elongStr;
1294 			if (static_cast<double>(obj->getVMagnitudeWithExtinction(core)) <= mag && obj->isAboveRealHorizon(core))
1295 			{
1296 				if (horizon)
1297 					coordStrings = getStringCoordinates(obj->getAltAzPosAuto(core), horizon, useSouthAzimuth, withDecimalDegree);
1298 				else
1299 					coordStrings = getStringCoordinates(obj->getJ2000EquatorialPos(core), horizon, useSouthAzimuth, withDecimalDegree);
1300 
1301 				if (celTypeId == 170) // double stars
1302 				{
1303 					double wdsSep = static_cast<double>(star.value(obj));
1304 					extra = QString::number(wdsSep, 'f', 3); // arc-seconds
1305 					sToolTip = StelUtils::decDegToDmsStr(wdsSep / 3600.);
1306 				}
1307 				else if (celTypeId == 171 || celTypeId == 173 || celTypeId == 174) // variable stars
1308 				{
1309 					if (star.value(obj) > 0.f)
1310 						extra = QString::number(star.value(obj), 'f', 5); // days
1311 					else
1312 						extra = dash;
1313 				}
1314 				else	// stars with high proper motion
1315 					extra = QString::number(star.value(obj), 'f', 5); // "/yr
1316 
1317 				QString sTransit = dash;
1318 				QString sMaxElevation = dash;
1319 				rts = obj->getRTSTime(core);
1320 				if (rts[1]>=0.)
1321 				{
1322 					sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1323 					if (withDecimalDegree)
1324 						sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(obj), 5, false, true);
1325 					else
1326 						sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(obj), 2);
1327 				}
1328 
1329 				angularDistance = obj->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core));
1330 				if (withDecimalDegree)
1331 					elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1332 				else
1333 					elongStr = StelUtils::radToDmsStr(angularDistance, true);
1334 
1335 				commonName = obj->getNameI18n();
1336 				if (commonName.isEmpty())
1337 					commonName = obj->getID();
1338 
1339 				fillCelestialPositionTable(commonName, coordStrings.first, coordStrings.second, obj->getVMagnitudeWithExtinction(core), dash, "", extra, sToolTip, sTransit, sMaxElevation, elongStr, sType);
1340 			}
1341 		}
1342 		ui->celestialPositionsTreeWidget->hideColumn(CColumnAngularSize);
1343 	}
1344 
1345 	adjustCelestialPositionsColumns();
1346 	// sort-by-name
1347 	ui->celestialPositionsTreeWidget->sortItems(CColumnName, Qt::AscendingOrder);
1348 }
1349 
getStringCoordinates(const Vec3d coord,const bool horizontal,const bool southAzimuth,const bool decimalDegrees)1350 QPair<QString, QString> AstroCalcDialog::getStringCoordinates(const Vec3d coord, const bool horizontal, const bool southAzimuth, const bool decimalDegrees)
1351 {
1352 	double lng, lat;
1353 	QString lngStr, latStr;
1354 	StelUtils::rectToSphe(&lng, &lat, coord);
1355 	if (horizontal)
1356 	{
1357 		// Azimuth is counted in reverse sense. N is zero, E is 90 degrees
1358 		lng = (southAzimuth ? 2. : 3.) * M_PI - lng;
1359 		if (lng > M_PI * 2)
1360 			lng -= M_PI * 2;
1361 		if (decimalDegrees)
1362 		{
1363 			lngStr = StelUtils::radToDecDegStr(lng, 5, false, true);
1364 			latStr = StelUtils::radToDecDegStr(lat, 5);
1365 		}
1366 		else
1367 		{
1368 			lngStr = StelUtils::radToDmsStr(lng, true);
1369 			latStr = StelUtils::radToDmsStr(lat, true);
1370 		}
1371 	}
1372 	else
1373 	{
1374 		if (decimalDegrees)
1375 		{
1376 			lngStr = StelUtils::radToDecDegStr(lng, 5, false, true);
1377 			latStr = StelUtils::radToDecDegStr(lat, 5);
1378 		}
1379 		else
1380 		{
1381 			lngStr = StelUtils::radToHmsStr(lng);
1382 			latStr = StelUtils::radToDmsStr(lat, true);
1383 		}
1384 	}
1385 
1386 	return QPair<QString, QString> (lngStr, latStr);
1387 }
1388 
saveCelestialPositions()1389 void AstroCalcDialog::saveCelestialPositions()
1390 {
1391 	QString filter = q_("Microsoft Excel Open XML Spreadsheet");
1392 	filter.append(" (*.xlsx);;");
1393 	filter.append(q_("CSV (Comma delimited)"));
1394 	filter.append(" (*.csv)");
1395 	QString defaultFilter("(*.xlsx)");
1396 	QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
1397 							q_("Save celestial positions of objects as..."),
1398 							QDir::homePath() + "/positions.xlsx",
1399 							filter,
1400 							&defaultFilter);
1401 
1402 	if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
1403 		saveTableAsCSV(filePath, ui->celestialPositionsTreeWidget, positionsHeader);
1404 	else
1405 	{
1406 		int count = ui->celestialPositionsTreeWidget->topLevelItemCount();
1407 		int columns = positionsHeader.size();
1408 		int *width = new int[static_cast<unsigned int>(columns)];
1409 		QString sData;
1410 
1411 		QXlsx::Document xlsx;
1412 		xlsx.setDocumentProperty("title", q_("Celestial positions of objects"));
1413 		xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
1414 		xlsx.addSheet(ui->celestialCategoryComboBox->currentData(Qt::DisplayRole).toString(), AbstractSheet::ST_WorkSheet);
1415 
1416 		QXlsx::Format header;
1417 		header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1418 		header.setPatternBackgroundColor(Qt::yellow);
1419 		header.setBorderStyle(QXlsx::Format::BorderThin);
1420 		header.setBorderColor(Qt::black);
1421 		header.setFontBold(true);
1422 		for (int i = 0; i < columns; i++)
1423 		{
1424 			// Row 1: Names of columns
1425 			sData = positionsHeader.at(i).trimmed();
1426 			xlsx.write(1, i + 1, sData, header);
1427 			width[i] = sData.size();
1428 		}
1429 
1430 		QXlsx::Format data;
1431 		data.setHorizontalAlignment(QXlsx::Format::AlignRight);
1432 		for (int i = 0; i < count; i++)
1433 		{
1434 			for (int j = 0; j < columns; j++)
1435 			{
1436 				// Row 2 and next: the data
1437 				sData = ui->celestialPositionsTreeWidget->topLevelItem(i)->text(j).trimmed();
1438 				xlsx.write(i + 2, j + 1, sData, data);
1439 				int w = sData.size();
1440 				if (w > width[j])
1441 				{
1442 					width[j] = w;
1443 				}
1444 			}
1445 		}
1446 
1447 		for (int i = 0; i < columns; i++)
1448 		{
1449 			xlsx.setColumnWidth(i+1, width[i]+2);
1450 		}
1451 
1452 		delete[] width;
1453 
1454 		// Add the date and time info for celestial positions
1455 		xlsx.write(count + 2, 1, ui->celestialPositionsTimeLabel->text());
1456 		QXlsx::CellRange range = CellRange(count+2, 1, count+2, columns);
1457 		QXlsx::Format extraData;
1458 		extraData.setBorderStyle(QXlsx::Format::BorderThin);
1459 		extraData.setBorderColor(Qt::black);
1460 		extraData.setPatternBackgroundColor(Qt::yellow);
1461 		extraData.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1462 		xlsx.mergeCells(range, extraData);
1463 
1464 		xlsx.saveAs(filePath);
1465 	}
1466 }
1467 
selectCurrentCelestialPosition(const QModelIndex & modelIndex)1468 void AstroCalcDialog::selectCurrentCelestialPosition(const QModelIndex& modelIndex)
1469 {
1470 	// Find the object
1471 	QString nameI18n = modelIndex.sibling(modelIndex.row(), CColumnName).data().toString();
1472 	bool found = (objectMgr->findAndSelectI18n(nameI18n) || objectMgr->findAndSelect(nameI18n));
1473 
1474 	if (!found)
1475 	{
1476 		QStringList list = nameI18n.split("(");
1477 		if (list.count() > 0 && nameI18n.lastIndexOf("(") != 0 && nameI18n.lastIndexOf("/") < 0)
1478 			nameI18n = list.at(0).trimmed();
1479 
1480 		found = (objectMgr->findAndSelectI18n(nameI18n) || objectMgr->findAndSelect(nameI18n));
1481 	}
1482 
1483 	if (found)
1484 	{
1485 		const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1486 		if (!newSelected.empty())
1487 		{
1488 			mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
1489 			mvMgr->setFlagTracking(true);
1490 		}
1491 	}
1492 }
1493 
selectCurrentEphemeride(const QModelIndex & modelIndex)1494 void AstroCalcDialog::selectCurrentEphemeride(const QModelIndex& modelIndex)
1495 {
1496 	// Find the object
1497 	const QString name = modelIndex.sibling(modelIndex.row(), EphemerisCOName).data(Qt::UserRole).toString();
1498 	const double JD = modelIndex.sibling(modelIndex.row(), EphemerisDate).data(Qt::UserRole).toDouble();
1499 
1500 	if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
1501 	{
1502 		core->setJD(JD);
1503 		const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1504 		if (!newSelected.empty())
1505 		{
1506 			// Can't point to home planet
1507 			if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
1508 			{
1509 				mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
1510 				mvMgr->setFlagTracking(true);
1511 			}
1512 			else
1513 			{
1514 				GETSTELMODULE(StelObjectMgr)->unSelect();
1515 			}
1516 		}
1517 	}
1518 }
1519 
setEphemerisHeaderNames()1520 void AstroCalcDialog::setEphemerisHeaderNames()
1521 {
1522 	const bool horizontalCoords = ui->ephemerisHorizontalCoordinatesCheckBox->isChecked();
1523 
1524 	ephemerisHeader.clear();
1525 	ephemerisHeader << q_("Name");
1526 	ephemerisHeader << q_("Date and Time");
1527 	if (horizontalCoords)
1528 	{
1529 		// TRANSLATORS: azimuth
1530 		ephemerisHeader << q_("Azimuth");
1531 		// TRANSLATORS: altitude
1532 		ephemerisHeader << q_("Altitude");
1533 	}
1534 	else
1535 	{
1536 		// TRANSLATORS: right ascension
1537 		ephemerisHeader << q_("RA (J2000)");
1538 		// TRANSLATORS: declination
1539 		ephemerisHeader << q_("Dec (J2000)");
1540 	}
1541 	// TRANSLATORS: magnitude
1542 	ephemerisHeader << q_("Mag.");
1543 	// TRANSLATORS: phase
1544 	ephemerisHeader << q_("Phase");
1545 	// TRANSLATORS: distance, AU
1546 	ephemerisHeader << QString("%1, %2").arg(q_("Dist."), qc_("AU", "distance, astronomical unit"));
1547 	// TRANSLATORS: elongation
1548 	ephemerisHeader << q_("Elong.");
1549 	ui->ephemerisTreeWidget->setHeaderLabels(ephemerisHeader);
1550 
1551 	// adjust the column width
1552 	for (int i = 0; i < EphemerisCount; ++i)
1553 	{
1554 		ui->ephemerisTreeWidget->resizeColumnToContents(i);
1555 	}
1556 }
1557 
initListEphemeris()1558 void AstroCalcDialog::initListEphemeris()
1559 {
1560 	ui->ephemerisTreeWidget->clear();
1561 	ui->ephemerisTreeWidget->setColumnCount(EphemerisCount);
1562 	setEphemerisHeaderNames();
1563 	ui->ephemerisTreeWidget->header()->setSectionsMovable(false);
1564 	ui->ephemerisTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
1565 }
1566 
reGenerateEphemeris()1567 void AstroCalcDialog::reGenerateEphemeris()
1568 {
1569 	reGenerateEphemeris(true);
1570 }
1571 
reGenerateEphemeris(bool withSelection)1572 void AstroCalcDialog::reGenerateEphemeris(bool withSelection)
1573 {
1574 	if (EphemerisList.size() > 0)
1575 	{
1576 		int row = ui->ephemerisTreeWidget->currentIndex().row();
1577 		generateEphemeris(); // Update list of ephemeris
1578 		if (row>0 && withSelection)
1579 		{
1580 			ui->ephemerisTreeWidget->setCurrentItem(ui->ephemerisTreeWidget->topLevelItem(row), 0, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1581 			ui->ephemerisTreeWidget->setFocus();
1582 		}
1583 	}
1584 	else
1585 		initListEphemeris(); // Just update headers
1586 }
1587 
generateEphemeris()1588 void AstroCalcDialog::generateEphemeris()
1589 {
1590 	const QString currentPlanet = ui->celestialBodyComboBox->currentData(Qt::UserRole).toString();
1591 	const QString secondaryPlanet = ui->secondaryCelestialBodyComboBox->currentData(Qt::UserRole).toString();
1592 	const QString distanceInfo = (core->getUseTopocentricCoordinates() ? q_("Topocentric distance") : q_("Planetocentric distance"));
1593 	const QString distanceUM = qc_("AU", "distance, astronomical unit");
1594 	QString englishName, nameI18n, elongStr = "", phaseStr = "";
1595 	const bool useHorizontalCoords = ui->ephemerisHorizontalCoordinatesCheckBox->isChecked();
1596 	const bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
1597 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1598 
1599 	DisplayedPositionIndex = -1; // deselect an ephemeris marker
1600 	initListEphemeris();
1601 
1602 	if (currentPlanet.isEmpty()) // avoid crash
1603 		return;
1604 
1605 	int idxRow = 0, colorIndex = 0;
1606 	double solarDay = 1.0, siderealDay = 1.0, siderealYear = 365.256363004; // days
1607 	const PlanetP& cplanet = core->getCurrentPlanet();
1608 	const PlanetP& sun = solarSystem->getSun();
1609 	if (!cplanet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1610 	{
1611 		if (cplanet==solarSystem->getEarth())
1612 			solarDay = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1613 		else
1614 			solarDay = cplanet->getMeanSolarDay();
1615 		siderealDay = cplanet->getSiderealDay();
1616 		siderealYear = cplanet->getSiderealPeriod();
1617 	}
1618 	const QMap<int, double>timeStepMap = {
1619 		{ 0, getCustomTimeStep() },		// custom time step
1620 		{ 1, 10. * StelCore::JD_MINUTE },
1621 		{ 2, 30. * StelCore::JD_MINUTE },
1622 		{ 3, StelCore::JD_HOUR },
1623 		{ 4, 6. * StelCore::JD_HOUR },
1624 		{ 5, 12. * StelCore::JD_HOUR },
1625 		{ 6, solarDay },
1626 		{ 7, 5. * solarDay },
1627 		{ 8, 10. * solarDay },
1628 		{ 9, 15. * solarDay },
1629 		{10, 30. * solarDay },
1630 		{11, 60. * solarDay },
1631 		{12, StelCore::JD_DAY },
1632 		{13, 5. * StelCore::JD_DAY },
1633 		{14, 10. * StelCore::JD_DAY },
1634 		{15, 15. * StelCore::JD_DAY },
1635 		{16, 30. * StelCore::JD_DAY },
1636 		{17, 60. * StelCore::JD_DAY },
1637 		{18, siderealDay },
1638 		{19, 5. * siderealDay },
1639 		{20, 10. * siderealDay },
1640 		{21, 15. * siderealDay },
1641 		{22, 30. * siderealDay },
1642 		{23, 60. * siderealDay },
1643 		{24, 100. * solarDay },
1644 		{25, 100. * siderealDay },
1645 		{26, 100. * StelCore::JD_DAY },
1646 		{27, siderealYear*solarDay },
1647 		{28, 365.25*solarDay },			// 1 Julian year
1648 		{29, 365.2568983*solarDay },		// 1 Gaussian year
1649 		{30, 29.530588853*solarDay },	// 1 synodic month
1650 		{31, 27.212220817*solarDay },	// 1 draconic month
1651 		{32, 27.321582241*solarDay },	// 1 mean tropical month
1652 		{33, 27.554549878*solarDay },	// 1 anomalistic month
1653 		{34, 365.259636*solarDay },		// 1 anomalistic year
1654 		{35, 6585.321314219*solarDay },	// 1 saros (223 synodic months)
1655 		{36, 500. * siderealDay },
1656 		{37, 500. * solarDay },
1657 		{38, StelCore::JD_MINUTE }
1658 	};
1659 	double currentStep = timeStepMap.value(ui->ephemerisStepComboBox->currentData().toInt(), solarDay);
1660 
1661 	const double currentJD = core->getJD(); // save current JD
1662 	double firstJD = StelUtils::qDateTimeToJd(ui->dateFromDateTimeEdit->dateTime());
1663 	firstJD -= core->getUTCOffset(firstJD) / 24.;
1664 	double secondJD = StelUtils::qDateTimeToJd(ui->dateToDateTimeEdit->dateTime());
1665 	secondJD -= core->getUTCOffset(secondJD) / 24.;
1666 	const int elements = static_cast<int>((secondJD - firstJD) / currentStep);
1667 	EphemerisList.clear();
1668 	const bool allNakedEyePlanets = (ui->allNakedEyePlanetsCheckBox->isChecked() && cplanet==solarSystem->getEarth());
1669 
1670 	QList<PlanetP> celestialObjects;
1671 	Q_ASSERT(celestialObjects.isEmpty());
1672 
1673 	int n = 1;
1674 	if (allNakedEyePlanets)
1675 	{
1676 		const QStringList planets = { "Mercury", "Venus", "Mars", "Jupiter", "Saturn"};
1677 		for (auto planet: planets)
1678 			celestialObjects.append(solarSystem->searchByEnglishName(planet));
1679 		n = planets.count();
1680 	}
1681 	else if (secondaryPlanet!="none")
1682 	{
1683 		celestialObjects.append(solarSystem->searchByEnglishName(currentPlanet));
1684 		celestialObjects.append(solarSystem->searchByEnglishName(secondaryPlanet));
1685 		n = 2;
1686 	}
1687 	else
1688 		celestialObjects.append(solarSystem->searchByEnglishName(currentPlanet));
1689 
1690 	EphemerisList.reserve(elements*n);
1691 	propMgr->setStelPropertyValue("SolarSystem.ephemerisDataLimit", n);
1692 
1693 	for (auto obj: celestialObjects)
1694 	{
1695 		englishName = obj->getEnglishName();
1696 		nameI18n = obj->getNameI18n();
1697 
1698 		if (allNakedEyePlanets && cplanet==solarSystem->getEarth())
1699 		{
1700 			if (englishName.contains("Mercury", Qt::CaseInsensitive))
1701 				colorIndex = 2;
1702 			else if (englishName.contains("Venus", Qt::CaseInsensitive))
1703 				colorIndex = 3;
1704 			else if (englishName.contains("Mars", Qt::CaseInsensitive))
1705 				colorIndex = 4;
1706 			else if (englishName.contains("Jupiter", Qt::CaseInsensitive))
1707 				colorIndex = 5;
1708 			else if (englishName.contains("Saturn", Qt::CaseInsensitive))
1709 				colorIndex = 6;
1710 			else
1711 				colorIndex = 0;
1712 		}
1713 		else if (secondaryPlanet!="none")
1714 		{
1715 			colorIndex = (secondaryPlanet==englishName ? 1 : 0);
1716 		}
1717 
1718 		if (obj == solarSystem->getSun())
1719 		{
1720 			phaseStr = dash;
1721 			elongStr = dash;
1722 		}
1723 
1724 		for (int i = 0; i <= elements; i++)
1725 		{
1726 			Vec3d pos, sunPos;
1727 			double JD = firstJD + i * currentStep;
1728 			core->setJD(JD);
1729 			core->update(0); // force update to get new coordinates
1730 
1731 			if (!obj->hasValidPositionalData(JD, Planet::PositionQuality::OrbitPlotting))
1732 				continue;
1733 
1734 			if (useHorizontalCoords)
1735 			{
1736 				pos = obj->getAltAzPosAuto(core);
1737 				sunPos = sun->getAltAzPosAuto(core);
1738 			}
1739 			else
1740 			{
1741 				pos = obj->getJ2000EquatorialPos(core);
1742 				sunPos = sun->getJ2000EquatorialPos(core);
1743 			}
1744 			QPair<QString, QString> coordStrings = getStringCoordinates(pos, useHorizontalCoords, useSouthAzimuth, withDecimalDegree);
1745 
1746 			Ephemeris item;
1747 			item.coord = pos;
1748 			item.sunCoord = sunPos;
1749 			item.colorIndex = colorIndex;
1750 			item.objDate = JD;
1751 			item.magnitude = obj->getVMagnitudeWithExtinction(core);
1752 			item.isComet = obj->getPlanetType()==Planet::isComet;
1753 			EphemerisList.append(item);
1754 
1755 			Vec3d observerHelioPos = core->getObserverHeliocentricEclipticPos();
1756 			if (phaseStr != dash)
1757 				phaseStr = QString("%1%").arg(QString::number(obj->getPhase(observerHelioPos) * 100, 'f', 2));
1758 
1759 			if (elongStr != dash)
1760 			{
1761 				if (withDecimalDegree)
1762 					elongStr = StelUtils::radToDecDegStr(obj->getElongation(observerHelioPos), 5, false, true);
1763 				else
1764 					elongStr = StelUtils::radToDmsStr(obj->getElongation(observerHelioPos), true);
1765 			}
1766 
1767 			ACEphemTreeWidgetItem* treeItem = new ACEphemTreeWidgetItem(ui->ephemerisTreeWidget);
1768 			treeItem->setText(EphemerisCOName, nameI18n);
1769 			treeItem->setData(EphemerisCOName, Qt::UserRole, englishName);
1770 			treeItem->setText(EphemerisDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))); // local date and time
1771 			treeItem->setData(EphemerisDate, Qt::UserRole, JD);
1772 			treeItem->setText(EphemerisRA, coordStrings.first);
1773 			treeItem->setData(EphemerisRA, Qt::UserRole, idxRow);
1774 			treeItem->setTextAlignment(EphemerisRA, Qt::AlignRight);
1775 			treeItem->setText(EphemerisDec, coordStrings.second);
1776 			treeItem->setTextAlignment(EphemerisDec, Qt::AlignRight);
1777 			treeItem->setText(EphemerisMagnitude, QString::number(obj->getVMagnitudeWithExtinction(core), 'f', 2));
1778 			treeItem->setTextAlignment(EphemerisMagnitude, Qt::AlignRight);
1779 			treeItem->setText(EphemerisPhase, phaseStr);
1780 			treeItem->setTextAlignment(EphemerisPhase, Qt::AlignRight);
1781 			treeItem->setText(EphemerisDistance, QString::number(obj->getJ2000EquatorialPos(core).length(), 'f', 6));
1782 			treeItem->setTextAlignment(EphemerisDistance, Qt::AlignRight);
1783 			treeItem->setToolTip(EphemerisDistance, QString("%1, %2").arg(distanceInfo, distanceUM));
1784 			treeItem->setText(EphemerisElongation, elongStr);
1785 			treeItem->setTextAlignment(EphemerisElongation, Qt::AlignRight);
1786 
1787 			idxRow++;
1788 		}
1789 	}
1790 	core->setJD(currentJD); // restore time
1791 
1792 	// adjust the column width
1793 	for (int i = 0; i < EphemerisCount; ++i)
1794 	{
1795 		ui->ephemerisTreeWidget->resizeColumnToContents(i);
1796 	}
1797 
1798 	// sort-by-date
1799 	ui->ephemerisTreeWidget->sortItems(EphemerisDate, Qt::AscendingOrder);
1800 
1801 	emit solarSystem->requestEphemerisVisualization();
1802 }
1803 
getCustomTimeStep()1804 double AstroCalcDialog::getCustomTimeStep()
1805 {
1806 	double solarDay = 1.0, siderealDay = 1.0, siderealYear = 365.256363004; // days
1807 	const PlanetP& cplanet = core->getCurrentPlanet();
1808 	if (!cplanet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1809 	{
1810 		if (cplanet==solarSystem->getEarth())
1811 			solarDay = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1812 		else
1813 			solarDay = cplanet->getMeanSolarDay();
1814 		siderealDay = cplanet->getSiderealDay();
1815 		siderealYear = cplanet->getSiderealPeriod();
1816 	}
1817 	const double timeStep = conf->value("astrocalc/custom_time_step", 1.0).toDouble();
1818 	// NOTE: Sync units with AstroCalcCustomStepsDialog::populateUnitMeasurementsList()!
1819 	const int customTimeStepKey=conf->value("astrocalc/custom_time_step_unit", 3).toInt();
1820 	const QMap<int, double>customTimeStepMap={
1821 			{ 1, StelCore::JD_MINUTE},	// minutes
1822 			{ 2, StelCore::JD_HOUR},	// hours
1823 			{ 3, solarDay},			// solar days
1824 			{ 4, siderealDay},		// sidereal days
1825 			{ 5, StelCore::JD_DAY},		// Julian days
1826 			{ 6, 29.530588853*solarDay},	// synodic months
1827 			{ 7, 27.212220817*solarDay},	// draconic months
1828 			{ 8, 27.321582241*solarDay},	// mean tropical months
1829 			{ 9, 27.554549878*solarDay},	// anomalistic months
1830 			{10, siderealYear},		// sidereal years
1831 			{11, 365.25*solarDay},		// Julian years
1832 			{12, 365.2568983*solarDay},	// Gaussian years
1833 			{13, 365.259636*solarDay},	// Anomalistic years
1834 			{14, 6585.321314219*solarDay}};	// 1 saros (223 synodic months)
1835 	return timeStep*customTimeStepMap.value(customTimeStepKey);
1836 }
1837 
saveEphemeris()1838 void AstroCalcDialog::saveEphemeris()
1839 {
1840 	QString filter = q_("Microsoft Excel Open XML Spreadsheet");
1841 	filter.append(" (*.xlsx);;");
1842 	filter.append(q_("CSV (Comma delimited)"));
1843 	filter.append(" (*.csv)");
1844 	QString defaultFilter("(*.xlsx)");
1845 	QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
1846 							q_("Save calculated ephemeris as..."),
1847 							QDir::homePath() + "/ephemeris.xlsx",
1848 							filter,
1849 							&defaultFilter);
1850 
1851 	if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
1852 		saveTableAsCSV(filePath, ui->ephemerisTreeWidget, ephemerisHeader);
1853 	else
1854 	{
1855 		int count = ui->ephemerisTreeWidget->topLevelItemCount();
1856 		int columns = ephemerisHeader.size();
1857 		int *width = new int[static_cast<unsigned int>(columns)];
1858 		QString sData;
1859 
1860 		QXlsx::Document xlsx;
1861 		xlsx.setDocumentProperty("title", q_("Ephemeris"));
1862 		xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
1863 		xlsx.addSheet(ui->celestialBodyComboBox->currentData(Qt::DisplayRole).toString(), AbstractSheet::ST_WorkSheet);
1864 
1865 		QXlsx::Format header;
1866 		header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1867 		header.setPatternBackgroundColor(Qt::yellow);
1868 		header.setBorderStyle(QXlsx::Format::BorderThin);
1869 		header.setBorderColor(Qt::black);
1870 		header.setFontBold(true);
1871 		for (int i = 0; i < columns; i++)
1872 		{
1873 			// Row 1: Names of columns
1874 			sData = ephemerisHeader.at(i).trimmed();
1875 			xlsx.write(1, i + 1, sData, header);
1876 			width[i] = sData.size();
1877 		}
1878 
1879 		QXlsx::Format data;
1880 		data.setHorizontalAlignment(QXlsx::Format::AlignRight);
1881 		for (int i = 0; i < count; i++)
1882 		{
1883 			for (int j = 0; j < columns; j++)
1884 			{
1885 				// Row 2 and next: the data
1886 				sData = ui->ephemerisTreeWidget->topLevelItem(i)->text(j).trimmed();
1887 				xlsx.write(i + 2, j + 1, sData, data);
1888 				int w = sData.size();
1889 				if (w > width[j])
1890 				{
1891 					width[j] = w;
1892 				}
1893 			}
1894 		}
1895 
1896 		for (int i = 0; i < columns; i++)
1897 		{
1898 			xlsx.setColumnWidth(i+1, width[i]+2);
1899 		}
1900 
1901 		delete[] width;
1902 		xlsx.saveAs(filePath);
1903 	}
1904 }
1905 
cleanupEphemeris()1906 void AstroCalcDialog::cleanupEphemeris()
1907 {
1908 	EphemerisList.clear();
1909 	ui->ephemerisTreeWidget->clear();
1910 }
1911 
setTransitHeaderNames()1912 void AstroCalcDialog::setTransitHeaderNames()
1913 {
1914 	transitHeader.clear();
1915 	transitHeader << q_("Name");
1916 	transitHeader << q_("Date and Time");
1917 	// TRANSLATORS: altitude
1918 	transitHeader << q_("Altitude");
1919 	// TRANSLATORS: magnitude
1920 	transitHeader << q_("Mag.");
1921 	transitHeader << q_("Solar Elongation");
1922 	transitHeader << q_("Lunar Elongation");
1923 	ui->transitTreeWidget->setHeaderLabels(transitHeader);
1924 
1925 	// adjust the column width
1926 	for (int i = 0; i < TransitCount; ++i)
1927 	{
1928 		ui->transitTreeWidget->resizeColumnToContents(i);
1929 	}
1930 }
1931 
initListTransit()1932 void AstroCalcDialog::initListTransit()
1933 {
1934 	ui->transitTreeWidget->clear();
1935 	ui->transitTreeWidget->setColumnCount(TransitCount);
1936 	setTransitHeaderNames();
1937 	ui->transitTreeWidget->header()->setSectionsMovable(false);
1938 	ui->transitTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
1939 }
1940 
generateTransits()1941 void AstroCalcDialog::generateTransits()
1942 {
1943 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
1944 	if (!selectedObjects.isEmpty())
1945 	{
1946 		QString name, englishName;
1947 		StelObjectP selectedObject = selectedObjects[0];
1948 		name = ui->transitCelestialBodyNameLabel->text();
1949 		selectedObject->getEnglishName().isEmpty() ? englishName = name : englishName = selectedObject->getEnglishName();
1950 		//const bool isPlanet = (selectedObject->getType() == "Planet");
1951 
1952 		if (!name.isEmpty()) // OK, let's calculate!
1953 		{
1954 			const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1955 
1956 			initListTransit();
1957 
1958 			double currentStep = 1.0;
1959 			const PlanetP& planet = core->getCurrentPlanet();
1960 			const PlanetP sun = solarSystem->getSun();
1961 			const PlanetP moon = solarSystem->getMoon();
1962 			const PlanetP earth = solarSystem->getEarth();
1963 			if (!planet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1964 			{
1965 				if (planet==earth)
1966 					currentStep = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1967 				else
1968 					currentStep = planet->getMeanSolarDay();
1969 			}
1970 
1971 			const double currentJD = core->getJD();   // save current JD
1972 			double startJD = StelUtils::qDateTimeToJd(QDateTime(ui->transitFromDateEdit->date()));
1973 			double stopJD = StelUtils::qDateTimeToJd(QDateTime(ui->transitToDateEdit->date()));
1974 			startJD = startJD - core->getUTCOffset(startJD) / 24.;
1975 			stopJD = stopJD - core->getUTCOffset(stopJD) / 24.;
1976 			int elements = static_cast<int>((stopJD - startJD) / currentStep);
1977 			double JD, /*UTCshift,*/ az, alt;
1978 			float magnitude;
1979 			QString altStr, magStr, elongSStr = dash, elongLStr =dash;
1980 			for (int i = 0; i <= elements; i++)
1981 			{
1982 				JD = startJD + i * currentStep;
1983 				core->setJD(JD);
1984 				core->update(0); // force update to get new coordinates
1985 				// UTCshift = core->getUTCOffset(JD) / 24.; // Fix DST shift...
1986 				Vec4d rts = selectedObject->getRTSTime(core);
1987 				JD = rts[1]; // static_cast<int>(JD) + 0.5 + rts[1]/24. - UTCshift;
1988 				core->setJD(JD);
1989 				core->update(0); // force update to get new coordinates
1990 
1991 				StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
1992 				if (withDecimalDegree)
1993 				{
1994 					altStr = StelUtils::radToDecDegStr(alt, 5, false, true);
1995 					elongSStr = (selectedObject==sun) ?                   dash : StelUtils::radToDecDegStr(selectedObject->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core)), 5, false, true);
1996 					elongLStr = (selectedObject==moon && planet==earth) ? dash : StelUtils::radToDecDegStr(selectedObject->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core)), 5, false, true);
1997 				}
1998 				else
1999 				{
2000 					altStr = StelUtils::radToDmsStr(alt, true);
2001 					elongSStr = (selectedObject==sun)                   ? dash : StelUtils::radToDmsStr(selectedObject->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core)), true);
2002 					elongLStr = (selectedObject==moon && planet==earth) ? dash : StelUtils::radToDmsStr(selectedObject->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core)), true);
2003 				}
2004 				magnitude = selectedObject->getVMagnitudeWithExtinction(core);
2005 				magStr = (magnitude > 50.f || selectedObject->getEnglishName().contains("marker", Qt::CaseInsensitive)? dash : QString::number(magnitude, 'f', 2));
2006 
2007 				ACTransitTreeWidgetItem* treeItem = new ACTransitTreeWidgetItem(ui->transitTreeWidget);
2008 				treeItem->setText(TransitCOName, name);
2009 				treeItem->setData(TransitCOName, Qt::UserRole, englishName);
2010 				treeItem->setText(TransitDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))); // local date and time
2011 				treeItem->setData(TransitDate, Qt::UserRole, JD);
2012 				treeItem->setText(TransitAltitude, altStr);
2013 				treeItem->setTextAlignment(TransitAltitude, Qt::AlignRight);
2014 				treeItem->setText(TransitMagnitude, magStr);
2015 				treeItem->setTextAlignment(TransitMagnitude, Qt::AlignRight);
2016 				treeItem->setText(TransitElongation, elongSStr);
2017 				treeItem->setTextAlignment(TransitElongation, Qt::AlignRight);
2018 				treeItem->setText(TransitAngularDistance, elongLStr);
2019 				treeItem->setTextAlignment(TransitAngularDistance, Qt::AlignRight);
2020 			}
2021 			core->setJD(currentJD);
2022 
2023 			// adjust the column width
2024 			for (int i = 0; i < TransitCount; ++i)
2025 			{
2026 				ui->transitTreeWidget->resizeColumnToContents(i);
2027 			}
2028 
2029 			// sort-by-date
2030 			ui->transitTreeWidget->sortItems(TransitDate, Qt::AscendingOrder);
2031 		}
2032 		else
2033 			cleanupTransits();
2034 	}
2035 }
2036 
cleanupTransits()2037 void AstroCalcDialog::cleanupTransits()
2038 {
2039 	ui->transitTreeWidget->clear();
2040 }
2041 
selectCurrentTransit(const QModelIndex & modelIndex)2042 void AstroCalcDialog::selectCurrentTransit(const QModelIndex& modelIndex)
2043 {
2044 	// Find the object
2045 	QString name = modelIndex.sibling(modelIndex.row(), TransitCOName).data(Qt::UserRole).toString();
2046 	double JD = modelIndex.sibling(modelIndex.row(), TransitDate).data(Qt::UserRole).toDouble();
2047 
2048 	if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
2049 	{
2050 		core->setJD(JD);
2051 		const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
2052 		if (!newSelected.empty())
2053 		{
2054 			// Can't point to home planet
2055 			if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
2056 			{
2057 				mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
2058 				mvMgr->setFlagTracking(true);
2059 			}
2060 			else
2061 			{
2062 				GETSTELMODULE(StelObjectMgr)->unSelect();
2063 			}
2064 		}
2065 	}
2066 }
2067 
setTransitCelestialBodyName()2068 void AstroCalcDialog::setTransitCelestialBodyName()
2069 {
2070 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
2071 	QString name;
2072 	if (!selectedObjects.isEmpty())
2073 	{
2074 		StelObjectP selectedObject = selectedObjects[0];
2075 		name = selectedObject->getNameI18n();
2076 		if (name.isEmpty())
2077 		{
2078 			QString otype = selectedObject->getType();
2079 			if (otype == "Nebula")
2080 			{
2081 				name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
2082 				if (name.isEmpty())
2083 					name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
2084 			}
2085 			if (otype == "Star" || otype=="Pulsar")
2086 				name = selectedObject->getID();
2087 		}
2088 		if (selectedObject->getType()=="Satellite")
2089 			name = QString();
2090 	}
2091 	ui->transitCelestialBodyNameLabel->setText(name);
2092 }
2093 
saveTransits()2094 void AstroCalcDialog::saveTransits()
2095 {
2096 	QString filter = q_("Microsoft Excel Open XML Spreadsheet");
2097 	filter.append(" (*.xlsx);;");
2098 	filter.append(q_("CSV (Comma delimited)"));
2099 	filter.append(" (*.csv)");
2100 	QString defaultFilter("(*.xlsx)");
2101 	QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
2102 							q_("Save calculated transits as..."),
2103 							QDir::homePath() + "/transits.xlsx",
2104 							filter,
2105 							&defaultFilter);
2106 
2107 	if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
2108 		saveTableAsCSV(filePath, ui->transitTreeWidget, ephemerisHeader);
2109 	else
2110 	{
2111 		int count = ui->transitTreeWidget->topLevelItemCount();
2112 		int columns = transitHeader.size();
2113 		int *width = new int[static_cast<unsigned int>(columns)];
2114 		QString sData;
2115 
2116 		QXlsx::Document xlsx;
2117 		xlsx.setDocumentProperty("title", q_("Transits"));
2118 		xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
2119 		xlsx.addSheet(ui->transitCelestialBodyNameLabel->text(), AbstractSheet::ST_WorkSheet);
2120 
2121 		QXlsx::Format header;
2122 		header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
2123 		header.setPatternBackgroundColor(Qt::yellow);
2124 		header.setBorderStyle(QXlsx::Format::BorderThin);
2125 		header.setBorderColor(Qt::black);
2126 		header.setFontBold(true);
2127 		for (int i = 0; i < columns; i++)
2128 		{
2129 			// Row 1: Names of columns
2130 			sData = transitHeader.at(i).trimmed();
2131 			xlsx.write(1, i + 1, sData, header);
2132 			width[i] = sData.size();
2133 		}
2134 
2135 		QXlsx::Format data;
2136 		data.setHorizontalAlignment(QXlsx::Format::AlignRight);
2137 		for (int i = 0; i < count; i++)
2138 		{
2139 			for (int j = 0; j < columns; j++)
2140 			{
2141 				// Row 2 and next: the data
2142 				sData = ui->transitTreeWidget->topLevelItem(i)->text(j).trimmed();
2143 				xlsx.write(i + 2, j + 1, sData, data);
2144 				int w = sData.size();
2145 				if (w > width[j])
2146 				{
2147 					width[j] = w;
2148 				}
2149 			}
2150 		}
2151 
2152 		for (int i = 0; i < columns; i++)
2153 		{
2154 			xlsx.setColumnWidth(i+1, width[i]+2);
2155 		}
2156 
2157 		delete[] width;
2158 		xlsx.saveAs(filePath);
2159 	}
2160 }
2161 
populateCelestialBodyList()2162 void AstroCalcDialog::populateCelestialBodyList()
2163 {
2164 	Q_ASSERT(ui->celestialBodyComboBox);
2165 	Q_ASSERT(ui->secondaryCelestialBodyComboBox);
2166 	Q_ASSERT(ui->graphsCelestialBodyComboBox);
2167 	Q_ASSERT(ui->firstCelestialBodyComboBox);
2168 	Q_ASSERT(ui->secondCelestialBodyComboBox);
2169 
2170 	QComboBox* planets = ui->celestialBodyComboBox;
2171 	QComboBox* planets2 = ui->secondaryCelestialBodyComboBox;
2172 	QComboBox* graphsp = ui->graphsCelestialBodyComboBox;
2173 	QComboBox* firstCB = ui->firstCelestialBodyComboBox;
2174 	QComboBox* secondCB = ui->secondCelestialBodyComboBox;
2175 
2176 	QList<PlanetP> ss = solarSystem->getAllPlanets();
2177 
2178 	// Save the current selection to be restored later
2179 	planets->blockSignals(true);
2180 	int indexP = planets->currentIndex();
2181 	QVariant selectedPlanetId = planets->itemData(indexP);
2182 	planets->clear();
2183 
2184 	planets2->blockSignals(true);
2185 	int indexP2 = planets2->currentIndex();
2186 	QVariant selectedPlanet2Id = planets2->itemData(indexP2);
2187 	planets2->clear();
2188 
2189 	graphsp->blockSignals(true);
2190 	int indexG = graphsp->currentIndex();
2191 	QVariant selectedGraphsPId = graphsp->itemData(indexG);
2192 	graphsp->clear();
2193 
2194 	firstCB->blockSignals(true);
2195 	int indexFCB = firstCB->currentIndex();
2196 	QVariant selectedFirstCelestialBodyId = firstCB->itemData(indexFCB);
2197 	firstCB->clear();
2198 
2199 	secondCB->blockSignals(true);
2200 	int indexSCB = secondCB->currentIndex();
2201 	QVariant selectedSecondCelestialBodyId = secondCB->itemData(indexSCB);
2202 	secondCB->clear();
2203 
2204 	// For each planet, display the localized name and store the original as user
2205 	// data. Unfortunately, there's no other way to do this than with a cycle.
2206 	for (const auto& p : ss)
2207 	{
2208 		if (!p->getEnglishName().contains("Observer", Qt::CaseInsensitive))
2209 		{
2210 			if (p->getEnglishName() != core->getCurrentPlanet()->getEnglishName())
2211 			{
2212 				// Let's exclude moons from list of celestial body for ephemeris tool (except the moons of current planet)
2213 				if (p->getPlanetType()==Planet::isMoon && p->getParent()==core->getCurrentPlanet())
2214 				{
2215 					planets->addItem(p->getNameI18n(), p->getEnglishName());
2216 					planets2->addItem(p->getNameI18n(), p->getEnglishName());
2217 				}
2218 				if (p->getPlanetType()!=Planet::isMoon)
2219 				{
2220 					planets->addItem(p->getNameI18n(), p->getEnglishName());
2221 					planets2->addItem(p->getNameI18n(), p->getEnglishName());
2222 				}
2223 				graphsp->addItem(p->getNameI18n(), p->getEnglishName());
2224 			}
2225 			firstCB->addItem(p->getNameI18n(), p->getEnglishName());
2226 			secondCB->addItem(p->getNameI18n(), p->getEnglishName());
2227 		}
2228 	}
2229 	planets2->addItem(dash, "none");
2230 	// Restore the selection
2231 	indexP = planets->findData(selectedPlanetId, Qt::UserRole, Qt::MatchCaseSensitive);
2232 	if (indexP < 0)
2233 	{
2234 		indexP = planets->findData(conf->value("astrocalc/ephemeris_celestial_body", "Moon").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2235 		if (indexP<0)
2236 			indexP = 0;
2237 	}
2238 	planets->setCurrentIndex(indexP);
2239 	planets->model()->sort(0);
2240 
2241 	// Restore the selection
2242 	indexP2 = planets2->findData(selectedPlanet2Id, Qt::UserRole, Qt::MatchCaseSensitive);
2243 	if (indexP2 < 0)
2244 	{
2245 		indexP2 = planets2->findData(conf->value("astrocalc/ephemeris_second_celestial_body", "none").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2246 		if (indexP2<0)
2247 			indexP2 = 0;
2248 	}
2249 	planets2->setCurrentIndex(indexP2);
2250 	planets2->model()->sort(0);
2251 
2252 	indexG = graphsp->findData(selectedGraphsPId, Qt::UserRole, Qt::MatchCaseSensitive);
2253 	if (indexG < 0)
2254 	{
2255 		indexG = graphsp->findData(conf->value("astrocalc/graphs_celestial_body", "Moon").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2256 		if (indexG<0)
2257 			indexG = 0;
2258 	}
2259 	graphsp->setCurrentIndex(indexG);
2260 	graphsp->model()->sort(0);
2261 
2262 	indexFCB = firstCB->findData(selectedFirstCelestialBodyId, Qt::UserRole, Qt::MatchCaseSensitive);
2263 	if (indexFCB < 0)
2264 	{
2265 		indexFCB = firstCB->findData(conf->value("astrocalc/first_celestial_body", "Sun").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2266 		if (indexFCB<0)
2267 			indexFCB = 0;
2268 	}
2269 	firstCB->setCurrentIndex(indexFCB);
2270 	firstCB->model()->sort(0);
2271 
2272 	indexSCB = secondCB->findData(selectedSecondCelestialBodyId, Qt::UserRole, Qt::MatchCaseSensitive);
2273 	if (indexSCB < 0)
2274 	{
2275 		indexSCB = secondCB->findData(conf->value("astrocalc/second_celestial_body", "Earth").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2276 		if (indexSCB<0)
2277 			indexSCB = 0;
2278 	}
2279 	secondCB->setCurrentIndex(indexSCB);
2280 	secondCB->model()->sort(0);
2281 
2282 	planets->blockSignals(false);
2283 	planets2->blockSignals(false);
2284 	graphsp->blockSignals(false);
2285 	firstCB->blockSignals(false);
2286 	secondCB->blockSignals(false);
2287 }
2288 
saveEphemerisCelestialBody(int index)2289 void AstroCalcDialog::saveEphemerisCelestialBody(int index)
2290 {
2291 	Q_ASSERT(ui->celestialBodyComboBox);
2292 	QComboBox* planets = ui->celestialBodyComboBox;
2293 	conf->setValue("astrocalc/ephemeris_celestial_body", planets->itemData(index).toString());
2294 }
2295 
saveEphemerisSecondaryCelestialBody(int index)2296 void AstroCalcDialog::saveEphemerisSecondaryCelestialBody(int index)
2297 {
2298 	Q_ASSERT(ui->secondaryCelestialBodyComboBox);
2299 	QComboBox* planets = ui->secondaryCelestialBodyComboBox;
2300 	conf->setValue("astrocalc/ephemeris_second_celestial_body", planets->itemData(index).toString());
2301 }
2302 
saveGraphsCelestialBody(int index)2303 void AstroCalcDialog::saveGraphsCelestialBody(int index)
2304 {
2305 	Q_ASSERT(ui->graphsCelestialBodyComboBox);
2306 	QComboBox* planets = ui->graphsCelestialBodyComboBox;
2307 	conf->setValue("astrocalc/graphs_celestial_body", planets->itemData(index).toString());
2308 }
2309 
saveGraphsFirstId(int index)2310 void AstroCalcDialog::saveGraphsFirstId(int index)
2311 {
2312 	Q_ASSERT(ui->graphsFirstComboBox);
2313 	conf->setValue("astrocalc/graphs_first_id", ui->graphsFirstComboBox->itemData(index).toInt());
2314 }
2315 
saveGraphsSecondId(int index)2316 void AstroCalcDialog::saveGraphsSecondId(int index)
2317 {
2318 	Q_ASSERT(ui->graphsSecondComboBox);
2319 	conf->setValue("astrocalc/graphs_second_id", ui->graphsSecondComboBox->itemData(index).toInt());
2320 }
2321 
updateGraphsDuration(int duration)2322 void AstroCalcDialog::updateGraphsDuration(int duration)
2323 {
2324 	if (graphsDuration!=duration)
2325 	{
2326 		graphsDuration = duration;
2327 		conf->setValue("astrocalc/graphs_duration", duration);
2328 	}
2329 }
2330 
populateEphemerisTimeStepsList()2331 void AstroCalcDialog::populateEphemerisTimeStepsList()
2332 {
2333 	typedef QPair<QString, QString> itemPairs;
2334 	const QList<itemPairs> items = {
2335 		{q_("1 minute"), "38"}, {q_("10 minutes"), "1"}, {q_("30 minutes"), "2"}, {q_("1 hour"), "3"}, {q_("6 hours"), "4"}, {q_("12 hours"), "5"},
2336 		{q_("1 solar day"), "6"}, {q_("5 solar days"), "7"}, {q_("10 solar days"), "8"}, {q_("15 solar days"), "9"}, {q_("30 solar days"), "10"},
2337 		{q_("60 solar days"), "11"}, {q_("100 solar days"), "24"},{q_("500 solar days"), "37"},{q_("1 sidereal day"), "18"},{q_("5 sidereal days"), "19"},
2338 		{q_("10 sidereal days"), "20"},{q_("15 sidereal days"), "21"},{q_("30 sidereal days"), "22"},{q_("60 sidereal days"), "23"},{q_("100 sidereal days"), "25"},
2339 		{q_("500 sidereal days"), "36"},{q_("1 sidereal year"), "27"},{q_("1 Julian day"), "12"},{q_("5 Julian days"), "13"},{q_("10 Julian days"), "14"},
2340 		{q_("15 Julian days"), "15"},{q_("30 Julian days"), "16"},{q_("60 Julian days"), "17"},{q_("100 Julian days"), "26"},{q_("1 Julian year"), "28"},
2341 		{q_("1 Gaussian year"), "29"},{q_("1 synodic month"), "30"},{q_("1 draconic month"), "31"},{q_("1 mean tropical month"), "32"},
2342 		{q_("1 anomalistic month"), "33"},{q_("1 anomalistic year"), "34"},{q_("1 saros"), "35"},{q_("custom interval"), "0"}
2343 	};
2344 	Q_ASSERT(ui->ephemerisStepComboBox);
2345 	QComboBox* steps = ui->ephemerisStepComboBox;
2346 	steps->blockSignals(true);
2347 	steps->clear();
2348 	int index = steps->currentIndex();
2349 	QVariant selectedStepId = steps->itemData(index);
2350 	for (const auto& f : items)
2351 	{
2352 		steps->addItem(f.first, f.second);
2353 	}
2354 
2355 	index = steps->findData(selectedStepId, Qt::UserRole, Qt::MatchCaseSensitive);
2356 	if (index < 0)
2357 	{
2358 		// default step: one day
2359 		index = steps->findData(conf->value("astrocalc/ephemeris_time_step", "6").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2360 	}
2361 	steps->setCurrentIndex(index);
2362 	steps->blockSignals(false);
2363 	enableCustomEphemerisTimeStepButton();
2364 }
2365 
saveEphemerisTimeStep(int index)2366 void AstroCalcDialog::saveEphemerisTimeStep(int index)
2367 {
2368 	Q_ASSERT(ui->ephemerisStepComboBox);
2369 	QComboBox* steps = ui->ephemerisStepComboBox;
2370 	conf->setValue("astrocalc/ephemeris_time_step", steps->itemData(index).toInt());
2371 	enableCustomEphemerisTimeStepButton();
2372 }
2373 
initEphemerisFlagNakedEyePlanets(void)2374 void AstroCalcDialog::initEphemerisFlagNakedEyePlanets(void)
2375 {
2376 	bool nep = conf->value("astrocalc/ephemeris_nakedeye_planets", "false").toBool();
2377 	if (core->getCurrentPlanet()==solarSystem->getEarth())
2378 	{
2379 		ui->celestialBodyComboBox->setEnabled(!nep);
2380 		ui->secondaryCelestialBodyComboBox->setEnabled(!nep);
2381 		ui->allNakedEyePlanetsCheckBox->setChecked(nep);
2382 		ui->allNakedEyePlanetsCheckBox->setEnabled(true);
2383 	}
2384 	else
2385 	{
2386 		ui->celestialBodyComboBox->setEnabled(true);
2387 		ui->secondaryCelestialBodyComboBox->setEnabled(true);
2388 		ui->allNakedEyePlanetsCheckBox->setChecked(false);
2389 		ui->allNakedEyePlanetsCheckBox->setEnabled(false);
2390 	}
2391 }
2392 
saveEphemerisFlagNakedEyePlanets(bool flag)2393 void AstroCalcDialog::saveEphemerisFlagNakedEyePlanets(bool flag)
2394 {
2395 	ui->celestialBodyComboBox->setEnabled(!flag);
2396 	ui->secondaryCelestialBodyComboBox->setEnabled(!flag);
2397 	conf->setValue("astrocalc/ephemeris_nakedeye_planets", flag);
2398 	reGenerateEphemeris(false);
2399 }
2400 
enableCustomEphemerisTimeStepButton()2401 void AstroCalcDialog::enableCustomEphemerisTimeStepButton()
2402 {
2403 	Q_ASSERT(ui->ephemerisStepComboBox);
2404 	if (ui->ephemerisStepComboBox->currentData(Qt::UserRole).toInt()==0)
2405 		ui->pushButtonCustomStepsDialog->setEnabled(true);
2406 	else
2407 		ui->pushButtonCustomStepsDialog->setEnabled(false);
2408 }
2409 
populatePlanetList()2410 void AstroCalcDialog::populatePlanetList()
2411 {
2412 	Q_ASSERT(ui->object1ComboBox); // object 1 is always major planet
2413 
2414 	QComboBox* planetList = ui->object1ComboBox;
2415 	QList<PlanetP> planets = solarSystem->getAllPlanets();
2416 	const StelTranslator& trans = localeMgr->getSkyTranslator();
2417 	QString cpName = core->getCurrentPlanet()->getEnglishName();
2418 
2419 	// Save the current selection to be restored later
2420 	planetList->blockSignals(true);
2421 	int index = planetList->currentIndex();
2422 	QVariant selectedPlanetId = planetList->itemData(index);
2423 	planetList->clear();
2424 	// For each planet, display the localized name and store the original as user
2425 	// data. Unfortunately, there's no other way to do this than with a cycle.
2426 	for (const auto& planet : planets)
2427 	{
2428 		// major planets and the Sun
2429 		if ((planet->getPlanetType() == Planet::isPlanet || planet->getPlanetType() == Planet::isStar) && planet->getEnglishName() != cpName)
2430 			planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2431 
2432 		// moons of the current planet
2433 		if (planet->getPlanetType() == Planet::isMoon && planet->getEnglishName() != cpName && planet->getParent() == core->getCurrentPlanet())
2434 			planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2435 	}
2436 	// special case: selected dwarf and minor planets
2437 	planets.clear();
2438 	planets.append(solarSystem->searchByEnglishName("Pluto"));
2439 	planets.append(solarSystem->searchByEnglishName("Ceres"));
2440 	planets.append(solarSystem->searchByEnglishName("Pallas"));
2441 	planets.append(solarSystem->searchByEnglishName("Juno"));
2442 	planets.append(solarSystem->searchByEnglishName("Vesta"));
2443 	for (const auto& planet : planets)
2444 	{
2445 		if (!planet.isNull() && planet->getEnglishName()!=cpName)
2446 			planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2447 	}
2448 	// Restore the selection
2449 	index = planetList->findData(selectedPlanetId, Qt::UserRole, Qt::MatchCaseSensitive);
2450 	if (index < 0)
2451 	{
2452 		index = planetList->findData(conf->value("astrocalc/phenomena_celestial_body", "Venus").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2453 		if (index<0)
2454 			index = 0;
2455 	}
2456 	planetList->setCurrentIndex(index);
2457 	planetList->model()->sort(0);
2458 	planetList->blockSignals(false);
2459 }
2460 
savePhenomenaCelestialBody(int index)2461 void AstroCalcDialog::savePhenomenaCelestialBody(int index)
2462 {
2463 	Q_ASSERT(ui->object1ComboBox);
2464 	QComboBox* planets = ui->object1ComboBox;
2465 	conf->setValue("astrocalc/phenomena_celestial_body", planets->itemData(index).toString());
2466 }
2467 
populateGroupCelestialBodyList()2468 void AstroCalcDialog::populateGroupCelestialBodyList()
2469 {
2470 	Q_ASSERT(ui->object2ComboBox);
2471 
2472 	QComboBox* groups = ui->object2ComboBox;
2473 	groups->blockSignals(true);
2474 	int index = groups->currentIndex();
2475 	QVariant selectedGroupId = groups->itemData(index);
2476 
2477 	QString brLimit = QString::number(brightLimit, 'f', 1);
2478 	const QMap<QString, int>itemsMap={
2479 		{q_("Latest selected object"), PHCLatestSelectedObject}, {q_("Solar system"), PHCSolarSystem}, {q_("Planets"), PHCPlanets}, {q_("Asteroids"), PHCAsteroids},
2480 		{q_("Plutinos"), PHCPlutinos}, {q_("Comets"), PHCComets},	{q_("Dwarf planets"), PHCDwarfPlanets},{q_("Cubewanos"), PHCCubewanos},
2481 		{q_("Scattered disc objects"), PHCScatteredDiscObjects},{q_("Oort cloud objects"), PHCOortCloudObjects},{q_("Sednoids"), PHCSednoids},
2482 		{q_("Bright stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightStars},{q_("Bright double stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightDoubleStars},
2483 		{q_("Bright variable stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightVariableStars},{q_("Bright star clusters (<%1 mag)").arg(brLimit), PHCBrightStarClusters},
2484 		{q_("Planetary nebulae (<%1 mag)").arg(brLimit), PHCPlanetaryNebulae},{q_("Bright nebulae (<%1 mag)").arg(brLimit), PHCBrightNebulae},{q_("Dark nebulae"), PHCDarkNebulae},
2485 		{q_("Bright galaxies (<%1 mag)").arg(brLimit), PHCBrightGalaxies},{q_("Symbiotic stars"), PHCSymbioticStars},{q_("Emission-line stars"), PHCEmissionLineStars},
2486 		{q_("Interstellar objects"), PHCInterstellarObjects},{q_("Planets and Sun"), PHCPlanetsSun},{q_("Sun, planets and moons of observer location"), PHCSunPlanetsMoons},
2487 		{q_("Bright Solar system objects (<%1 mag)").arg(QString::number(brightLimit + 2.0, 'f', 1)), PHCBrightSolarSystemObjects},
2488 		{q_("Solar system objects: minor bodies"), PHCSolarSystemMinorBodies},{q_("Moons of first body"), PHCMoonsFirstBody},
2489 		{q_("Bright carbon stars"), PHCBrightCarbonStars},{q_("Bright barium stars"), PHCBrightBariumStars}
2490 	};
2491 	QMapIterator<QString, int> i(itemsMap);
2492 	groups->clear();
2493 	while (i.hasNext())
2494 	{
2495 		i.next();
2496 		groups->addItem(i.key(), QString::number(i.value()));
2497 	}
2498 
2499 	index = groups->findData(selectedGroupId, Qt::UserRole, Qt::MatchCaseSensitive);
2500 	if (index < 0)
2501 		index = groups->findData(conf->value("astrocalc/phenomena_celestial_group", "1").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2502 	groups->setCurrentIndex(index);
2503 	groups->model()->sort(0);
2504 	groups->blockSignals(false);
2505 }
2506 
savePhenomenaCelestialGroup(int index)2507 void AstroCalcDialog::savePhenomenaCelestialGroup(int index)
2508 {
2509 	Q_ASSERT(ui->object2ComboBox);
2510 	QComboBox* group = ui->object2ComboBox;
2511 	conf->setValue("astrocalc/phenomena_celestial_group", group->itemData(index).toInt());
2512 }
2513 
cleanupPhenomena()2514 void AstroCalcDialog::cleanupPhenomena()
2515 {
2516 	ui->phenomenaTreeWidget->clear();
2517 	adjustPhenomenaColumns();
2518 }
2519 
savePhenomenaOppositionFlag(bool b)2520 void AstroCalcDialog::savePhenomenaOppositionFlag(bool b)
2521 {
2522 	conf->setValue("astrocalc/flag_phenomena_opposition", b);
2523 }
2524 
savePhenomenaPerihelionAphelionFlag(bool b)2525 void AstroCalcDialog::savePhenomenaPerihelionAphelionFlag(bool b)
2526 {
2527 	conf->setValue("astrocalc/flag_phenomena_perihelion", b);
2528 }
2529 
savePhenomenaAngularSeparation()2530 void AstroCalcDialog::savePhenomenaAngularSeparation()
2531 {
2532 	conf->setValue("astrocalc/phenomena_angular_separation", QString::number(ui->allowedSeparationSpinBox->valueDegrees(), 'f', 5));
2533 }
2534 
drawAltVsTimeDiagram()2535 void AstroCalcDialog::drawAltVsTimeDiagram()
2536 {
2537 	// Avoid crash!
2538 	if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
2539 		return;
2540 
2541 	// special case - plot the graph when tab is visible
2542 	//..
2543 	// we got notified about a reason to redraw the plot, but dialog was
2544 	// not visible. which means we must redraw when becoming visible again!
2545 	if (!dialog->isVisible() && plotAltVsTime)
2546 	{
2547 		graphPlotNeedsRefresh = true;
2548 		return;
2549 	}
2550 
2551 	if (!plotAltVsTime) return;
2552 
2553 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
2554 
2555 	if (!selectedObjects.isEmpty())
2556 	{
2557 		// X axis - time; Y axis - altitude
2558 		QList<double> aX, aY, sX, sY, sYn, sYa, sYc, mX, mY;
2559 		QVector<double> xs, ys, ysn, ysa, ysc, xm, ym;
2560 
2561 		StelObjectP selectedObject = selectedObjects[0];
2562 		ui->altVsTimeTitle->setText(selectedObject->getNameI18n());
2563 		const bool onEarth = core->getCurrentPlanet()==solarSystem->getEarth();
2564 
2565 		const double currentJD = core->getJD();
2566 		const double shift = core->getUTCOffset(currentJD) / 24.0;
2567 		const double noon = static_cast<int>(currentJD + shift);
2568 		double az, alt, deg, ltime, JD;
2569 		bool sign;
2570 
2571 		double xMaxY = -100.;
2572 		int step = 180;
2573 		int limit = 485;
2574 #ifdef USE_STATIC_PLUGIN_SATELLITES
2575 		bool isSatellite = false;
2576 
2577 		SatelliteP sat;
2578 		if (selectedObject->getType() == "Satellite")
2579 		{
2580 			// get reference to satellite
2581 			isSatellite = true;
2582 			sat = GETSTELMODULE(Satellites)->getById(selectedObject->getInfoMap(core)["catalog"].toString());
2583 		}
2584 #endif
2585 
2586 		for (int i = -5; i <= limit; i++) // 24 hours + 15 minutes in both directions
2587 		{
2588 			// A new point on the graph every 3 minutes with shift to right 12 hours
2589 			// to get midnight at the center of diagram (i.e. accuracy is 3 minutes)
2590 			ltime = i * step + 43200;
2591 			aX.append(ltime);
2592 			JD = noon + ltime / 86400 - shift - 0.5;
2593 			core->setJD(JD);
2594 
2595 #ifdef USE_STATIC_PLUGIN_SATELLITES
2596 			if (isSatellite)
2597 			{
2598 				// update data for that single satellite only
2599 				sat->update(0.0);
2600 			}
2601 			else
2602 #endif
2603 				core->update(0.0);
2604 
2605 			StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
2606 			StelUtils::radToDecDeg(alt, sign, deg);
2607 			if (!sign) deg *= -1;
2608 			aY.append(deg);
2609 			if (deg > xMaxY)
2610 			{
2611 				xMaxY = deg;
2612 				transitX = ltime;
2613 			}
2614 		}
2615 
2616 		if (plotAltVsTimeSun)
2617 		{
2618 			PlanetP sun = solarSystem->getSun();
2619 			for (int i = -1; i <= 25; i++)
2620 			{
2621 				ltime = i * 3600 + 43200;
2622 				sX.append(ltime);
2623 				JD = noon + ltime / 86400 - shift - 0.5;
2624 				core->setJD(JD);
2625 				core->update(0.0);
2626 				StelUtils::rectToSphe(&az, &alt, sun->getAltAzPosAuto(core));
2627 				StelUtils::radToDecDeg(alt, sign, deg);
2628 				if (!sign) deg *= -1;
2629 				sY.append(deg);
2630 				sYc.append(deg + 6);
2631 				sYn.append(deg + 12);
2632 				sYa.append(deg + 18);
2633 			}
2634 		}
2635 
2636 		if (plotAltVsTimeMoon && onEarth)
2637 		{
2638 			PlanetP moon = solarSystem->getMoon();
2639 			for (int i = -1; i <= 25; i++)
2640 			{
2641 				ltime = i * 3600 + 43200;
2642 				mX.append(ltime);
2643 				JD = noon + ltime / 86400 - shift - 0.5;
2644 				core->setJD(JD);
2645 				core->update(0.0);
2646 				StelUtils::rectToSphe(&az, &alt, moon->getAltAzPosAuto(core));
2647 				StelUtils::radToDecDeg(alt, sign, deg);
2648 				if (!sign) deg *= -1;
2649 				mY.append(deg);
2650 			}
2651 		}
2652 
2653 		core->setJD(currentJD);
2654 
2655 		QVector<double> x = aX.toVector(), y = aY.toVector();
2656 		minY = *std::min_element(aY.begin(), aY.end()) - 2.0;
2657 		maxY = *std::max_element(aY.begin(), aY.end()) + 2.0;
2658 
2659 		// additional data: Sun + Twilight
2660 		if (plotAltVsTimeSun)
2661 		{
2662 			xs = sX.toVector();
2663 			ys = sY.toVector();
2664 			ysc = sYc.toVector();
2665 			ysn = sYn.toVector();
2666 			ysa = sYa.toVector();
2667 			double minYs = *std::min_element(sY.begin(), sY.end());
2668 			double maxYs = *std::max_element(sY.begin(), sY.end());
2669 
2670 			if (minY >= minYs - 2.0)  minY = minYs - 2.0;
2671 			if (maxY <= maxYs + 20.0) maxY = maxYs + 20.0;
2672 		}
2673 
2674 		// additional data: Moon
2675 		if (plotAltVsTimeMoon && onEarth)
2676 		{
2677 			xm = mX.toVector();
2678 			ym = mY.toVector();
2679 			double minYm = *std::min_element(mY.begin(), mY.end());
2680 			double maxYm = *std::max_element(mY.begin(), mY.end());
2681 
2682 			if (minY >= minYm - 2.0)  minY = minYm - 2.0;
2683 			if (maxY <= maxYm + 2.0)  maxY = maxYm + 2.0;
2684 		}
2685 
2686 		if (plotAltVsTimePositive && minY<altVsTimePositiveLimit)
2687 			minY = altVsTimePositiveLimit;
2688 
2689 		prepareAxesAndGraph();
2690 		drawCurrentTimeDiagram();
2691 
2692 		QString name = selectedObject->getNameI18n();
2693 		if (name.isEmpty())
2694 		{
2695 			QString otype = selectedObject->getType();
2696 			if (otype == "Nebula")
2697 			{
2698 				name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
2699 				if (name.isEmpty())
2700 					name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
2701 			}
2702 
2703 			if (otype == "Star" || otype=="Pulsar")
2704 				selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
2705 		}
2706 
2707 		drawTransitTimeDiagram();
2708 
2709 		ui->altVsTimePlot->graph(0)->setData(x, y);
2710 		ui->altVsTimePlot->graph(0)->setName(name);
2711 		if (plotAltVsTimeSun)
2712 		{
2713 			ui->altVsTimePlot->graph(3)->setData(xs, ys);
2714 			ui->altVsTimePlot->graph(4)->setData(xs, ysc);
2715 			ui->altVsTimePlot->graph(5)->setData(xs, ysn);
2716 			ui->altVsTimePlot->graph(6)->setData(xs, ysa);
2717 		}
2718 		if (plotAltVsTimeMoon && onEarth)
2719 			ui->altVsTimePlot->graph(7)->setData(xm, ym);
2720 
2721 		ui->altVsTimePlot->replot();
2722 	}
2723 
2724 	// clean up the data when selection is removed
2725 	if (!objectMgr->getWasSelected())
2726 	{
2727 		ui->altVsTimePlot->graph(0)->data()->clear(); // main data: Altitude vs. Time graph
2728 		ui->altVsTimePlot->graph(2)->data()->clear(); // additional data: Transit Time Diagram
2729 		ui->altVsTimePlot->graph(3)->data()->clear(); // additional data: Sun
2730 		ui->altVsTimePlot->graph(4)->data()->clear(); // additional data: Civil Twilight
2731 		ui->altVsTimePlot->graph(5)->data()->clear(); // additional data: Nautical Twilight
2732 		ui->altVsTimePlot->graph(6)->data()->clear(); // additional data: Astronomical Twilight
2733 		ui->altVsTimePlot->graph(7)->data()->clear(); // additional data: Moon
2734 		ui->altVsTimePlot->replot();
2735 	}
2736 }
2737 
2738 // Added vertical line indicating "now"
drawCurrentTimeDiagram()2739 void AstroCalcDialog::drawCurrentTimeDiagram()
2740 {
2741 	// special case - plot the graph when tab is visible
2742 	// and only if dialog is visible at all
2743 	if (!dialog->isVisible() || (!plotAltVsTime && !plotAziVsTime)) return;
2744 
2745 	const double currentJD = core->getJD();
2746 	const double UTCOffset = core->getUTCOffset(currentJD);
2747 	double now = ((currentJD + 0.5 - static_cast<int>(currentJD)) * 86400.0) + UTCOffset * 3600.0;
2748 	if (now > 129600) now -= 86400;
2749 	if (now < 43200) now += 86400;
2750 
2751 	QList<double> ax, ay;
2752 	ax.append(now);
2753 	ax.append(now);
2754 	ay.append(-180.);
2755 	ay.append(360.);
2756 	QVector<double> x = ax.toVector(), y = ay.toVector();
2757 	if (plotAltVsTime)
2758 	{
2759 		ui->altVsTimePlot->graph(1)->setData(x, y);
2760 		ui->altVsTimePlot->replot();
2761 	}
2762 	if (plotAziVsTime)
2763 	{
2764 		ui->aziVsTimePlot->graph(1)->setData(x, y);
2765 		ui->aziVsTimePlot->replot();
2766 	}
2767 
2768 	// detect roll over graph day limits.
2769 	// if so, update the graph
2770 	int graphJD = static_cast<int>(currentJD + UTCOffset / 24.);
2771 	if (oldGraphJD != graphJD || graphPlotNeedsRefresh)
2772 	{
2773 		oldGraphJD = graphJD;
2774 		graphPlotNeedsRefresh = false;
2775 		emit graphDayChanged();
2776 	}
2777 }
2778 
2779 // Added vertical line indicating time of transit
drawTransitTimeDiagram()2780 void AstroCalcDialog::drawTransitTimeDiagram()
2781 {
2782 	// special case - plot the graph when tab is visible
2783 	if (!plotAltVsTime)
2784 		return;
2785 
2786 	QList<double> ax, ay;
2787 	ax.append(transitX);
2788 	ax.append(transitX);
2789 	ay.append(minY);
2790 	ay.append(maxY);
2791 	QVector<double> x = ax.toVector(), y = ay.toVector();
2792 	ui->altVsTimePlot->graph(2)->setData(x, y);
2793 	ui->altVsTimePlot->replot();
2794 }
2795 
prepareAxesAndGraph()2796 void AstroCalcDialog::prepareAxesAndGraph()
2797 {
2798 	QString xAxisStr = q_("Local Time");
2799 	QString yAxisStr = QString("%1, %2").arg(q_("Altitude"), QChar(0x00B0));
2800 
2801 	QColor axisColor(Qt::white);
2802 	QPen axisPen(axisColor, 1);
2803 
2804 	ui->altVsTimePlot->clearGraphs();
2805 
2806 	// main data: Altitude vs. Time graph
2807 	ui->altVsTimePlot->addGraph();
2808 	ui->altVsTimePlot->setBackground(QBrush(QColor(86, 87, 90)));
2809 	ui->altVsTimePlot->graph(0)->setPen(QPen(Qt::red, 1));
2810 	ui->altVsTimePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
2811 	ui->altVsTimePlot->graph(0)->rescaleAxes(true);
2812 
2813 	// additional data: Current Time Diagram
2814 	ui->altVsTimePlot->addGraph();
2815 	ui->altVsTimePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
2816 	ui->altVsTimePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
2817 	ui->altVsTimePlot->graph(1)->setName("[Now]");
2818 
2819 	// additional data: Transit Time Diagram
2820 	ui->altVsTimePlot->addGraph();
2821 	ui->altVsTimePlot->graph(2)->setPen(QPen(Qt::cyan, 1));
2822 	ui->altVsTimePlot->graph(2)->setLineStyle(QCPGraph::lsLine);
2823 	ui->altVsTimePlot->graph(2)->setName("[Transit]");
2824 
2825 	// additional data: Sun Elevation
2826 	ui->altVsTimePlot->addGraph();
2827 	ui->altVsTimePlot->graph(3)->setPen(QPen(Qt::darkBlue, 1));
2828 	ui->altVsTimePlot->graph(3)->setLineStyle(QCPGraph::lsLine);
2829 	ui->altVsTimePlot->graph(3)->setName("[Sun]");
2830 	// additional data: Civil Twilight
2831 	QPen pen;
2832 	pen.setStyle(Qt::DotLine);
2833 	pen.setWidth(1);
2834 	pen.setColor(Qt::blue);
2835 	ui->altVsTimePlot->addGraph();
2836 	ui->altVsTimePlot->graph(4)->setPen(pen);
2837 	ui->altVsTimePlot->graph(4)->setName("[Civil Twilight]");
2838 	// additional data: Nautical Twilight
2839 	ui->altVsTimePlot->addGraph();
2840 	ui->altVsTimePlot->graph(5)->setPen(pen);
2841 	ui->altVsTimePlot->graph(5)->setName("[Nautical Twilight]");
2842 	// additional data: Astronomical Twilight
2843 	ui->altVsTimePlot->addGraph();
2844 	ui->altVsTimePlot->graph(6)->setPen(pen);
2845 	ui->altVsTimePlot->graph(6)->setName("[Astronomical Twilight]");
2846 
2847 	// additional data: Moon Elevation
2848 	pen.setStyle(Qt::DashLine);
2849 	pen.setColor(Qt::darkBlue);
2850 	ui->altVsTimePlot->addGraph();
2851 	ui->altVsTimePlot->graph(7)->setPen(pen);
2852 	ui->altVsTimePlot->graph(7)->setName("[Moon]");
2853 
2854 	ui->altVsTimePlot->xAxis->setLabel(xAxisStr);
2855 	ui->altVsTimePlot->yAxis->setLabel(yAxisStr);
2856 
2857 	ui->altVsTimePlot->xAxis->setRange(43200, 129600); // 24 hours since 12h00m (range in seconds)
2858 	ui->altVsTimePlot->xAxis->setScaleType(QCPAxis::stLinear);
2859 	ui->altVsTimePlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
2860 	ui->altVsTimePlot->xAxis->setLabelColor(axisColor);
2861 	ui->altVsTimePlot->xAxis->setTickLabelColor(axisColor);
2862 	ui->altVsTimePlot->xAxis->setBasePen(axisPen);
2863 	ui->altVsTimePlot->xAxis->setTickPen(axisPen);
2864 	ui->altVsTimePlot->xAxis->setSubTickPen(axisPen);
2865 	ui->altVsTimePlot->xAxis->setDateTimeFormat("H:mm");
2866 	ui->altVsTimePlot->xAxis->setDateTimeSpec(Qt::UTC); // Qt::UTC + core->getUTCOffset() give local time
2867 	ui->altVsTimePlot->xAxis->setAutoTickStep(false);
2868 	ui->altVsTimePlot->xAxis->setTickStep(7200); // step is 2 hours (in seconds)
2869 	ui->altVsTimePlot->xAxis->setAutoSubTicks(false);
2870 	ui->altVsTimePlot->xAxis->setSubTickCount(7);
2871 
2872 	ui->altVsTimePlot->yAxis->setRange(minY, maxY);
2873 	ui->altVsTimePlot->yAxis->setScaleType(QCPAxis::stLinear);
2874 	ui->altVsTimePlot->yAxis->setLabelColor(axisColor);
2875 	ui->altVsTimePlot->yAxis->setTickLabelColor(axisColor);
2876 	ui->altVsTimePlot->yAxis->setBasePen(axisPen);
2877 	ui->altVsTimePlot->yAxis->setTickPen(axisPen);
2878 	ui->altVsTimePlot->yAxis->setSubTickPen(axisPen);
2879 }
2880 
drawXVsTimeGraphs()2881 void AstroCalcDialog::drawXVsTimeGraphs()
2882 {
2883 	PlanetP ssObj = solarSystem->searchByEnglishName(ui->graphsCelestialBodyComboBox->currentData().toString());
2884 	// added special case - the tool is not applicable on non-Earth locations
2885 	if (!ssObj.isNull() && core->getCurrentPlanet()==solarSystem->getEarth())
2886 	{
2887 		// X axis - time; Y axis - altitude
2888 		QList<double> aX, aY, bY;
2889 
2890 		const double currentJD = core->getJD();
2891 		int year, month, day;
2892 		double startJD, JD, ltime, /*UTCshift,*/ width = 1.0;
2893 		StelUtils::getDateFromJulianDay(currentJD, &year, &month, &day);
2894 		StelUtils::getJDFromDate(&startJD, year, 1, 1, 0, 0, 0);
2895 
2896 		int dYear = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod()*graphsDuration) + 3;
2897 		int firstGraph = ui->graphsFirstComboBox->currentData().toInt();
2898 		int secondGraph = ui->graphsSecondComboBox->currentData().toInt();
2899 
2900 		for (int i = -2; i <= dYear; i++)
2901 		{
2902 			JD = startJD + i;
2903 
2904 			if (firstGraph==GraphTransitAltitudeVsTime || secondGraph==GraphTransitAltitudeVsTime)
2905 			{
2906 				core->setJD(JD);
2907 				//UTCshift = core->getUTCOffset(JD) / 24.; // Fix DST shift...
2908 				Vec4d rts = ssObj->getRTSTime(core);
2909 				//JD += (rts[1]/24. - UTCshift); // FIXME: New logic has JD, not hours, here.
2910 				JD = rts[1]; // Maybe that's all?
2911 			}
2912 
2913 			ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
2914 			aX.append(ltime);
2915 
2916 			core->setJD(JD);
2917 
2918 			aY.append(computeGraphValue(ssObj, firstGraph));
2919 			bY.append(computeGraphValue(ssObj, secondGraph));
2920 
2921 			core->update(0.0);
2922 		}
2923 		core->setJD(currentJD);
2924 
2925 		QVector<double> x = aX.toVector(), ya = aY.toVector(), yb = bY.toVector();
2926 
2927 		double minYa = *std::min_element(aY.begin(), aY.end());
2928 		double maxYa = *std::max_element(aY.begin(), aY.end());
2929 
2930 		width = (maxYa - minYa) / 50.0;
2931 		minY1 = minYa - width;
2932 		maxY1 = maxYa + width;
2933 
2934 		minYa = *std::min_element(bY.begin(), bY.end());
2935 		maxYa = *std::max_element(bY.begin(), bY.end());
2936 
2937 		width = (maxYa - minYa) / 50.0;
2938 		minY2 = minYa - width;
2939 		maxY2 = maxYa + width;
2940 
2941 		prepareXVsTimeAxesAndGraph();
2942 
2943 		ui->graphsPlot->clearGraphs();
2944 
2945 		ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
2946 		ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
2947 		ui->graphsPlot->graph(0)->setPen(QPen(Qt::green, 1));
2948 		ui->graphsPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
2949 		ui->graphsPlot->graph(0)->rescaleAxes(true);
2950 		ui->graphsPlot->graph(0)->setData(x, ya);
2951 		ui->graphsPlot->graph(0)->setName("[0]");
2952 
2953 		ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis2);
2954 		ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
2955 		ui->graphsPlot->graph(1)->setPen(QPen(Qt::yellow, 1));
2956 		ui->graphsPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
2957 		ui->graphsPlot->graph(1)->rescaleAxes(true);
2958 		ui->graphsPlot->graph(1)->setData(x, yb);
2959 		ui->graphsPlot->graph(1)->setName("[1]");
2960 
2961 		if (graphsDuration>1)
2962 		{
2963 			int JDshift = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod());
2964 			QList<double> axj, ayj;
2965 			for (int i = 0; i < graphsDuration; i++)
2966 			{
2967 				JD = startJD + i*JDshift;
2968 				ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
2969 				axj.append(ltime);
2970 				axj.append(ltime);
2971 				ayj.append(minY1);
2972 				ayj.append(maxY1);
2973 				QVector<double> xj = axj.toVector(), yj = ayj.toVector();
2974 				int j = 2 + i;
2975 				ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
2976 				ui->graphsPlot->graph(j)->setPen(QPen(Qt::red, 1, Qt::DashLine));
2977 				ui->graphsPlot->graph(j)->setLineStyle(QCPGraph::lsLine);
2978 				ui->graphsPlot->graph(j)->setData(xj, yj);
2979 				ui->graphsPlot->graph(j)->setName(QString("[%1]").arg(j));
2980 				axj.clear();
2981 				ayj.clear();
2982 			}
2983 		}
2984 
2985 		ui->graphsPlot->replot();
2986 	}
2987 	else
2988 	{
2989 		prepareXVsTimeAxesAndGraph();
2990 		ui->graphsPlot->clearGraphs();
2991 		ui->graphsPlot->replot();
2992 	}
2993 }
2994 
updateXVsTimeGraphs()2995 void AstroCalcDialog::updateXVsTimeGraphs()
2996 {
2997 	// Do all that only if the dialog is visible!
2998 	if (!dialog->isVisible())
2999 		return;
3000 
3001 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3002 	if (!selectedObjects.isEmpty())
3003 	{
3004 		QComboBox* celestialBody = ui->graphsCelestialBodyComboBox;
3005 		celestialBody->blockSignals(true);
3006 		int index = celestialBody->findData(selectedObjects[0]->getEnglishName(), Qt::UserRole, Qt::MatchCaseSensitive);
3007 		if (index>=0)
3008 			celestialBody->setCurrentIndex(index);
3009 		celestialBody->blockSignals(false);
3010 	}
3011 
3012 	if (ui->tabWidgetGraphs->currentIndex()==3)
3013 		drawXVsTimeGraphs();
3014 }
3015 
mouseOverGraphs(QMouseEvent * event)3016 void AstroCalcDialog::mouseOverGraphs(QMouseEvent* event)
3017 {
3018 	double x = ui->graphsPlot->xAxis->pixelToCoord(event->pos().x());
3019 	double y = ui->graphsPlot->yAxis->pixelToCoord(event->pos().y());
3020 	double y2 = ui->graphsPlot->yAxis2->pixelToCoord(event->pos().y());
3021 
3022 	QCPAbstractPlottable* abstractGraph = ui->graphsPlot->plottableAt(event->pos(), false);
3023 	QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
3024 
3025 	int year, month, day;
3026 	double startJD, ltime;
3027 	StelUtils::getDateFromJulianDay(core->getJD(), &year, &month, &day);
3028 	StelUtils::getJDFromDate(&startJD, year, 1, 1, 0, 0, 0);
3029 
3030 	if (x > ui->graphsPlot->xAxis->range().lower && x < ui->graphsPlot->xAxis->range().upper
3031 	    && y > ui->graphsPlot->yAxis->range().lower && y < ui->graphsPlot->yAxis->range().upper)
3032 	{
3033 		QString info = "";
3034 		if (graph)
3035 		{
3036 			ltime = (x / StelCore::ONE_OVER_JD_SECOND) + startJD;
3037 
3038 			if (graph->name() == "[0]")
3039 				info = QString("%1<br />%2: %3").arg(StelUtils::julianDayToISO8601String(ltime).replace("T", " "), ui->graphsPlot->yAxis->label() , QString::number(y, 'f', 2));
3040 
3041 			if (graph->name() == "[1]")
3042 				info = QString("%1<br />%2: %3").arg(StelUtils::julianDayToISO8601String(ltime).replace("T", " "), ui->graphsPlot->yAxis2->label() , QString::number(y2, 'f', 2));
3043 		}
3044 		ui->graphsPlot->setToolTip(info);
3045 	}
3046 
3047 	ui->graphsPlot->update();
3048 	ui->graphsPlot->replot();
3049 }
3050 
computeGraphValue(const PlanetP & ssObj,const int graphType)3051 double AstroCalcDialog::computeGraphValue(const PlanetP &ssObj, const int graphType)
3052 {
3053 	double value = 0.;
3054 	switch (graphType)
3055 	{
3056 		case GraphMagnitudeVsTime:
3057 			value = ssObj->getVMagnitude(core);
3058 			break;
3059 		case GraphPhaseVsTime:
3060 			value = ssObj->getPhase(core->getObserverHeliocentricEclipticPos()) * 100.;
3061 			break;
3062 		case GraphDistanceVsTime:
3063 			value =  ssObj->getJ2000EquatorialPos(core).length();
3064 			break;
3065 		case GraphElongationVsTime:
3066 			value = ssObj->getElongation(core->getObserverHeliocentricEclipticPos()) * 180. / M_PI;
3067 			break;
3068 		case GraphAngularSizeVsTime:
3069 		{
3070 			value = ssObj->getAngularSize(core) * 360. / M_PI;
3071 			if (value < 1.)
3072 				value *= 60.;
3073 			break;
3074 		}
3075 		case GraphPhaseAngleVsTime:
3076 			value = ssObj->getPhaseAngle(core->getObserverHeliocentricEclipticPos()) * 180. / M_PI;
3077 			break;
3078 		case GraphHDistanceVsTime:
3079 			value =  ssObj->getHeliocentricEclipticPos().length();
3080 			break;
3081 		case GraphTransitAltitudeVsTime:
3082 		{
3083 			double az, alt;
3084 			bool sign;
3085 			StelUtils::rectToSphe(&az, &alt, ssObj->getAltAzPosAuto(core));
3086 			StelUtils::radToDecDeg(alt, sign, value); // convert to degrees
3087 			if (!sign)
3088 				value *= -1;
3089 			break;
3090 		}
3091 		case GraphRightAscensionVsTime:
3092 		{
3093 			double dec_equ, ra_equ;
3094 			StelUtils::rectToSphe(&ra_equ, &dec_equ, ssObj->getEquinoxEquatorialPos(core));
3095 			ra_equ = 2.*M_PI-ra_equ;
3096 			value = ra_equ*12./M_PI;
3097 			if (value>24.)
3098 				value -= 24.;
3099 			break;
3100 		}
3101 		case GraphDeclinationVsTime:
3102 		{
3103 			double dec_equ, ra_equ;
3104 			bool sign;
3105 			StelUtils::rectToSphe(&ra_equ, &dec_equ, ssObj->getEquinoxEquatorialPos(core));
3106 			StelUtils::radToDecDeg(dec_equ, sign, value); // convert to degrees
3107 			if (!sign)
3108 				value *= -1;
3109 			break;
3110 		}
3111 	}
3112 	return value;
3113 }
3114 
populateFunctionsList()3115 void AstroCalcDialog::populateFunctionsList()
3116 {
3117 	Q_ASSERT(ui->graphsFirstComboBox);
3118 	Q_ASSERT(ui->graphsSecondComboBox);
3119 
3120 	typedef QPair<QString, GraphsTypes> graph;
3121 	const QList<graph> functions = {
3122 		{ q_("Magnitude vs. Time"),    GraphMagnitudeVsTime},
3123 		{ q_("Phase vs. Time"),        GraphPhaseVsTime},
3124 		{ q_("Distance vs. Time"),     GraphDistanceVsTime},
3125 		{ q_("Elongation vs. Time"),   GraphElongationVsTime},
3126 		{ q_("Angular size vs. Time"), GraphAngularSizeVsTime},
3127 		{ q_("Phase angle vs. Time"),  GraphPhaseAngleVsTime},
3128 		// TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can short it to use in the drop-down list.
3129 		{ q_("Heliocentric distance vs. Time"), GraphHDistanceVsTime},
3130 		// TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can short it to use in the drop-down list.
3131 		{ q_("Transit altitude vs. Time"), GraphTransitAltitudeVsTime},
3132 		// TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can short it to use in the drop-down list.
3133 		{ q_("Right ascension vs. Time"), GraphRightAscensionVsTime},
3134 		{ q_("Declination vs. Time"), GraphDeclinationVsTime}};
3135 
3136 	QComboBox* first = ui->graphsFirstComboBox;
3137 	QComboBox* second = ui->graphsSecondComboBox;
3138 	first->blockSignals(true);
3139 	second->blockSignals(true);
3140 
3141 	int indexF = first->currentIndex();
3142 	QVariant selectedFirstId = first->itemData(indexF);
3143 	int indexS = second->currentIndex();
3144 	QVariant selectedSecondId = second->itemData(indexS);
3145 
3146 	first->clear();
3147 	second->clear();
3148 
3149 	for (const auto& f : functions)
3150 	{
3151 		first->addItem(f.first, f.second);
3152 		second->addItem(f.first, f.second);
3153 	}
3154 
3155 	indexF = first->findData(selectedFirstId, Qt::UserRole, Qt::MatchCaseSensitive);
3156 	if (indexF < 0)
3157 		indexF = first->findData(conf->value("astrocalc/graphs_first_id", GraphMagnitudeVsTime).toInt(), Qt::UserRole, Qt::MatchCaseSensitive);
3158 	first->setCurrentIndex(indexF);
3159 	first->model()->sort(0);
3160 
3161 	indexS = second->findData(selectedSecondId, Qt::UserRole, Qt::MatchCaseSensitive);
3162 	if (indexS < 0)
3163 		indexS = second->findData(conf->value("astrocalc/graphs_second_id", GraphPhaseVsTime).toInt(), Qt::UserRole, Qt::MatchCaseSensitive);
3164 	second->setCurrentIndex(indexS);
3165 	second->model()->sort(0);
3166 
3167 	first->blockSignals(false);
3168 	second->blockSignals(false);
3169 }
3170 
prepareXVsTimeAxesAndGraph()3171 void AstroCalcDialog::prepareXVsTimeAxesAndGraph()
3172 {
3173 	QString distMU = qc_("AU", "distance, astronomical unit");
3174 	QString asMU = QString("'");
3175 
3176 	PlanetP ssObj = solarSystem->searchByEnglishName(ui->graphsCelestialBodyComboBox->currentData().toString());
3177 	if (!ssObj.isNull())
3178 	{
3179 		if (ssObj->getJ2000EquatorialPos(core).length() < 0.1)
3180 		{
3181 			// TRANSLATORS: Mega-meter (SI symbol: Mm; Mega-meter is a unit of length in the metric system,
3182 			// equal to one million meters)
3183 			distMU = q_("Mm");
3184 		}
3185 		if ((ssObj->getAngularSize(core) * 360. / M_PI) < 1.) asMU = QString("\"");
3186 	}
3187 
3188 	bool direction1 = false;
3189 	bool direction2 = false;
3190 
3191 	switch (ui->graphsFirstComboBox->currentData().toInt())
3192 	{
3193 		case GraphMagnitudeVsTime:
3194 			yAxis1Legend = q_("Magnitude");
3195 			if (minY1 < -1000.) minY1 = 0.0;
3196 			if (maxY1 > 1000.) maxY1 = 6.0;
3197 			direction1 = true;
3198 			break;
3199 		case GraphPhaseVsTime:
3200 			yAxis1Legend = QString("%1, %").arg(q_("Phase"));
3201 			if (minY1 < -1000.) minY1 = 0.0;
3202 			if (maxY1 > 1000.) maxY1 = 100.0;
3203 			break;
3204 		case GraphDistanceVsTime:
3205 			yAxis1Legend = QString("%1, %2").arg(q_("Distance"), distMU);
3206 			if (minY1 < -1000.) minY1 = 0.0;
3207 			if (maxY1 > 1000.) maxY1 = 50.0;
3208 			break;
3209 		case GraphElongationVsTime:
3210 			yAxis1Legend = QString("%1, %2").arg(q_("Elongation"), QChar(0x00B0));
3211 			if (minY1 < -1000.) minY1 = 0.0;
3212 			if (maxY1 > 1000.) maxY1 = 180.0;
3213 			break;
3214 		case GraphAngularSizeVsTime:
3215 			yAxis1Legend = QString("%1, %2").arg(q_("Angular size"), asMU);
3216 			if (minY1 < -1000.) minY1 = 0.0;
3217 			if (maxY1 > 1000.) maxY1 = 30.0;
3218 			break;
3219 		case GraphPhaseAngleVsTime:
3220 			yAxis1Legend = QString("%1, %2").arg(q_("Phase angle"), QChar(0x00B0));
3221 			if (minY1 < -1000.) minY1 = 0.0;
3222 			if (maxY1 > 1000.) maxY1 = 180.0;
3223 			break;
3224 		case GraphHDistanceVsTime:
3225 			// TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can abbreviate it.
3226 			yAxis1Legend = QString("%1, %2").arg(qc_("Heliocentric distance","axis name"), distMU);
3227 			if (minY1 < -1000.) minY1 = 0.0;
3228 			if (maxY1 > 1000.) maxY1 = 50.0;
3229 			break;
3230 		case GraphTransitAltitudeVsTime:
3231 			// TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can abbreviate it.
3232 			yAxis1Legend = QString("%1, %2").arg(qc_("Transit altitude","axis name"), QChar(0x00B0));
3233 			if (minY1 < -1000.) minY1 = 0.0;
3234 			if (maxY1 > 1000.) maxY1 = 90.0;
3235 			break;
3236 		case GraphRightAscensionVsTime:
3237 			// TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can abbreviate it.
3238 			yAxis1Legend = QString("%1, %2").arg(qc_("Right ascension","axis name"), qc_("h","time"));
3239 			if (minY1 < -1000.) minY1 = 0.0;
3240 			if (maxY1 > 1000.) maxY1 = 24.0;
3241 			break;
3242 		case GraphDeclinationVsTime:
3243 			yAxis1Legend = QString("%1, %2").arg(q_("Declination"), QChar(0x00B0));
3244 			if (minY1 < -1000.) minY1 = -90.0;
3245 			if (maxY1 > 1000.) maxY1 = 90.0;
3246 			break;
3247 	}
3248 
3249 	switch (ui->graphsSecondComboBox->currentData().toInt())
3250 	{
3251 		case GraphMagnitudeVsTime:
3252 			yAxis2Legend = q_("Magnitude");
3253 			if (minY2 < -1000.) minY2 = 0.0;
3254 			if (maxY2 > 1000.) maxY2 = 6.0;
3255 			direction2 = true;
3256 			break;
3257 		case GraphPhaseVsTime:
3258 			yAxis2Legend = QString("%1, %").arg(q_("Phase"));
3259 			if (minY2 < -1000.) minY2 = 0.0;
3260 			if (maxY2 > 1000.) maxY2 = 100.0;
3261 			break;
3262 		case GraphDistanceVsTime:
3263 			yAxis2Legend = QString("%1, %2").arg(q_("Distance"), distMU);
3264 			if (minY2 < -1000.) minY2 = 0.0;
3265 			if (maxY2 > 1000.) maxY2 = 50.0;
3266 			break;
3267 		case GraphElongationVsTime:
3268 			yAxis2Legend = QString("%1, %2").arg(q_("Elongation"), QChar(0x00B0));
3269 			if (minY2 < -1000.) minY2 = 0.0;
3270 			if (maxY2 > 1000.) maxY2 = 180.0;
3271 			break;
3272 		case GraphAngularSizeVsTime:
3273 			yAxis2Legend = QString("%1, %2").arg(q_("Angular size"), asMU);
3274 			if (minY2 < -1000.) minY2 = 0.0;
3275 			if (maxY2 > 1000.) maxY2 = 30.0;
3276 			break;
3277 		case GraphPhaseAngleVsTime:
3278 			yAxis2Legend = QString("%1, %2").arg(q_("Phase angle"), QChar(0x00B0));
3279 			if (minY2 < -1000.) minY2 = 0.0;
3280 			if (maxY2 > 1000.) maxY2 = 180.0;
3281 			break;
3282 		case GraphHDistanceVsTime:
3283 			// TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can short it.
3284 			yAxis2Legend = QString("%1, %2").arg(q_("Heliocentric distance"), distMU);
3285 			if (minY2 < -1000.) minY2 = 0.0;
3286 			if (maxY2 > 1000.) maxY2 = 50.0;
3287 			break;
3288 		case GraphTransitAltitudeVsTime:
3289 			// TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can short it.
3290 			yAxis2Legend = QString("%1, %2").arg(q_("Transit altitude"), QChar(0x00B0));
3291 			if (minY2 < -1000.) minY2 = 0.0;
3292 			if (maxY2 > 1000.) maxY2 = 90.0;
3293 			break;
3294 		case GraphRightAscensionVsTime:
3295 			// TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can short it.
3296 			yAxis2Legend = QString("%1, %2").arg(qc_("Right ascension","axis name"), qc_("h","time"));
3297 			if (minY2 < -1000.) minY2 = 0.0;
3298 			if (maxY2 > 1000.) maxY2 = 24.0;
3299 			break;
3300 		case GraphDeclinationVsTime:
3301 			yAxis2Legend = QString("%1, %2").arg(q_("Declination"), QChar(0x00B0));
3302 			if (minY2 < -1000.) minY2 = -90.0;
3303 			if (maxY2 > 1000.) maxY2 = 90.0;
3304 			break;
3305 	}
3306 
3307 	QColor axisColor(Qt::white);
3308 	QPen axisPen(axisColor, 1);
3309 	QColor axisColorL(Qt::green);
3310 	QPen axisPenL(axisColorL, 1);
3311 	QColor axisColorR(Qt::yellow);
3312 	QPen axisPenR(axisColorR, 1);
3313 
3314 	ui->graphsPlot->setLocale(QLocale(localeMgr->getAppLanguage()));
3315 	ui->graphsPlot->yAxis->setLabel(yAxis1Legend);
3316 	ui->graphsPlot->yAxis2->setLabel(yAxis2Legend);
3317 
3318 	int dYear = (static_cast<int>(solarSystem->getEarth()->getSiderealPeriod()*graphsDuration) + 1) * 86400;
3319 	ui->graphsPlot->xAxis->setRange(0, dYear);
3320 	ui->graphsPlot->xAxis->setScaleType(QCPAxis::stLinear);
3321 	ui->graphsPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
3322 	ui->graphsPlot->xAxis->setLabelColor(axisColor);
3323 	ui->graphsPlot->xAxis->setTickLabelColor(axisColor);
3324 	ui->graphsPlot->xAxis->setBasePen(axisPen);
3325 	ui->graphsPlot->xAxis->setTickPen(axisPen);
3326 	ui->graphsPlot->xAxis->setSubTickPen(axisPen);
3327 	ui->graphsPlot->xAxis->setDateTimeFormat("d\nMMM");
3328 	ui->graphsPlot->xAxis->setDateTimeSpec(Qt::UTC);
3329 	ui->graphsPlot->xAxis->setAutoTicks(true);
3330 	ui->graphsPlot->xAxis->setAutoTickCount(20);
3331 
3332 	ui->graphsPlot->yAxis->setRange(minY1, maxY1);
3333 	ui->graphsPlot->yAxis->setScaleType(QCPAxis::stLinear);
3334 	ui->graphsPlot->yAxis->setLabelColor(axisColorL);
3335 	ui->graphsPlot->yAxis->setTickLabelColor(axisColorL);
3336 	ui->graphsPlot->yAxis->setBasePen(axisPenL);
3337 	ui->graphsPlot->yAxis->setTickPen(axisPenL);
3338 	ui->graphsPlot->yAxis->setSubTickPen(axisPenL);
3339 	ui->graphsPlot->yAxis->setRangeReversed(direction1);
3340 
3341 	ui->graphsPlot->yAxis2->setRange(minY2, maxY2);
3342 	ui->graphsPlot->yAxis2->setScaleType(QCPAxis::stLinear);
3343 	ui->graphsPlot->yAxis2->setLabelColor(axisColorR);
3344 	ui->graphsPlot->yAxis2->setTickLabelColor(axisColorR);
3345 	ui->graphsPlot->yAxis2->setBasePen(axisPenR);
3346 	ui->graphsPlot->yAxis2->setTickPen(axisPenR);
3347 	ui->graphsPlot->yAxis2->setSubTickPen(axisPenR);
3348 	ui->graphsPlot->yAxis2->setRangeReversed(direction2);
3349 	ui->graphsPlot->yAxis2->setVisible(true);
3350 
3351 	ui->graphsPlot->clearGraphs();
3352 	ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
3353 	ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
3354 	ui->graphsPlot->graph(0)->setPen(QPen(Qt::red, 1));
3355 	ui->graphsPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
3356 	ui->graphsPlot->graph(0)->rescaleAxes(true);
3357 
3358 	ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis2);
3359 	ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
3360 	ui->graphsPlot->graph(1)->setPen(QPen(Qt::yellow, 1));
3361 	ui->graphsPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
3362 	ui->graphsPlot->graph(1)->rescaleAxes(true);
3363 }
3364 
prepareMonthlyElevationAxesAndGraph()3365 void AstroCalcDialog::prepareMonthlyElevationAxesAndGraph()
3366 {
3367 	QString xAxisStr = q_("Date");
3368 	QString yAxisStr = QString("%1, %2").arg(q_("Altitude"), QChar(0x00B0));
3369 
3370 	QColor axisColor(Qt::white);
3371 	QPen axisPen(axisColor, 1);
3372 
3373 	ui->monthlyElevationGraph->setLocale(QLocale(localeMgr->getAppLanguage()));
3374 	ui->monthlyElevationGraph->xAxis->setLabel(xAxisStr);
3375 	ui->monthlyElevationGraph->yAxis->setLabel(yAxisStr);
3376 
3377 	int dYear = (static_cast<int>(solarSystem->getEarth()->getSiderealPeriod()) + 1) * 86400;
3378 	ui->monthlyElevationGraph->xAxis->setRange(0, dYear);
3379 	ui->monthlyElevationGraph->xAxis->setScaleType(QCPAxis::stLinear);
3380 	ui->monthlyElevationGraph->xAxis->setTickLabelType(QCPAxis::ltDateTime);
3381 	ui->monthlyElevationGraph->xAxis->setLabelColor(axisColor);
3382 	ui->monthlyElevationGraph->xAxis->setTickLabelColor(axisColor);
3383 	ui->monthlyElevationGraph->xAxis->setBasePen(axisPen);
3384 	ui->monthlyElevationGraph->xAxis->setTickPen(axisPen);
3385 	ui->monthlyElevationGraph->xAxis->setSubTickPen(axisPen);
3386 	ui->monthlyElevationGraph->xAxis->setDateTimeFormat("d\nMMM");
3387 	ui->monthlyElevationGraph->xAxis->setDateTimeSpec(Qt::UTC);
3388 	ui->monthlyElevationGraph->xAxis->setAutoTicks(true);
3389 	ui->monthlyElevationGraph->xAxis->setAutoTickCount(15);
3390 
3391 	ui->monthlyElevationGraph->yAxis->setRange(minYme, maxYme);
3392 	ui->monthlyElevationGraph->yAxis->setScaleType(QCPAxis::stLinear);
3393 	ui->monthlyElevationGraph->yAxis->setLabelColor(axisColor);
3394 	ui->monthlyElevationGraph->yAxis->setTickLabelColor(axisColor);
3395 	ui->monthlyElevationGraph->yAxis->setBasePen(axisPen);
3396 	ui->monthlyElevationGraph->yAxis->setTickPen(axisPen);
3397 	ui->monthlyElevationGraph->yAxis->setSubTickPen(axisPen);
3398 
3399 	ui->monthlyElevationGraph->clearGraphs();
3400 	ui->monthlyElevationGraph->addGraph();
3401 	ui->monthlyElevationGraph->setBackground(QBrush(QColor(86, 87, 90)));
3402 	ui->monthlyElevationGraph->graph(0)->setPen(QPen(Qt::red, 1));
3403 	ui->monthlyElevationGraph->graph(0)->setLineStyle(QCPGraph::lsLine);
3404 	ui->monthlyElevationGraph->graph(0)->rescaleAxes(true);
3405 }
3406 
syncMonthlyElevationTime()3407 void AstroCalcDialog::syncMonthlyElevationTime()
3408 {
3409 	ui->monthlyElevationTimeInfo->setText(QString("%1 %2").arg(QString::number(ui->monthlyElevationTime->value()), qc_("h", "time")));
3410 }
3411 
updateMonthlyElevationTime()3412 void AstroCalcDialog::updateMonthlyElevationTime()
3413 {
3414 	syncMonthlyElevationTime();
3415 
3416 	conf->setValue("astrocalc/me_time", ui->monthlyElevationTime->value());
3417 
3418 	drawMonthlyElevationGraph();
3419 }
3420 
saveMonthlyElevationPositiveFlag(bool state)3421 void AstroCalcDialog::saveMonthlyElevationPositiveFlag(bool state)
3422 {
3423 	if (plotMonthlyElevationPositive!=state)
3424 	{
3425 		plotMonthlyElevationPositive = state;
3426 		conf->setValue("astrocalc/me_positive_only", plotMonthlyElevationPositive);
3427 
3428 		drawMonthlyElevationGraph();
3429 	}
3430 }
3431 
saveMonthlyElevationPositiveLimit(int limit)3432 void AstroCalcDialog::saveMonthlyElevationPositiveLimit(int limit)
3433 {
3434 	if (monthlyElevationPositiveLimit!=limit)
3435 	{
3436 		monthlyElevationPositiveLimit = limit;
3437 		conf->setValue("astrocalc/me_positive_limit", monthlyElevationPositiveLimit);
3438 		drawMonthlyElevationGraph();
3439 	}
3440 }
3441 
drawMonthlyElevationGraph()3442 void AstroCalcDialog::drawMonthlyElevationGraph()
3443 {
3444 	ui->monthlyElevationGraph->setToolTip("");
3445 
3446 	// Avoid crash!
3447 	if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
3448 		return;
3449 
3450 	// special case - plot the graph when tab is visible
3451 	//..
3452 	// we got notified about a reason to redraw the plot, but dialog was
3453 	// not visible. which means we must redraw when becoming visible again!
3454 	if (!dialog->isVisible() && plotMonthlyElevation)
3455 	{
3456 		graphPlotNeedsRefresh = true;
3457 		return;
3458 	}
3459 
3460 	if (!plotMonthlyElevation) return;
3461 
3462 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3463 	if (!selectedObjects.isEmpty())
3464 	{
3465 		// X axis - time; Y axis - altitude
3466 		QList<double> aX, aY;
3467 		StelObjectP selectedObject = selectedObjects[0];
3468 		ui->monthlyElevationTitle->setText(selectedObject->getNameI18n());
3469 		if (selectedObject->getType() == "Satellite")
3470 		{
3471 			ui->monthlyElevationGraph->graph(0)->data()->clear();
3472 			ui->monthlyElevationGraph->replot();
3473 			return;
3474 		}
3475 
3476 		const double currentJD = core->getJD();
3477 		int hour = ui->monthlyElevationTime->value();
3478 		double az, alt, deg, startJD, JD, ltime;
3479 		bool sign;
3480 		int year, month, day;
3481 		StelUtils::getDateFromJulianDay(currentJD, &year, &month, &day);
3482 		StelUtils::getJDFromDate(&startJD, year, 1, 1, hour, 0, 0);
3483 		startJD -= core->getUTCOffset(startJD)/24; // Time zone correction
3484 		int dYear = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod()/5.) + 3;
3485 		for (int i = -2; i <= dYear; i++)
3486 		{
3487 			JD = startJD + i*5;
3488 			ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
3489 			aX.append(ltime);
3490 			core->setJD(JD);
3491 			StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
3492 			StelUtils::radToDecDeg(alt, sign, deg);
3493 			if (!sign) deg *= -1;
3494 			aY.append(deg);
3495 			core->update(0.0);
3496 		}
3497 		core->setJD(currentJD);
3498 
3499 		QVector<double> x = aX.toVector(), y = aY.toVector();
3500 		minYme = *std::min_element(aY.begin(), aY.end()) - 2.0;
3501 		maxYme = *std::max_element(aY.begin(), aY.end()) + 2.0;
3502 
3503 		if (plotMonthlyElevationPositive && minYme<monthlyElevationPositiveLimit)
3504 			minYme = monthlyElevationPositiveLimit;
3505 
3506 		prepareMonthlyElevationAxesAndGraph();
3507 
3508 		QString name = selectedObject->getNameI18n();
3509 		if (name.isEmpty())
3510 		{
3511 			QString otype = selectedObject->getType();
3512 			if (otype == "Nebula")
3513 			{
3514 				name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
3515 				if (name.isEmpty())
3516 					name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
3517 			}
3518 			if (otype == "Star" || otype=="Pulsar")
3519 				selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
3520 		}
3521 		ui->monthlyElevationGraph->graph(0)->setData(x, y);
3522 		ui->monthlyElevationGraph->graph(0)->setName(name);
3523 		ui->monthlyElevationGraph->replot();
3524 		ui->monthlyElevationGraph->setToolTip(name);
3525 	}
3526 
3527 	// clean up the data when selection is removed
3528 	if (!objectMgr->getWasSelected())
3529 	{
3530 		ui->monthlyElevationGraph->graph(0)->data()->clear();
3531 		ui->monthlyElevationGraph->replot();
3532 	}
3533 }
3534 
3535 // click inside AltVsTime graph area sets new current time
altTimeClick(QMouseEvent * event)3536 void AstroCalcDialog::altTimeClick(QMouseEvent* event)
3537 {
3538 	Qt::MouseButtons buttons = event->buttons();
3539 	if (!(buttons & Qt::LeftButton)) return;
3540 
3541 	double	x = ui->altVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3542 	double	y = ui->altVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3543 
3544 	if (ui->altVsTimePlot->xAxis->range().contains(x) && ui->altVsTimePlot->yAxis->range().contains(y) )
3545 	{
3546 		setClickedTime(x);
3547 	}
3548 }
3549 
3550 // click inside AziVsTime graph area sets new current time
aziTimeClick(QMouseEvent * event)3551 void AstroCalcDialog::aziTimeClick(QMouseEvent* event)
3552 {
3553 	Qt::MouseButtons buttons = event->buttons();
3554 	if (!(buttons & Qt::LeftButton)) return;
3555 
3556 	double	x = ui->aziVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3557 	double	y = ui->aziVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3558 
3559 	if (ui->aziVsTimePlot->xAxis->range().contains(x) && ui->aziVsTimePlot->yAxis->range().contains(y))
3560 	{
3561 		setClickedTime(x);
3562 	}
3563 }
3564 
3565 
setClickedTime(double posx)3566 void AstroCalcDialog::setClickedTime(double posx)
3567 {
3568 	double JD = core->getJD();
3569 	double shift = core->getUTCOffset(JD) / 24;
3570 	int noonJD = static_cast<int>(JD + shift);
3571 	JD = posx / 86400.0 + noonJD - 0.5 - shift;
3572 
3573 	core->setRealTimeSpeed();
3574 	core->setJD(JD);
3575 	drawCurrentTimeDiagram();
3576 
3577 	// if object is tracked, we make our own (smoothed) movement
3578 	if (mvMgr->getFlagTracking())
3579 	{
3580 		StelObjectP obj = objectMgr->getSelectedObject()[0];
3581 		mvMgr->moveToObject(obj, 0.4f);
3582 	}
3583 }
3584 
3585 // When dialog becomes visible: check if there is a
3586 // graph plot to refresh
handleVisibleEnabled()3587 void AstroCalcDialog::handleVisibleEnabled()
3588 {
3589 	if (dialog->isVisible())
3590 	{
3591 		// check which graph needs refresh (only one is set, if any)
3592 		if (graphPlotNeedsRefresh)
3593 		{
3594 			if (plotAltVsTime || plotAziVsTime)
3595 				drawCurrentTimeDiagram();
3596 			if (plotMonthlyElevation)
3597 				drawMonthlyElevationGraph();
3598 			if (plotAngularDistanceGraph)
3599 				drawAngularDistanceGraph();
3600 		}
3601 		else
3602 			drawCurrentTimeDiagram();
3603 	}
3604 
3605 	graphPlotNeedsRefresh = false;
3606 }
3607 
mouseOverLine(QMouseEvent * event)3608 void AstroCalcDialog::mouseOverLine(QMouseEvent* event)
3609 {
3610 	const double x = ui->altVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3611 	const double y = ui->altVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3612 
3613 	QCPAbstractPlottable* abstractGraph = ui->altVsTimePlot->plottableAt(event->pos(), false);
3614 	QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
3615 
3616 	if (ui->altVsTimePlot->xAxis->range().contains(x) && ui->altVsTimePlot->yAxis->range().contains(y))
3617 	{
3618 		QString info = "";
3619 		if (graph)
3620 		{
3621 			double JD;
3622 			if (graph->name() == "[Now]")
3623 			{
3624 				JD = core->getJD();
3625 				info = q_("Now about %1").arg(StelUtils::jdToQDateTime(JD + core->getUTCOffset(JD)/24).toString("H:mm"));
3626 			}
3627 			else if (graph->name() == "[Transit]")
3628 			{
3629 				JD = transitX / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
3630 				info = q_("Passage of meridian at approximately %1").arg(StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm"));
3631 			}
3632 			else if (graph->name() == "[Sun]")
3633 				info = solarSystem->getSun()->getNameI18n();
3634 			else if (graph->name() == "[Moon]")
3635 				info = solarSystem->getMoon()->getNameI18n();
3636 			else if (graph->name() == "[Civil Twilight]")
3637 				info = q_("Line of civil twilight");
3638 			else if (graph->name() == "[Nautical Twilight]")
3639 				info = q_("Line of nautical twilight");
3640 			else if (graph->name() == "[Astronomical Twilight]")
3641 				info = q_("Line of astronomical twilight");
3642 			else
3643 			{
3644 				JD = x / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
3645 				QString LT = StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm");
3646 
3647 				if (StelApp::getInstance().getFlagShowDecimalDegrees())
3648 					info = QString("%1<br />%2: %3<br />%4: %5%6").arg(ui->altVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Altitude"), QString::number(y, 'f', 2), QChar(0x00B0));
3649 				else
3650 					info = QString("%1<br />%2: %3<br />%4: %5").arg(ui->altVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Altitude"), StelUtils::decDegToDmsStr(y));
3651 			}
3652 		}
3653 		ui->altVsTimePlot->setToolTip(info);
3654 	}
3655 
3656 	ui->altVsTimePlot->update();
3657 	ui->altVsTimePlot->replot();
3658 }
3659 
setPhenomenaHeaderNames()3660 void AstroCalcDialog::setPhenomenaHeaderNames()
3661 {
3662 	phenomenaHeader.clear();
3663 	phenomenaHeader << q_("Phenomenon");
3664 	phenomenaHeader << q_("Date and Time");
3665 	phenomenaHeader << q_("Object 1");
3666 	// TRANSLATORS: Magnitude of object 1
3667 	phenomenaHeader << q_("Mag. 1");
3668 	phenomenaHeader << q_("Object 2");
3669 	// TRANSLATORS: Magnitude of object 2
3670 	phenomenaHeader << q_("Mag. 2");
3671 	phenomenaHeader << q_("Separation");
3672 	phenomenaHeader << q_("Elevation");
3673 	phenomenaHeader << q_("Solar Elongation");
3674 	phenomenaHeader << q_("Lunar Elongation");
3675 	ui->phenomenaTreeWidget->setHeaderLabels(phenomenaHeader);
3676 	adjustPhenomenaColumns();
3677 }
3678 
adjustPhenomenaColumns()3679 void AstroCalcDialog::adjustPhenomenaColumns()
3680 {
3681 	// adjust the column width
3682 	for (int i = 0; i < PhenomenaCount; ++i)
3683 	{
3684 		ui->phenomenaTreeWidget->resizeColumnToContents(i);
3685 	}
3686 }
3687 
initListPhenomena()3688 void AstroCalcDialog::initListPhenomena()
3689 {
3690 	ui->phenomenaTreeWidget->clear();
3691 	ui->phenomenaTreeWidget->setColumnCount(PhenomenaCount);
3692 	setPhenomenaHeaderNames();
3693 	ui->phenomenaTreeWidget->header()->setSectionsMovable(false);
3694 	ui->phenomenaTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
3695 }
3696 
selectCurrentPhenomen(const QModelIndex & modelIndex)3697 void AstroCalcDialog::selectCurrentPhenomen(const QModelIndex& modelIndex)
3698 {
3699 	// Find the object
3700 	QString name = ui->object1ComboBox->currentData().toString();
3701 	if (modelIndex.sibling(modelIndex.row(), PhenomenaType).data().toString().contains(q_("Opposition"), Qt::CaseInsensitive))
3702 		name = modelIndex.sibling(modelIndex.row(), PhenomenaObject2).data().toString();
3703 	double JD = modelIndex.sibling(modelIndex.row(), PhenomenaDate).data(Qt::UserRole).toDouble();
3704 
3705 	if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
3706 	{
3707 		core->setJD(JD);
3708 		const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
3709 		if (!newSelected.empty())
3710 		{
3711 			mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
3712 			mvMgr->setFlagTracking(true);
3713 		}
3714 	}
3715 }
3716 
calculatePhenomena()3717 void AstroCalcDialog::calculatePhenomena()
3718 {
3719 	const QString currentPlanet = ui->object1ComboBox->currentData().toString();
3720 	const double separation = ui->allowedSeparationSpinBox->valueDegrees();
3721 	const bool opposition = ui->phenomenaOppositionCheckBox->isChecked();
3722 	const bool perihelion = ui->phenomenaPerihelionAphelionCheckBox->isChecked();
3723 
3724 	initListPhenomena();
3725 
3726 	double startJD = StelUtils::qDateTimeToJd(QDateTime(ui->phenomenFromDateEdit->date()));
3727 	double stopJD = StelUtils::qDateTimeToJd(QDateTime(ui->phenomenToDateEdit->date().addDays(1)));
3728 	if (stopJD<=startJD) // Stop warming atmosphere!..
3729 		return;
3730 
3731 	QList<PlanetP> objects;
3732 	Q_ASSERT(objects.isEmpty());
3733 	QList<PlanetP> allObjects = solarSystem->getAllPlanets();
3734 
3735 	QList<NebulaP> dso;
3736 	Q_ASSERT(dso.isEmpty());
3737 	QVector<NebulaP> allDSO = dsoMgr->getAllDeepSkyObjects();
3738 
3739 	QList<StelObjectP> star, doubleStar, variableStar;
3740 	Q_ASSERT(star.isEmpty());
3741 	Q_ASSERT(doubleStar.isEmpty());
3742 	Q_ASSERT(variableStar.isEmpty());
3743 	QList<StelObjectP> hipStars = starMgr->getHipparcosStars();
3744 	QList<StelObjectP> carbonStars = starMgr->getHipparcosCarbonStars();
3745 	QList<StelObjectP> bariumStars = starMgr->getHipparcosBariumStars();
3746 	QList<StelACStarData> doubleHipStars = starMgr->getHipparcosDoubleStars();
3747 	QList<StelACStarData> variableHipStars = starMgr->getHipparcosVariableStars();
3748 
3749 	int obj2Type = ui->object2ComboBox->currentData().toInt();
3750 	switch (obj2Type)
3751 	{
3752 		case PHCSolarSystem: // All Solar system objects
3753 			for (const auto& object : allObjects)
3754 			{
3755 				if (object->getPlanetType() != Planet::isUNDEFINED && object->getPlanetType() != Planet::isObserver)
3756 					objects.append(object);
3757 			}
3758 			break;
3759 		case PHCPlanets: // Planets
3760 			for (const auto& object : allObjects)
3761 			{
3762 				if (object->getPlanetType() == Planet::isPlanet && object->getEnglishName() != core->getCurrentPlanet()->getEnglishName() && object->getEnglishName() != currentPlanet)
3763 					objects.append(object);
3764 			}
3765 			break;
3766 		case PHCAsteroids:
3767 		case PHCPlutinos:
3768 		case PHCComets:
3769 		case PHCDwarfPlanets:
3770 		case PHCCubewanos:
3771 		case PHCScatteredDiscObjects:
3772 		case PHCOortCloudObjects:
3773 		case PHCSednoids:
3774 		case PHCInterstellarObjects:
3775 		{
3776 			static const QMap<int, Planet::PlanetType>map = {
3777 				{PHCAsteroids, Planet::isAsteroid},
3778 				{PHCPlutinos, Planet::isPlutino},
3779 				{PHCComets, Planet::isComet},
3780 				{PHCDwarfPlanets, Planet::isDwarfPlanet},
3781 				{PHCCubewanos, Planet::isCubewano},
3782 				{PHCScatteredDiscObjects, Planet::isSDO},
3783 				{PHCOortCloudObjects, Planet::isOCO},
3784 				{PHCSednoids, Planet::isSednoid},
3785 				{PHCInterstellarObjects, Planet::isInterstellar}};
3786 			const Planet::PlanetType pType = map.value(obj2Type, Planet::isUNDEFINED);
3787 
3788 			for (const auto& object : allObjects)
3789 			{
3790 				if (object->getPlanetType() == pType)
3791 					objects.append(object);
3792 			}
3793 			break;
3794 		}
3795 		case PHCBrightStars: // Stars
3796 		case PHCBrightCarbonStars: // Bright carbon stars
3797 		case PHCBrightBariumStars: // Bright barium stars
3798 		{
3799 			QList<StelObjectP> stars;
3800 			if (obj2Type==PHCBrightStars)
3801 				stars = hipStars;
3802 			else if (obj2Type==PHCBrightBariumStars)
3803 				stars = bariumStars;
3804 			else
3805 				stars = carbonStars;
3806 			for (const auto& object : qAsConst(stars))
3807 			{
3808 				if (object->getVMagnitude(core) < (brightLimit - 5.0))
3809 					star.append(object);
3810 			}
3811 			break;
3812 		}
3813 		case PHCBrightDoubleStars: // Double stars
3814 			for (const auto& object : doubleHipStars)
3815 			{
3816 				if (object.firstKey()->getVMagnitude(core) < (brightLimit - 5.0))
3817 					star.append(object.firstKey());
3818 			}
3819 			break;
3820 		case PHCBrightVariableStars: // Variable stars
3821 			for (const auto& object : variableHipStars)
3822 			{
3823 				if (object.firstKey()->getVMagnitude(core) < (brightLimit - 5.0))
3824 					star.append(object.firstKey());
3825 			}
3826 			break;
3827 		case PHCBrightStarClusters: // Star clusters
3828 			for (const auto& object : allDSO)
3829 			{
3830 				if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebCl || object->getDSOType() == Nebula::NebOc || object->getDSOType() == Nebula::NebGc || object->getDSOType() == Nebula::NebSA || object->getDSOType() == Nebula::NebSC || object->getDSOType() == Nebula::NebCn))
3831 					dso.append(object);
3832 			}
3833 			break;
3834 		case PHCPlanetaryNebulae: // Planetary nebulae
3835 			for (const auto& object : allDSO)
3836 			{
3837 				if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebPn || object->getDSOType() == Nebula::NebPossPN || object->getDSOType() == Nebula::NebPPN))
3838 					dso.append(object);
3839 			}
3840 			break;
3841 		case PHCBrightNebulae: // Bright nebulae
3842 			for (const auto& object : allDSO)
3843 			{
3844 				if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebN || object->getDSOType() == Nebula::NebBn || object->getDSOType() == Nebula::NebEn || object->getDSOType() == Nebula::NebRn || object->getDSOType() == Nebula::NebHII || object->getDSOType() == Nebula::NebISM || object->getDSOType() == Nebula::NebCn || object->getDSOType() == Nebula::NebSNR))
3845 					dso.append(object);
3846 			}
3847 			break;
3848 		case PHCDarkNebulae: // Dark nebulae
3849 			for (const auto& object : allDSO)
3850 			{
3851 				if (object->getDSOType() == Nebula::NebDn || object->getDSOType() == Nebula::NebMolCld || object->getDSOType() == Nebula::NebYSO)
3852 					dso.append(object);
3853 			}
3854 			break;
3855 		case PHCBrightGalaxies: // Galaxies
3856 			for (const auto& object : allDSO)
3857 			{
3858 				if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebGx || object->getDSOType() == Nebula::NebAGx || object->getDSOType() == Nebula::NebRGx || object->getDSOType() == Nebula::NebQSO || object->getDSOType() == Nebula::NebPossQSO || object->getDSOType() == Nebula::NebBLL || object->getDSOType() == Nebula::NebBLA || object->getDSOType() == Nebula::NebIGx))
3859 					dso.append(object);
3860 			}
3861 			break;
3862 		case PHCSymbioticStars: // Symbiotic stars
3863 			for (const auto& object : allDSO)
3864 			{
3865 				if (object->getDSOType() == Nebula::NebSymbioticStar)
3866 					dso.append(object);
3867 			}
3868 			break;
3869 		case PHCEmissionLineStars: // Emission-line stars
3870 			for (const auto& object : allDSO)
3871 			{
3872 				if (object->getDSOType() == Nebula::NebEmissionLineStar)
3873 					dso.append(object);
3874 			}
3875 			break;
3876 		case PHCPlanetsSun: // Planets and Sun
3877 			for (const auto& object : allObjects)
3878 			{
3879 				if ((object->getPlanetType() == Planet::isPlanet || object->getPlanetType() == Planet::isStar) && object->getEnglishName() != core->getCurrentPlanet()->getEnglishName() && object->getEnglishName() != currentPlanet)
3880 					objects.append(object);
3881 			}
3882 			break;
3883 		case PHCSunPlanetsMoons: // Sun, planets and moons of observer location
3884 		{
3885 			PlanetP cp = core->getCurrentPlanet();
3886 			for (const auto& object : allObjects)
3887 			{
3888 				if ((object->getPlanetType() == Planet::isPlanet || object->getPlanetType() == Planet::isStar || (object->getParent()==cp && object->getPlanetType()==Planet::isMoon)) && object->getEnglishName() != cp->getEnglishName() && object->getEnglishName() != currentPlanet)
3889 					objects.append(object);
3890 			}
3891 			break;
3892 		}
3893 		case PHCBrightSolarSystemObjects: // Bright Solar system objects
3894 			for (const auto& object : allObjects)
3895 			{
3896 				if (object->getVMagnitude(core) < (brightLimit + 2.0f) && object->getPlanetType() != Planet::isUNDEFINED)
3897 					objects.append(object);
3898 			}
3899 			break;
3900 		case PHCSolarSystemMinorBodies: // Minor bodies of Solar system
3901 			for (const auto& object : allObjects)
3902 			{
3903 				if (object->getPlanetType() != Planet::isUNDEFINED && object->getPlanetType() != Planet::isPlanet && object->getPlanetType() != Planet::isStar && object->getPlanetType() != Planet::isMoon && object->getPlanetType() != Planet::isComet && object->getPlanetType() != Planet::isArtificial && object->getPlanetType() != Planet::isObserver && !object->getEnglishName().contains("Pluto", Qt::CaseInsensitive))
3904 					objects.append(object);
3905 			}
3906 			break;
3907 		case PHCMoonsFirstBody: // Moons of first body
3908 			PlanetP firstPplanet = solarSystem->searchByEnglishName(currentPlanet);
3909 			for (const auto& object : allObjects)
3910 			{
3911 				if (object->getParent()==firstPplanet && object->getPlanetType() == Planet::isMoon)
3912 					objects.append(object);
3913 			}
3914 			break;
3915 	}
3916 
3917 	PlanetP planet = solarSystem->searchByEnglishName(currentPlanet);
3918 	PlanetP sun = solarSystem->getSun();
3919 	if (planet)
3920 	{
3921 		const double currentJD = core->getJD();   // save current JD
3922 		startJD = startJD - core->getUTCOffset(startJD) / 24.;
3923 		stopJD = stopJD - core->getUTCOffset(stopJD) / 24.;
3924 
3925 		if (obj2Type == PHCLatestSelectedObject)
3926 		{
3927 			QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3928 			if (!selectedObjects.isEmpty())
3929 			{
3930 				StelObjectP selectedObject = selectedObjects[0];
3931 				if (selectedObject!=planet && selectedObject->getType() != "Satellite")
3932 				{
3933 					// conjunction
3934 					fillPhenomenaTable(findClosestApproach(planet, selectedObject, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, selectedObject, PhenomenaTypeIndex::Conjunction);
3935 					// opposition
3936 					if (opposition)
3937 						fillPhenomenaTable(findClosestApproach(planet, selectedObject, startJD, stopJD, separation, PhenomenaTypeIndex::Opposition), planet, selectedObject, PhenomenaTypeIndex::Opposition);
3938 				}
3939 			}
3940 		}
3941 		else if ((obj2Type >= PHCSolarSystem && obj2Type < PHCBrightStars) || (obj2Type >= PHCInterstellarObjects && obj2Type <= PHCMoonsFirstBody))
3942 		{
3943 			// Solar system objects
3944 			for (auto& obj : objects)
3945 			{
3946 				// conjunction
3947 				StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3948 				fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj, PhenomenaTypeIndex::Conjunction);
3949 				// opposition
3950 				if (opposition)
3951 					fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Opposition), planet, obj, PhenomenaTypeIndex::Opposition);
3952 				// shadows from moons
3953 				if (obj2Type==PHCMoonsFirstBody || obj2Type==PHCSolarSystem || obj2Type==PHCBrightSolarSystemObjects)
3954 					fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Shadows), planet, obj, PhenomenaTypeIndex::Shadows);
3955 			}
3956 		}
3957 		else if (obj2Type == PHCBrightStars || obj2Type == PHCBrightDoubleStars || obj2Type == PHCBrightVariableStars || obj2Type == PHCBrightCarbonStars || obj2Type == PHCBrightBariumStars)
3958 		{
3959 			// Stars
3960 			for (auto& obj : star)
3961 			{
3962 				// conjunction
3963 				StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3964 				fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj, PhenomenaTypeIndex::Conjunction);
3965 			}
3966 		}
3967 		else
3968 		{
3969 			// Deep-sky objects
3970 			for (auto& obj : dso)
3971 			{
3972 				// conjunction
3973 				StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3974 				fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj);
3975 			}
3976 		}
3977 
3978 		if (planet!=sun && planet->getPlanetType()!=Planet::isMoon)
3979 		{
3980 			StelObjectP mObj = qSharedPointerCast<StelObject>(sun);
3981 			if (planet->getHeliocentricEclipticPos().length()<core->getCurrentPlanet()->getHeliocentricEclipticPos().length())
3982 			{
3983 				// greatest elongations for inner planets
3984 				fillPhenomenaTable(findGreatestElongationApproach(planet, mObj, startJD, stopJD), planet, sun, PhenomenaTypeIndex::GreatestElongation);
3985 			}
3986 			// stationary points
3987 			fillPhenomenaTable(findStationaryPointApproach(planet, startJD, stopJD), planet, sun, PhenomenaTypeIndex::StationaryPoint);
3988 			// perihelion and aphelion points
3989 			if (perihelion)
3990 				fillPhenomenaTable(findOrbitalPointApproach(planet, startJD, stopJD), planet, sun, PhenomenaTypeIndex::OrbitalPoint);
3991 		}
3992 
3993 		core->setJD(currentJD); // restore time
3994 		core->update(0);
3995 	}
3996 
3997 	// adjust the column width
3998 	adjustPhenomenaColumns();
3999 	// sort-by-date
4000 	ui->phenomenaTreeWidget->sortItems(PhenomenaDate, Qt::AscendingOrder);
4001 }
4002 
savePhenomena()4003 void AstroCalcDialog::savePhenomena()
4004 {
4005 	QString filter = q_("Microsoft Excel Open XML Spreadsheet");
4006 	filter.append(" (*.xlsx);;");
4007 	filter.append(q_("CSV (Comma delimited)"));
4008 	filter.append(" (*.csv)");
4009 	QString defaultFilter("(*.xlsx)");
4010 	QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
4011 							q_("Save calculated phenomena as..."),
4012 							QDir::homePath() + "/phenomena.xlsx",
4013 							filter,
4014 							&defaultFilter);
4015 
4016 	if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
4017 		saveTableAsCSV(filePath, ui->phenomenaTreeWidget, phenomenaHeader);
4018 	else
4019 	{
4020 		int count = ui->phenomenaTreeWidget->topLevelItemCount();
4021 		int columns = phenomenaHeader.size();
4022 
4023 		int *width;
4024 		width = new int[columns];
4025 		QString sData;
4026 		int w;
4027 
4028 		QXlsx::Document xlsx;
4029 		xlsx.setDocumentProperty("title", q_("Phenomena"));
4030 		xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
4031 		xlsx.addSheet(q_("Phenomena"), AbstractSheet::ST_WorkSheet);
4032 
4033 		QXlsx::Format header;
4034 		header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
4035 		header.setPatternBackgroundColor(Qt::yellow);
4036 		header.setBorderStyle(QXlsx::Format::BorderThin);
4037 		header.setBorderColor(Qt::black);
4038 		header.setFontBold(true);
4039 		for (int i = 0; i < columns; i++)
4040 		{
4041 			// Row 1: Names of columns
4042 			sData = phenomenaHeader.at(i).trimmed();
4043 			xlsx.write(1, i + 1, sData, header);
4044 			width[i] = sData.size();
4045 		}
4046 
4047 		QXlsx::Format data;
4048 		data.setHorizontalAlignment(QXlsx::Format::AlignRight);
4049 		for (int i = 0; i < count; i++)
4050 		{
4051 			for (int j = 0; j < columns; j++)
4052 			{
4053 				// Row 2 and next: the data
4054 				sData = ui->phenomenaTreeWidget->topLevelItem(i)->text(j).trimmed();
4055 				xlsx.write(i + 2, j + 1, sData, data);
4056 				w = sData.size();
4057 				if (w > width[j])
4058 				{
4059 					width[j] = w;
4060 				}
4061 			}
4062 		}
4063 
4064 		for (int i = 0; i < columns; i++)
4065 		{
4066 			xlsx.setColumnWidth(i+1, width[i]+2);
4067 		}
4068 
4069 		delete[] width;
4070 		xlsx.saveAs(filePath);
4071 	}
4072 }
4073 
fillPhenomenaTableVis(QString phenomenType,double JD,QString firstObjectName,float firstObjectMagnitude,QString secondObjectName,float secondObjectMagnitude,QString separation,QString elevation,QString elongation,QString angularDistance,QString elongTooltip,QString angDistTooltip)4074 void AstroCalcDialog::fillPhenomenaTableVis(QString phenomenType, double JD, QString firstObjectName, float firstObjectMagnitude,
4075 					    QString secondObjectName, float secondObjectMagnitude, QString separation, QString elevation,
4076 					    QString elongation, QString angularDistance, QString elongTooltip, QString angDistTooltip)
4077 {
4078 	ACPhenTreeWidgetItem* treeItem = new ACPhenTreeWidgetItem(ui->phenomenaTreeWidget);
4079 	treeItem->setText(PhenomenaType, phenomenType);
4080 	// local date and time
4081 	treeItem->setText(PhenomenaDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD)));
4082 	treeItem->setData(PhenomenaDate, Qt::UserRole, JD);
4083 	treeItem->setText(PhenomenaObject1, firstObjectName);
4084 	treeItem->setText(PhenomenaMagnitude1, (firstObjectMagnitude > 90.f ? dash : QString::number(firstObjectMagnitude, 'f', 2)));
4085 	treeItem->setTextAlignment(PhenomenaMagnitude1, Qt::AlignRight);
4086 	treeItem->setToolTip(PhenomenaMagnitude1, q_("Magnitude of first object"));
4087 	treeItem->setText(PhenomenaObject2, secondObjectName);
4088 	treeItem->setText(PhenomenaMagnitude2, (secondObjectMagnitude > 90.f ? dash : QString::number(secondObjectMagnitude, 'f', 2)));
4089 	treeItem->setToolTip(PhenomenaMagnitude2, q_("Magnitude of second object"));
4090 	treeItem->setTextAlignment(PhenomenaMagnitude2, Qt::AlignRight);
4091 	treeItem->setText(PhenomenaSeparation, separation);
4092 	treeItem->setTextAlignment(PhenomenaSeparation, Qt::AlignRight);
4093 	treeItem->setText(PhenomenaElevation, elevation);
4094 	treeItem->setTextAlignment(PhenomenaElevation, Qt::AlignRight);
4095 	treeItem->setToolTip(PhenomenaElevation, q_("Elevation of first object at moment of phenomena"));
4096 	treeItem->setText(PhenomenaElongation, elongation);
4097 	treeItem->setToolTip(PhenomenaElongation, elongTooltip.isEmpty() ? q_("Angular distance from the Sun") : elongTooltip);
4098 	treeItem->setTextAlignment(PhenomenaElongation, Qt::AlignRight);
4099 	treeItem->setText(PhenomenaAngularDistance, angularDistance);
4100 	treeItem->setToolTip(PhenomenaAngularDistance, angDistTooltip.isEmpty() ? q_("Angular distance from the Moon") : angDistTooltip);
4101 	treeItem->setTextAlignment(PhenomenaAngularDistance, Qt::AlignRight);
4102 }
4103 
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const PlanetP object2,int mode)4104 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const PlanetP object2, int mode)
4105 {
4106 	QMap<double, double>::ConstIterator it;
4107 	PlanetP sun = solarSystem->getSun();
4108 	PlanetP moon = solarSystem->getMoon();
4109 	PlanetP earth = solarSystem->getEarth();
4110 	PlanetP planet = core->getCurrentPlanet();
4111 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4112 	double az, alt;
4113 	for (it = list.constBegin(); it != list.constEnd(); ++it)
4114 	{
4115 		core->setJD(it.key());
4116 		core->update(0);
4117 
4118 		QString phenomenType = q_("Conjunction");
4119 		double separation = it.value();
4120 		bool occultation = false;
4121 		const double s1 = object1->getSpheroidAngularSize(core);
4122 		const double s2 = object2->getSpheroidAngularSize(core);
4123 		const double d1 = object1->getJ2000EquatorialPos(core).length();
4124 		const double d2 = object2->getJ2000EquatorialPos(core).length();
4125 		if (mode==PhenomenaTypeIndex::Shadows) // shadows
4126 		{
4127 			phenomenType = q_("Shadow transit");
4128 			separation = object1->getJ2000EquatorialPos(core).angle(object2->getJ2000EquatorialPos(core));
4129 			if (d1<d2) // the moon is behind planet, so the moon in shadow
4130 				phenomenType = q_("Eclipse");
4131 		}
4132 		else if (mode==PhenomenaTypeIndex::Opposition) // opposition
4133 		{
4134 			phenomenType = q_("Opposition");
4135 			// Added a special case - lunar eclipse
4136 			if (qAbs(separation) <= 0.02 && ((object1 == moon  && object2 == sun) || (object1 == sun  && object2 == moon)))
4137 				phenomenType = q_("Eclipse");
4138 
4139 			separation = M_PI - separation;
4140 		}
4141 		else if (mode==PhenomenaTypeIndex::GreatestElongation) // greatest elongations
4142 		{
4143 			if (separation < 0.0) // we use negative value for eastern elongations!
4144 			{
4145 				separation *= -1.0;
4146 				phenomenType = q_("Greatest eastern elongation");
4147 			}
4148 			else
4149 				phenomenType = q_("Greatest western elongation");
4150 		}
4151 		else if (mode==PhenomenaTypeIndex::StationaryPoint) // stationary points
4152 		{
4153 			if (separation < 0.0) // we use negative value for start retrograde motion!
4154 			{
4155 				// TRANSLATORS: The planet are stand still in the equatorial coordinates
4156 				phenomenType = q_("Stationary (begin retrograde motion)");
4157 			}
4158 			else
4159 			{
4160 				// TRANSLATORS: The planet are stand still in the equatorial coordinates
4161 				phenomenType = q_("Stationary (begin prograde motion)");
4162 			}
4163 		}
4164 		else if (mode==PhenomenaTypeIndex::OrbitalPoint)
4165 		{
4166 			if (separation < 0.0) // we use negative value for perihelion!
4167 				phenomenType = q_("Perihelion");
4168 			else
4169 				phenomenType = q_("Aphelion");
4170 		}
4171 		else if (separation < (s2 * M_PI / 180.) || separation < (s1 * M_PI / 180.))
4172 		{
4173 			if ((d1 < d2 && s1 <= s2) || (d1 > d2 && s1 > s2))
4174 			{
4175 				// The passage of the celestial body in front of another of greater apparent diameter
4176 				phenomenType = qc_("Transit", "passage of the celestial body");
4177 			}
4178 			else
4179 				phenomenType = q_("Occultation");
4180 
4181 			// Added a special case - solar eclipse
4182 			if (qAbs(s1 - s2) <= 0.05 && (object1 == sun || object2 == sun)) // 5% error of difference of sizes
4183 				phenomenType = q_("Eclipse");
4184 
4185 			occultation = true;
4186 		}
4187 		else if (qAbs(separation) <= 0.0087 && ((object1 == moon  && object2 == sun) || (object1 == sun  && object2 == moon))) // Added a special case - partial solar eclipse
4188 		{
4189 			phenomenType = q_("Eclipse");
4190 		}
4191 		else if (object1 == sun || object2 == sun) // this is may be superior of inferior conjunction for inner planet
4192 		{
4193 			double dcp = planet->getHeliocentricEclipticPos().length();
4194 			double dp  = (object1 == sun) ? object2->getHeliocentricEclipticPos().length() :
4195 							object1->getHeliocentricEclipticPos().length();
4196 			if (dp < dcp) // OK, it's inner planet
4197 			{
4198 				if (object1 == sun)
4199 					phenomenType = d1<d2 ? q_("Superior conjunction") : q_("Inferior conjunction");
4200 				else
4201 					phenomenType = d2<d1 ? q_("Superior conjunction") : q_("Inferior conjunction");
4202 			}
4203 		}
4204 
4205 		QString elongStr = "";
4206 		if (((object1 == sun || object2 == sun) && mode==PhenomenaTypeIndex::Conjunction) || (object2 == sun && mode==PhenomenaTypeIndex::Opposition))
4207 			elongStr = dash;
4208 		else
4209 		{
4210 			double elongation = object1->getElongation(core->getObserverHeliocentricEclipticPos());
4211 			if (mode==PhenomenaTypeIndex::Opposition) // calculate elongation for the second object in this case!
4212 				elongation = object2->getElongation(core->getObserverHeliocentricEclipticPos());
4213 
4214 			if (withDecimalDegree)
4215 				elongStr = StelUtils::radToDecDegStr(elongation, 5, false, true);
4216 			else
4217 				elongStr = StelUtils::radToDmsStr(elongation, true);
4218 		}
4219 
4220 		QString angDistStr = "";
4221 		if (planet != earth)
4222 			angDistStr = dash;
4223 		else
4224 		{
4225 			if (object1 == moon || object2 == moon)
4226 				angDistStr = dash;
4227 			else
4228 			{
4229 				double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4230 				if (mode==1) // calculate elongation for the second object in this case!
4231 					angularDistance = object2->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4232 
4233 				if (withDecimalDegree)
4234 					angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4235 				else
4236 					angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4237 			}
4238 		}
4239 
4240 		QString elongationInfo = q_("Angular distance from the Sun");
4241 		QString angularDistanceInfo = q_("Angular distance from the Moon");
4242 		if (mode==1)
4243 		{
4244 			elongationInfo = q_("Angular distance from the Sun for second object");
4245 			angularDistanceInfo = q_("Angular distance from the Moon for second object");
4246 		}
4247 
4248 		QString separationStr = dash;
4249 		float magnitude = object2->getVMagnitude(core);
4250 		if (!occultation)
4251 		{
4252 			if (withDecimalDegree)
4253 				separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4254 			else
4255 				separationStr = StelUtils::radToDmsStr(separation, true);
4256 		}
4257 		else
4258 			magnitude = 99.f; // Let's hide obviously wrong data
4259 
4260 		QString nameObj2 = object2->getNameI18n();
4261 		if (mode==PhenomenaTypeIndex::StationaryPoint)
4262 		{
4263 			nameObj2 = dash;
4264 			magnitude = 99.f;
4265 			separationStr = dash;
4266 		}
4267 
4268 		QString elevationStr = dash;
4269 		StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4270 		if (withDecimalDegree)
4271 			elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4272 		else
4273 			elevationStr = StelUtils::radToDmsPStr(alt, 2);
4274 
4275 		fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), nameObj2, magnitude, separationStr, elevationStr, elongStr, angDistStr, elongationInfo, angularDistanceInfo);
4276 	}
4277 }
4278 
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const NebulaP object2)4279 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const NebulaP object2)
4280 {
4281 	QMap<double, double>::ConstIterator it;
4282 	PlanetP sun = solarSystem->getSun();
4283 	PlanetP moon = solarSystem->getMoon();
4284 	PlanetP earth = solarSystem->getEarth();
4285 	PlanetP planet = core->getCurrentPlanet();
4286 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4287 	double az, alt;
4288 	for (it = list.constBegin(); it != list.constEnd(); ++it)
4289 	{
4290 		core->setJD(it.key());
4291 		core->update(0);
4292 
4293 		QString phenomenType = q_("Conjunction");
4294 		double separation = it.value();
4295 		bool occultation = false;
4296 		if (separation < (object2->getAngularSize(core) * M_PI / 180.) || separation < (object1->getSpheroidAngularSize(core) * M_PI / 180.))
4297 		{
4298 			phenomenType = q_("Occultation");
4299 			occultation = true;
4300 		}
4301 
4302 		QString elongStr = "";
4303 		if (object1 == sun)
4304 			elongStr = dash;
4305 		else
4306 		{
4307 			if (withDecimalDegree)
4308 				elongStr = StelUtils::radToDecDegStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), 5, false, true);
4309 			else
4310 				elongStr = StelUtils::radToDmsStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), true);
4311 		}
4312 
4313 		QString angDistStr = "";
4314 		if (planet != earth)
4315 			angDistStr = dash;
4316 		else
4317 		{
4318 			if (object1 == moon)
4319 				angDistStr = dash;
4320 			else
4321 			{
4322 				double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4323 				if (withDecimalDegree)
4324 					angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4325 				else
4326 					angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4327 			}
4328 		}
4329 
4330 		QString commonName = object2->getNameI18n();
4331 		if (commonName.isEmpty())
4332 			commonName = object2->getDSODesignation();
4333 		if (commonName.isEmpty())
4334 			commonName = object2->getDSODesignationWIC();
4335 
4336 		QString separationStr = dash;
4337 		float magnitude = object2->getVMagnitude(core);
4338 		if (!occultation)
4339 		{
4340 			if (withDecimalDegree)
4341 				separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4342 			else
4343 				separationStr = StelUtils::radToDmsStr(separation, true);
4344 		}
4345 		else
4346 			magnitude = 99.f; // Let's hide obviously wrong data
4347 
4348 		QString elevationStr = dash;
4349 		StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4350 		if (withDecimalDegree)
4351 			elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4352 		else
4353 			elevationStr = StelUtils::radToDmsPStr(alt, 2);
4354 
4355 		fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), commonName, magnitude, separationStr, elevationStr, elongStr, angDistStr);
4356 	}
4357 }
4358 
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const StelObjectP object2,int mode=0)4359 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const StelObjectP object2, int mode = 0)
4360 {
4361 	QMap<double, double>::ConstIterator it;
4362 	PlanetP sun = solarSystem->getSun();
4363 	PlanetP moon = solarSystem->getMoon();
4364 	PlanetP earth = solarSystem->getEarth();
4365 	PlanetP planet = core->getCurrentPlanet();
4366 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4367 	double az, alt;
4368 	for (it = list.constBegin(); it != list.constEnd(); ++it)
4369 	{
4370 		core->setJD(it.key());
4371 		core->update(0);
4372 
4373 		QString phenomenType = q_("Conjunction");
4374 		double separation = it.value();
4375 		bool occultation = false;
4376 		const double s1 = object1->getSpheroidAngularSize(core);
4377 		const double s2 = object2->getAngularSize(core);
4378 		const double d1 = object1->getJ2000EquatorialPos(core).length();
4379 		const double d2 = object2->getJ2000EquatorialPos(core).length();
4380 		if (mode==PhenomenaTypeIndex::Opposition)
4381 		{
4382 			phenomenType = q_("Opposition");
4383 			// Added a special case - lunar eclipse
4384 			if (qAbs(separation) <= 0.02 && ((object1 == moon  && object2 == sun) || (object1 == sun  && object2 == moon)))
4385 				phenomenType = q_("Eclipse");
4386 
4387 			separation = M_PI - separation;
4388 		}
4389 		else if (separation < (s2 * M_PI / 180.) || separation < (s1 * M_PI / 180.))
4390 		{
4391 			if ((d1 < d2 && s1 <= s2) || (d1 > d2 && s1 > s2))
4392 			{
4393 				// The passage of the celestial body in front of another of greater apparent diameter
4394 				phenomenType = qc_("Transit", "passage of the celestial body");
4395 			}
4396 			else
4397 				phenomenType = q_("Occultation");
4398 
4399 			// Added a special case - solar eclipse
4400 			if (qAbs(s1 - s2) <= 0.05 && (object1 == sun || object2 == sun)) // 5% error of difference of sizes
4401 				phenomenType = q_("Eclipse");
4402 
4403 			occultation = true;
4404 		}
4405 		else if (qAbs(separation) <= 0.0087 && ((object1 == moon  && object2 == sun) || (object1 == sun  && object2 == moon))) // Added a special case - partial solar eclipse
4406 		{
4407 			phenomenType = q_("Eclipse");
4408 		}
4409 		else if (object1 == sun || object2 == sun) // this is may be superior of inferior conjunction for inner planet
4410 		{
4411 			double dcp = (planet->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4412 			double dp;
4413 			if (object1 == sun)
4414 				dp = (object2->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4415 			else
4416 				dp = (object1->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4417 			if (dp < dcp) // OK, it's inner planet
4418 			{
4419 				if (object1 == sun)
4420 					phenomenType = d1<d2 ? q_("Superior conjunction") : q_("Inferior conjunction");
4421 				else
4422 					phenomenType = d2<d1 ? q_("Superior conjunction") : q_("Inferior conjunction");
4423 			}
4424 		}
4425 
4426 		QString elongStr = "";
4427 		if (object1 == sun)
4428 			elongStr = dash;
4429 		else
4430 		{
4431 			if (withDecimalDegree)
4432 				elongStr = StelUtils::radToDecDegStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), 5, false, true);
4433 			else
4434 				elongStr = StelUtils::radToDmsStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), true);
4435 		}
4436 
4437 		QString angDistStr = "";
4438 		if (planet != earth)
4439 			angDistStr = dash;
4440 		else
4441 		{
4442 			if (object1 == moon)
4443 				angDistStr = dash;
4444 			else
4445 			{
4446 				double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4447 				if (withDecimalDegree)
4448 					angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4449 				else
4450 					angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4451 			}
4452 		}
4453 
4454 		QString commonName = object2->getNameI18n();
4455 		if (commonName.isEmpty())
4456 			commonName = object2->getID();
4457 
4458 		QString separationStr = dash;
4459 		float magnitude = object2->getVMagnitude(core);
4460 		if (!occultation)
4461 		{
4462 			if (withDecimalDegree)
4463 				separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4464 			else
4465 				separationStr = StelUtils::radToDmsStr(separation, true);
4466 		}
4467 		else
4468 			magnitude = 99.f; // Let's hide obviously wrong data
4469 
4470 		QString elevationStr = dash;
4471 		StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4472 		if (withDecimalDegree)
4473 			elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4474 		else
4475 			elevationStr = StelUtils::radToDmsPStr(alt, 2);
4476 
4477 		fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), commonName, magnitude, separationStr, elevationStr, elongStr, angDistStr);
4478 	}
4479 }
4480 
findInitialStep(double startJD,double stopJD,QStringList objects)4481 double AstroCalcDialog::findInitialStep(double startJD, double stopJD, QStringList objects)
4482 {
4483 	double step = (stopJD - startJD) / 16.0;
4484 	double limit = 24.8 * 365.25;
4485 	QRegularExpression mp("^[(](\\d+)[)]\\s(.+)$");
4486 
4487 	if (objects.contains("Moon", Qt::CaseInsensitive))
4488 		limit = 0.25;
4489 	else if (objects.contains("C/",Qt::CaseInsensitive) || objects.contains("P/",Qt::CaseInsensitive))
4490 		limit = 0.5;
4491 	else if (objects.contains("Earth",Qt::CaseInsensitive) || objects.contains("Sun", Qt::CaseInsensitive))
4492 		limit = 1.;
4493 	else if (objects.contains("Venus",Qt::CaseInsensitive) || objects.contains("Mercury", Qt::CaseInsensitive))
4494 		limit = 2.5;
4495 	else if (objects.contains("Mars",Qt::CaseInsensitive))
4496 		limit = 5.;
4497 	else if (objects.indexOf(mp)>=0)
4498 		limit = 10.;
4499 	else if (objects.contains("Jupiter", Qt::CaseInsensitive) || objects.contains("Saturn", Qt::CaseInsensitive))
4500 		limit = 15.;
4501 	else if (objects.contains("Neptune", Qt::CaseInsensitive) || objects.contains("Uranus", Qt::CaseInsensitive) || objects.contains("Pluto",Qt::CaseInsensitive))
4502 		limit = 20.;
4503 
4504 	if (step > limit)
4505 		step = limit;
4506 
4507 	return step;
4508 }
4509 
findClosestApproach(PlanetP & object1,StelObjectP & object2,const double startJD,const double stopJD,const double maxSeparation,const int mode)4510 QMap<double, double> AstroCalcDialog::findClosestApproach(PlanetP& object1, StelObjectP& object2, const double startJD, const double stopJD, const double maxSeparation, const int mode)
4511 {
4512 	QMap<double, double> separations;
4513 	QPair<double, double> extremum;
4514 
4515 	if (object2->getType()=="Planet")
4516 	{
4517 		PlanetP planet = qSharedPointerCast<Planet>(object2);
4518 		// We shouldn't compute eclipses and shadow transits not for planets and their moons
4519 		if (mode==PhenomenaTypeIndex::Shadows && object2->getEnglishName()!="Sun" && planet->getParent()!=object1)
4520 			return separations;
4521 
4522 		// If we don't have at least partial overlap between planet valid dates and our interval, skip by returning an empty map.
4523 		const Vec2d planetValidityLimits=planet->getValidPositionalDataRange(Planet::PositionQuality::Position);
4524 		if ( (planetValidityLimits[0] > stopJD) || (planetValidityLimits[1] < startJD) )
4525 			return separations;
4526 	}
4527 
4528 	QStringList objects;
4529 	Q_ASSERT(objects.isEmpty());
4530 	objects.append(object1->getEnglishName());
4531 	objects.append(object2->getEnglishName());
4532 	double step0 = findInitialStep(startJD, stopJD, objects);
4533 	double step = step0;
4534 	double jd = startJD;
4535 	double prevDist = findDistance(jd, object1, object2, mode);
4536 	int prevSgn = 0;
4537 	jd += step;
4538 	while (jd <= stopJD)
4539 	{
4540 		double dist = findDistance(jd, object1, object2, mode);
4541 		int sgn = StelUtils::sign(dist - prevDist);
4542 
4543 		double factor = qAbs((dist - prevDist) / dist);
4544 		if (factor > 10.)
4545 			step = step0 * factor / 10.;
4546 		else
4547 			step = step0;
4548 
4549 		if (sgn != prevSgn && prevSgn == -1)
4550 		{
4551 			if (step > step0)
4552 			{
4553 				jd -= step;
4554 				step = step0;
4555 				sgn = prevSgn;
4556 				while (jd <= stopJD)
4557 				{
4558 					dist = findDistance(jd, object1, object2, mode);
4559 					sgn = StelUtils::sign(dist - prevDist);
4560 					if (sgn != prevSgn)
4561 						break;
4562 
4563 					prevDist = dist;
4564 					prevSgn = sgn;
4565 					jd += step;
4566 				}
4567 			}
4568 
4569 			if (findPrecise(&extremum, object1, object2, jd, step, sgn, mode))
4570 			{
4571 				double sep = extremum.second * 180. / M_PI;
4572 				if (sep < maxSeparation)
4573 					separations.insert(extremum.first, extremum.second);
4574 			}
4575 		}
4576 
4577 		prevDist = dist;
4578 		prevSgn = sgn;
4579 		jd += step;
4580 	}
4581 	return separations;
4582 }
4583 
findPrecise(QPair<double,double> * out,PlanetP object1,StelObjectP object2,double JD,double step,int prevSign,int mode)4584 bool AstroCalcDialog::findPrecise(QPair<double, double>* out, PlanetP object1, StelObjectP object2, double JD, double step, int prevSign, int mode)
4585 {
4586 	if (out == Q_NULLPTR)
4587 		return false;
4588 
4589 	double prevDist = findDistance(JD, object1, object2, mode);
4590 	step = -step / 2.;
4591 	prevSign = -prevSign;
4592 
4593 	while (true)
4594 	{
4595 		JD += step;
4596 		double dist = findDistance(JD, object1, object2, mode);
4597 
4598 		if (qAbs(step) < 1. / 1440.)
4599 		{
4600 			out->first = JD - step / 2.0;
4601 			out->second = findDistance(JD - step / 2.0, object1, object2, mode);
4602 			if (out->second < findDistance(JD - 5.0, object1, object2, mode))
4603 				return true;
4604 			else
4605 				return false;
4606 		}
4607 		int sgn = StelUtils::sign(dist - prevDist);
4608 		if (sgn != prevSign)
4609 		{
4610 			step = -step / 2.0;
4611 			sgn = -sgn;
4612 		}
4613 		prevDist = dist;
4614 		prevSign = sgn;
4615 	}
4616 }
4617 
findDistance(double JD,PlanetP object1,StelObjectP object2,int mode)4618 double AstroCalcDialog::findDistance(double JD, PlanetP object1, StelObjectP object2, int mode)
4619 {
4620 	core->setJD(JD);
4621 	core->update(0);
4622 	double angle = object1->getJ2000EquatorialPos(core).angle(object2->getJ2000EquatorialPos(core));
4623 	if (mode==PhenomenaTypeIndex::Opposition)
4624 		angle = M_PI - angle;
4625 	else if (mode==PhenomenaTypeIndex::Shadows)
4626 		angle = object1->getHeliocentricEclipticPos().angle(qSharedPointerCast<Planet>(object2)->getHeliocentricEclipticPos());
4627 	return angle;
4628 }
4629 
findGreatestElongationApproach(PlanetP & object1,StelObjectP & object2,double startJD,double stopJD)4630 QMap<double, double> AstroCalcDialog::findGreatestElongationApproach(PlanetP& object1, StelObjectP& object2, double startJD, double stopJD)
4631 {
4632 	QMap<double, double> separations;
4633 	QPair<double, double> extremum;
4634 
4635 	QStringList objects;
4636 	Q_ASSERT(objects.isEmpty());
4637 	objects.append(object1->getEnglishName());
4638 	objects.append(object2->getEnglishName());
4639 	double step0 = findInitialStep(startJD, stopJD, objects);
4640 	double step = step0;
4641 	double jd = startJD;
4642 	double prevDist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4643 	jd += step;
4644 	while (jd <= stopJD)
4645 	{
4646 		double dist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4647 		double factor = qAbs((dist - prevDist) / dist);
4648 		if (factor > 10.)
4649 			step = step0 * factor / 10.;
4650 		else
4651 			step = step0;
4652 
4653 		if (dist>prevDist)
4654 		{
4655 			if (step > step0)
4656 			{
4657 				jd -= step;
4658 				step = step0;
4659 				while (jd <= stopJD)
4660 				{
4661 					dist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4662 					if (dist<prevDist)
4663 						break;
4664 
4665 					prevDist = dist;
4666 					jd += step;
4667 				}
4668 			}
4669 
4670 			if (findPreciseGreatestElongation(&extremum, object1, object2, jd, stopJD, step))
4671 			{
4672 				separations.insert(extremum.first, extremum.second);
4673 			}
4674 		}
4675 
4676 		prevDist = dist;
4677 		jd += step;
4678 	}
4679 	return separations;
4680 }
4681 
findPreciseGreatestElongation(QPair<double,double> * out,PlanetP object1,StelObjectP object2,double JD,double stopJD,double step)4682 bool AstroCalcDialog::findPreciseGreatestElongation(QPair<double, double>* out, PlanetP object1, StelObjectP object2, double JD, double stopJD, double step)
4683 {
4684 	if (out == Q_NULLPTR)
4685 		return false;
4686 
4687 	double prevDist = findDistance(JD, object1, object2, PhenomenaTypeIndex::Conjunction);
4688 	step = -step / 2.;
4689 
4690 	while (true)
4691 	{
4692 		JD += step;
4693 		double dist = findDistance(JD, object1, object2, PhenomenaTypeIndex::Conjunction);
4694 
4695 		if (qAbs(step) < 1. / 1440.)
4696 		{
4697 			out->first = JD - step / 2.0;
4698 			out->second = findDistance(JD - step / 2.0, object1, object2, PhenomenaTypeIndex::Conjunction);
4699 			if (out->second > findDistance(JD - 5.0, object1, object2, PhenomenaTypeIndex::Conjunction))
4700 			{
4701 				if (object1->getJ2000EquatorialPos(core).longitude()>object2->getJ2000EquatorialPos(core).longitude())
4702 					out->second *= -1.0; // let's use negative value for eastern elongations
4703 				return true;
4704 			}
4705 			else
4706 				return false;
4707 		}
4708 		if (dist<prevDist)
4709 		{
4710 			step = -step / 2.0;
4711 		}
4712 		prevDist = dist;
4713 
4714 		if (JD > stopJD)
4715 			return false;
4716 	}
4717 }
4718 
findStationaryPointApproach(PlanetP & object1,double startJD,double stopJD)4719 QMap<double, double> AstroCalcDialog::findStationaryPointApproach(PlanetP &object1, double startJD, double stopJD)
4720 {
4721 	double RA, prevRA, step, step0;
4722 	QMap<double, double> separations;
4723 	QPair<double, double> extremum;
4724 
4725 	QStringList objects;
4726 	Q_ASSERT(objects.isEmpty());
4727 	objects.append(object1->getEnglishName());
4728 	step0 = findInitialStep(startJD, stopJD, objects);
4729 	step = step0;
4730 	double jd = startJD;
4731 	prevRA = findRightAscension(jd, object1);
4732 	jd += step;
4733 	while (jd <= stopJD)
4734 	{
4735 		RA = findRightAscension(jd, object1);
4736 		double factor = qAbs((RA - prevRA) / RA);
4737 		if (factor > 10.)
4738 			step = step0 * factor / 10.;
4739 		else
4740 			step = step0;
4741 
4742 		if (RA>prevRA && qAbs(RA - prevRA)<180.)
4743 		{
4744 			if (step > step0)
4745 			{
4746 				jd -= step;
4747 				step = step0;
4748 				while (jd <= stopJD)
4749 				{
4750 					RA = findRightAscension(jd, object1);
4751 					if (RA<prevRA)
4752 						break;
4753 
4754 					prevRA = RA;
4755 					jd += step;
4756 				}
4757 			}
4758 
4759 			if (findPreciseStationaryPoint(&extremum, object1, jd, stopJD, step, true))
4760 			{
4761 				separations.insert(extremum.first, extremum.second);
4762 			}
4763 		}
4764 		prevRA = RA;
4765 		jd += step;
4766 	}
4767 
4768 	step0 = findInitialStep(startJD, stopJD, objects);
4769 	step = step0;
4770 	jd = startJD;
4771 	prevRA = findRightAscension(jd, object1);
4772 	jd += step;
4773 	while (jd <= stopJD)
4774 	{
4775 		RA = findRightAscension(jd, object1);
4776 		double factor = qAbs((RA - prevRA) / RA);
4777 		if (factor > 10.)
4778 			step = step0 * factor / 10.;
4779 		else
4780 			step = step0;
4781 
4782 		if (RA<prevRA && qAbs(RA - prevRA)<180.)
4783 		{
4784 			if (step > step0)
4785 			{
4786 				jd -= step;
4787 				step = step0;
4788 				while (jd <= stopJD)
4789 				{
4790 					RA = findRightAscension(jd, object1);
4791 					if (RA>prevRA)
4792 						break;
4793 
4794 					prevRA = RA;
4795 					jd += step;
4796 				}
4797 			}
4798 
4799 			if (findPreciseStationaryPoint(&extremum, object1, jd, stopJD, step, false))
4800 			{
4801 				separations.insert(extremum.first, extremum.second);
4802 			}
4803 		}
4804 		prevRA = RA;
4805 		jd += step;
4806 	}
4807 
4808 	return separations;
4809 }
4810 
findPreciseStationaryPoint(QPair<double,double> * out,PlanetP object,double JD,double stopJD,double step,bool retrograde)4811 bool AstroCalcDialog::findPreciseStationaryPoint(QPair<double, double> *out, PlanetP object, double JD, double stopJD, double step, bool retrograde)
4812 {
4813 	double RA, prevRA;
4814 
4815 	if (out == Q_NULLPTR)
4816 		return false;
4817 
4818 	prevRA = findRightAscension(JD, object);
4819 	step /= -2.;
4820 
4821 	while (true)
4822 	{
4823 		JD += step;
4824 		RA = findRightAscension(JD, object);
4825 
4826 		if (qAbs(step) < 1. / 1440.)
4827 		{
4828 			out->first = JD - step / 2.0;
4829 			out->second = findRightAscension(JD - step / 2.0, object);
4830 			if (retrograde) // begin retrograde motion
4831 			{
4832 				if (out->second > findRightAscension(JD - 5.0, object))
4833 				{
4834 					out->second = -1.0;
4835 					return true;
4836 				}
4837 				else
4838 					return false;
4839 			}
4840 			else
4841 			{
4842 				if (out->second < findRightAscension(JD - 5.0, object))
4843 				{
4844 					out->second = 1.0;
4845 					return true;
4846 				}
4847 				else
4848 					return false;
4849 			}
4850 		}
4851 		if (retrograde)
4852 		{
4853 			if (RA<prevRA)
4854 				step /= -2.0;
4855 		}
4856 		else
4857 		{
4858 			if (RA>prevRA)
4859 				step /= -2.0;
4860 		}
4861 		prevRA = RA;
4862 
4863 		if (JD > stopJD)
4864 			return false;
4865 	}
4866 }
4867 
4868 // return J2000 RA of a planetary object (without aberration)
findRightAscension(double JD,PlanetP object)4869 double AstroCalcDialog::findRightAscension(double JD, PlanetP object)
4870 {
4871 	const double JDE=JD+core->computeDeltaT(JD)/86400.;
4872 	const Vec3d obs=core->getCurrentPlanet()->getHeliocentricEclipticPos(JDE);
4873 	Vec3d body=object->getHeliocentricEclipticPos(JDE);
4874 	const double distanceCorrection=(body-obs).length() * (AU / (SPEED_OF_LIGHT * 86400.));
4875 	body=object->getHeliocentricEclipticPos(JDE-distanceCorrection);
4876 	Vec3d bodyJ2000=StelCore::matVsop87ToJ2000.multiplyWithoutTranslation(body - obs);
4877 	double ra, dec;
4878 	StelUtils::rectToSphe(&ra, &dec, bodyJ2000);
4879 	return ra*M_180_PI;
4880 }
4881 
findOrbitalPointApproach(PlanetP & object1,double startJD,double stopJD)4882 QMap<double, double> AstroCalcDialog::findOrbitalPointApproach(PlanetP &object1, double startJD, double stopJD)
4883 {
4884 	QMap<double, double> separations;
4885 	QPair<double, double> extremum;
4886 
4887 	QStringList objects;
4888 	Q_ASSERT(objects.isEmpty());
4889 	objects.append(object1->getEnglishName());
4890 	double step0 = findInitialStep(startJD, stopJD, objects);
4891 	double step = step0;
4892 	double jd = startJD - step;
4893 	double prevDistance = findRightAscension(jd, object1);
4894 	jd += step;
4895 	double stopJDfx = stopJD + step;
4896 	while (jd <= stopJDfx)
4897 	{
4898 		double distance = findHeliocentricDistance(jd, object1);
4899 		double factor = qAbs((distance - prevDistance) / distance);
4900 		if (factor > 10.)
4901 			step = step0 * factor / 10.;
4902 		else
4903 			step = step0;
4904 
4905 		if (distance>prevDistance)
4906 		{
4907 			if (step > step0)
4908 			{
4909 				jd -= step;
4910 				step = step0;
4911 				while (jd <= stopJDfx)
4912 				{
4913 					distance = findHeliocentricDistance(jd, object1);
4914 					if (distance<prevDistance)
4915 						break;
4916 
4917 					prevDistance = distance;
4918 					jd += step;
4919 				}
4920 			}
4921 
4922 			if (findPreciseOrbitalPoint(&extremum, object1, jd, stopJDfx, step, false))
4923 			{
4924 				if (extremum.first>startJD && extremum.first<stopJD)
4925 					separations.insert(extremum.first, extremum.second);
4926 			}
4927 		}
4928 
4929 		prevDistance = distance;
4930 		jd += step;
4931 	}
4932 
4933 	step0 = findInitialStep(startJD, stopJD, objects);
4934 	step = step0;
4935 	jd = startJD - step;
4936 	prevDistance = findRightAscension(jd, object1);
4937 	jd += step;
4938 	while (jd <= stopJDfx)
4939 	{
4940 		double distance = findHeliocentricDistance(jd, object1);
4941 		double factor = qAbs((distance - prevDistance) / distance);
4942 		if (factor > 10.)
4943 			step = step0 * factor / 10.;
4944 		else
4945 			step = step0;
4946 
4947 		if (distance<prevDistance)
4948 		{
4949 			if (step > step0)
4950 			{
4951 				jd -= step;
4952 				step = step0;
4953 				while (jd <= stopJDfx)
4954 				{
4955 					distance = findHeliocentricDistance(jd, object1);
4956 					if (distance>prevDistance)
4957 						break;
4958 
4959 					prevDistance = distance;
4960 					jd += step;
4961 				}
4962 			}
4963 
4964 			if (findPreciseOrbitalPoint(&extremum, object1, jd, stopJDfx, step, true))
4965 			{
4966 				if (extremum.first>startJD && extremum.first<stopJD)
4967 					separations.insert(extremum.first, extremum.second);
4968 			}
4969 		}
4970 
4971 		prevDistance = distance;
4972 		jd += step;
4973 	}
4974 
4975 	return separations;
4976 }
4977 
findPreciseOrbitalPoint(QPair<double,double> * out,PlanetP object1,double JD,double stopJD,double step,bool minimal)4978 bool AstroCalcDialog::findPreciseOrbitalPoint(QPair<double, double>* out, PlanetP object1, double JD, double stopJD, double step, bool minimal)
4979 {
4980 	double dist, prevDist, timeDist = qAbs(stopJD-JD);
4981 
4982 	if (out == Q_NULLPTR)
4983 		return false;
4984 
4985 	prevDist = findHeliocentricDistance(JD, object1);
4986 	step /= -2.;
4987 
4988 	bool result;
4989 	while (true)
4990 	{
4991 		JD += step;
4992 		dist = findHeliocentricDistance(JD, object1);
4993 
4994 		if (qAbs(step) < 1. / 1440.)
4995 		{
4996 			out->first = JD - step / 2.0;
4997 			out->second = findHeliocentricDistance(JD - step / 2.0, object1);
4998 			if (minimal)
4999 			{
5000 				result = (out->second > findHeliocentricDistance(JD - step / 5.0, object1));
5001 				if (result)
5002 					out->second *= -1;
5003 			}
5004 			else
5005 				result = (out->second < findHeliocentricDistance(JD - step / 5.0, object1));
5006 
5007 			return result;
5008 		}
5009 		if (minimal)
5010 		{
5011 			if (dist>prevDist)
5012 				step /= -2.0;
5013 		}
5014 		else
5015 		{
5016 			if (dist<prevDist)
5017 				step /= -2.0;
5018 		}
5019 		prevDist = dist;
5020 
5021 		if (JD > stopJD || JD < (stopJD - 2*timeDist))
5022 			return false;
5023 	}
5024 }
5025 
changePage(QListWidgetItem * current,QListWidgetItem * previous)5026 void AstroCalcDialog::changePage(QListWidgetItem* current, QListWidgetItem* previous)
5027 {
5028 	if (!current)
5029 		current = previous;
5030 
5031 	// reset all flags to make sure only one is set
5032 	plotAltVsTime = false;
5033 	plotAziVsTime = false;
5034 	plotMonthlyElevation = false;
5035 	plotAngularDistanceGraph = false;
5036 	plotDistanceGraph = false;
5037 
5038 	ui->stackedWidget->setCurrentIndex(ui->stackListWidget->row(current));
5039 
5040 	// special case
5041 	if (ui->stackListWidget->row(current) == 0)
5042 		currentCelestialPositions();
5043 
5044 	// special case - ephemeris
5045 	if (ui->stackListWidget->row(current) == 1)
5046 	{
5047 		double JD = core->getJD() + core->getUTCOffset(core->getJD()) / 24;
5048 		QDateTime currentDT = StelUtils::jdToQDateTime(JD);
5049 		ui->dateFromDateTimeEdit->setDateTime(currentDT);
5050 		ui->dateToDateTimeEdit->setDateTime(currentDT.addMonths(1));
5051 	}
5052 
5053 	// special case - transits
5054 	if (ui->stackListWidget->row(current) == 2)
5055 		setTransitCelestialBodyName();
5056 
5057 	// special case - graphs
5058 	if (ui->stackListWidget->row(current) == 4)
5059 	{
5060 		int idx = ui->tabWidgetGraphs->currentIndex();
5061 		if (idx==0) // 'Alt. vs Time' is visible
5062 		{
5063 			plotAltVsTime = true;
5064 			drawAltVsTimeDiagram(); // Is object already selected?
5065 		}
5066 
5067 		if (idx==1) //  'Azi. vs Time' is visible
5068 		{
5069 			plotAziVsTime = true;
5070 			drawAziVsTimeDiagram(); // Is object already selected?
5071 		}
5072 
5073 		if (idx==2) // 'Monthly Elevation' is visible
5074 		{
5075 			plotMonthlyElevation = true;
5076 			drawMonthlyElevationGraph(); // Is object already selected?
5077 		}
5078 
5079 		if (idx==3) // 'Graphs' is visible
5080 			updateXVsTimeGraphs();
5081 
5082 		if(idx==4) // 'Angular distance' is visible
5083 		{
5084 			plotAngularDistanceGraph = true;
5085 			drawAngularDistanceGraph();
5086 		}
5087 	}
5088 
5089 	// special case (PCalc)
5090 	if (ui->stackListWidget->row(current) == 6)
5091 	{
5092 		int index = ui->tabWidgetPC->currentIndex();
5093 		if (index==0) // First tab: Data
5094 			computePlanetaryData();
5095 
5096 		if (index==1) // Second tab: Graphs
5097 		{
5098 			plotDistanceGraph = true;
5099 			drawDistanceGraph();
5100 		}
5101 	}
5102 }
5103 
changePCTab(int index)5104 void AstroCalcDialog::changePCTab(int index)
5105 {
5106 	if (index==0) // First tab: Data
5107 	{
5108 		plotDistanceGraph = false;
5109 		computePlanetaryData();
5110 	}
5111 	if (index==1) // Second tab: Graphs
5112 	{
5113 		plotDistanceGraph = true;
5114 		drawDistanceGraph();
5115 	}
5116 }
5117 
changeGraphsTab(int index)5118 void AstroCalcDialog::changeGraphsTab(int index)
5119 {
5120 	// reset all flags to make sure only one is set
5121 	plotAltVsTime = false;
5122 	plotAziVsTime = false;
5123 	plotMonthlyElevation = false;
5124 	plotAngularDistanceGraph = false;
5125 
5126 	if (index==0) // Altitude vs. Time
5127 	{
5128 		plotAltVsTime = true;
5129 		drawAltVsTimeDiagram(); // Is object already selected?
5130 	}
5131 	if (index==1) // Azimuth vs. Time
5132 	{
5133 		plotAziVsTime = true;
5134 		drawAziVsTimeDiagram(); // Is object already selected?
5135 	}
5136 	if (index==2) // Monthly Elevation
5137 	{
5138 		plotMonthlyElevation = true;
5139 		drawMonthlyElevationGraph(); // Is object already selected?
5140 	}
5141 	if (index==3) // Graphs
5142 		updateXVsTimeGraphs();
5143 	if (index==4) // Angular Distance
5144 	{
5145 		plotAngularDistanceGraph = true;
5146 		drawAngularDistanceGraph(); // Is object already selected?
5147 	}
5148 }
5149 
updateTabBarListWidgetWidth()5150 void AstroCalcDialog::updateTabBarListWidgetWidth()
5151 {
5152 	ui->stackListWidget->setWrapping(false);
5153 
5154 	// Update list item sizes after translation
5155 	ui->stackListWidget->adjustSize();
5156 
5157 	QAbstractItemModel* model = ui->stackListWidget->model();
5158 	if (!model)
5159 		return;
5160 
5161 	// stackListWidget->font() does not work properly!
5162 	// It has a incorrect fontSize in the first loading, which produces the bug#995107.
5163 	QFont font;
5164 	font.setPixelSize(14);
5165 	font.setWeight(75);
5166 	QFontMetrics fontMetrics(font);
5167 
5168 	int iconSize = ui->stackListWidget->iconSize().width();
5169 
5170 	int width = 0;
5171 	for (int row = 0; row < model->rowCount(); row++)
5172 	{
5173 		int textWidth = fontMetrics.boundingRect(ui->stackListWidget->item(row)->text()).width();
5174 		width += qMax(iconSize, textWidth);
5175 		width += 24; // margin - 12px left and 12px right
5176 	}
5177 
5178 	// Hack to force the window to be resized...
5179 	ui->stackListWidget->setMinimumWidth(width);
5180 	ui->stackListWidget->updateGeometry();
5181 }
5182 
updateSolarSystemData()5183 void AstroCalcDialog::updateSolarSystemData()
5184 {
5185 	if (dialog)
5186 	{
5187 		populateCelestialBodyList();
5188 		populateGroupCelestialBodyList();
5189 		currentCelestialPositions();
5190 		calculateWutObjects();
5191 	}
5192 }
5193 
populateTimeIntervalsList()5194 void AstroCalcDialog::populateTimeIntervalsList()
5195 {
5196 	Q_ASSERT(ui->wutComboBox);
5197 
5198 	QComboBox* wut = ui->wutComboBox;
5199 	wut->blockSignals(true);
5200 	int index = wut->currentIndex();
5201 	QVariant selectedIntervalId = wut->itemData(index);
5202 
5203 	wut->clear();
5204 	wut->addItem(qc_("In the Evening", "Celestial object is observed..."), "0");
5205 	wut->addItem(qc_("In the Morning", "Celestial object is observed..."), "1");
5206 	wut->addItem(qc_("Around Midnight", "Celestial object is observed..."), "2");
5207 	wut->addItem(qc_("In Any Time of the Night", "Celestial object is observed..."), "3");
5208 
5209 	index = wut->findData(selectedIntervalId, Qt::UserRole, Qt::MatchCaseSensitive);
5210 	if (index < 0)
5211 		index = wut->findData(conf->value("astrocalc/wut_time_interval", "0").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
5212 	wut->setCurrentIndex(index);
5213 	wut->model()->sort(0);
5214 	wut->blockSignals(false);
5215 }
5216 
populateWutGroups()5217 void AstroCalcDialog::populateWutGroups()
5218 {
5219 	Q_ASSERT(ui->wutCategoryListWidget);
5220 	StelModuleMgr& moduleMgr = StelApp::getInstance().getModuleMgr();
5221 
5222 	QListWidget* category = ui->wutCategoryListWidget;
5223 	category->blockSignals(true);
5224 
5225 	wutCategories.clear();
5226 	wutCategories.insert(q_("Planets"),				EWPlanets);
5227 	wutCategories.insert(q_("Bright stars"),			EWBrightStars);
5228 	wutCategories.insert(q_("Bright nebulae"),			EWBrightNebulae);
5229 	wutCategories.insert(q_("Dark nebulae"),			EWDarkNebulae);
5230 	wutCategories.insert(q_("Galaxies"),				EWGalaxies);
5231 	wutCategories.insert(q_("Open star clusters"),		EWOpenStarClusters);
5232 	wutCategories.insert(q_("Asteroids"),				EWAsteroids);
5233 	wutCategories.insert(q_("Comets"),				EWComets);
5234 	wutCategories.insert(q_("Plutinos"),				EWPlutinos);
5235 	wutCategories.insert(q_("Dwarf planets"),			EWDwarfPlanets);
5236 	wutCategories.insert(q_("Cubewanos"),			EWCubewanos);
5237 	wutCategories.insert(q_("Scattered disc objects"), 	EWScatteredDiscObjects);
5238 	wutCategories.insert(q_("Oort cloud objects"),		EWOortCloudObjects);
5239 	wutCategories.insert(q_("Sednoids"),				EWSednoids);
5240 	wutCategories.insert(q_("Planetary nebulae"),		EWPlanetaryNebulae);
5241 	wutCategories.insert(q_("Bright double stars"),		EWBrightDoubleStars);
5242 	wutCategories.insert(q_("Bright variable stars"),	EWBrightVariableStars);
5243 	wutCategories.insert(q_("Bright stars with high proper motion"),	EWBrightStarsWithHighProperMotion);
5244 	wutCategories.insert(q_("Symbiotic stars"),			EWSymbioticStars);
5245 	wutCategories.insert(q_("Emission-line stars"),		EWEmissionLineStars);
5246 	wutCategories.insert(q_("Supernova candidates"),	EWSupernovaeCandidates);
5247 	wutCategories.insert(q_("Supernova remnant candidates"), EWSupernovaeRemnantCandidates);
5248 	wutCategories.insert(q_("Supernova remnants"),	EWSupernovaeRemnants);
5249 	wutCategories.insert(q_("Clusters of galaxies"), 		EWClustersOfGalaxies);
5250 	wutCategories.insert(q_("Interstellar objects"),		EWInterstellarObjects);
5251 	wutCategories.insert(q_("Globular star clusters"),	EWGlobularStarClusters);
5252 	wutCategories.insert(q_("Regions of the sky"), 		EWRegionsOfTheSky);
5253 	wutCategories.insert(q_("Active galaxies"), 			EWActiveGalaxies);
5254 	if (moduleMgr.isPluginLoaded("Pulsars"))
5255 	{
5256 		// Add the category when pulsars is visible
5257 		if (propMgr->getProperty("Pulsars.pulsarsVisible")->getValue().toBool())
5258 			wutCategories.insert(q_("Pulsars"), EWPulsars);
5259 	}
5260 	if (moduleMgr.isPluginLoaded("Exoplanets"))
5261 	{
5262 		// Add the category when exoplanets is visible
5263 		if (propMgr->getProperty("Exoplanets.showExoplanets")->getValue().toBool())
5264 			wutCategories.insert(q_("Exoplanetary systems"), EWExoplanetarySystems);
5265 	}
5266 	if (moduleMgr.isPluginLoaded("Novae"))
5267 		wutCategories.insert(q_("Bright nova stars"), EWBrightNovaStars);
5268 	if (moduleMgr.isPluginLoaded("Supernovae"))
5269 		wutCategories.insert(q_("Bright supernova stars"), 	EWBrightSupernovaStars);
5270 	wutCategories.insert(q_("Interacting galaxies"), 	EWInteractingGalaxies);
5271 	wutCategories.insert(q_("Deep-sky objects"), 		EWDeepSkyObjects);
5272 	wutCategories.insert(q_("Messier objects"), 		EWMessierObjects);
5273 	wutCategories.insert(q_("NGC/IC objects"), 		EWNGCICObjects);
5274 	wutCategories.insert(q_("Caldwell objects"), 		EWCaldwellObjects);
5275 	wutCategories.insert(q_("Herschel 400 objects"), 	EWHerschel400Objects);
5276 	wutCategories.insert(q_("Algol-type eclipsing systems"),	EWAlgolTypeVariableStars);
5277 	wutCategories.insert(q_("The classical cepheids"),	EWClassicalCepheidsTypeVariableStars);
5278 	wutCategories.insert(q_("Bright carbon stars"),		EWCarbonStars);
5279 	wutCategories.insert(q_("Bright barium stars"),		EWBariumStars);
5280 
5281 	category->clear();
5282 	category->addItems(wutCategories.keys());
5283 	category->sortItems(Qt::AscendingOrder);
5284 
5285 	category->blockSignals(false);
5286 }
5287 
saveWutMagnitudeLimit(double mag)5288 void AstroCalcDialog::saveWutMagnitudeLimit(double mag)
5289 {
5290 	conf->setValue("astrocalc/wut_magnitude_limit", QString::number(mag, 'f', 2));
5291 	calculateWutObjects();
5292 }
5293 
saveWutMinAngularSizeLimit()5294 void AstroCalcDialog::saveWutMinAngularSizeLimit()
5295 {
5296 	// Convert to angular minutes
5297 	conf->setValue("astrocalc/wut_angular_limit_min", QString::number(ui->wutAngularSizeLimitMinSpinBox->valueDegrees()*60.0, 'f', 2));
5298 	calculateWutObjects();
5299 }
5300 
saveWutMaxAngularSizeLimit()5301 void AstroCalcDialog::saveWutMaxAngularSizeLimit()
5302 {
5303 	// Convert to angular minutes
5304 	conf->setValue("astrocalc/wut_angular_limit_max", QString::number(ui->wutAngularSizeLimitMaxSpinBox->valueDegrees()*60.0, 'f', 2));
5305 	calculateWutObjects();
5306 }
5307 
saveWutMinAltitude()5308 void AstroCalcDialog::saveWutMinAltitude()
5309 {
5310 	conf->setValue("astrocalc/wut_altitude_min", QString::number(ui->wutAltitudeMinSpinBox->valueDegrees(), 'f', 2));
5311 	calculateWutObjects();
5312 }
5313 
saveWutAngularSizeFlag(bool state)5314 void AstroCalcDialog::saveWutAngularSizeFlag(bool state)
5315 {
5316 	conf->setValue("astrocalc/wut_angular_limit_flag", state);
5317 	calculateWutObjects();
5318 }
5319 
5320 
saveWutTimeInterval(int index)5321 void AstroCalcDialog::saveWutTimeInterval(int index)
5322 {
5323 	Q_ASSERT(ui->wutComboBox);
5324 	QComboBox* wutTimeInterval = ui->wutComboBox;
5325 	conf->setValue("astrocalc/wut_time_interval", wutTimeInterval->itemData(index).toInt());
5326 
5327 	// Calculate WUT objects!
5328 	calculateWutObjects();
5329 }
5330 
setWUTHeaderNames(const bool magnitude,const bool separation)5331 void AstroCalcDialog::setWUTHeaderNames(const bool magnitude, const bool separation)
5332 {
5333 	wutHeader.clear();
5334 	wutHeader << q_("Name");
5335 	if (magnitude)
5336 	{
5337 		// TRANSLATORS: magnitude
5338 		wutHeader << q_("Mag.");
5339 	}
5340 	else
5341 	{
5342 		// TRANSLATORS: opacity
5343 		wutHeader << q_("Opac.");
5344 	}
5345 	wutHeader << qc_("Rise", "celestial event");
5346 	wutHeader << qc_("Transit", "celestial event; passage across a meridian");
5347 	// TRANSLATORS: elevation
5348 	wutHeader << q_("Elev.");
5349 	wutHeader << qc_("Set", "celestial event");
5350 	if (separation)
5351 	{
5352 		// TRANSLATORS: separation
5353 		wutHeader << q_("Sep.");
5354 	}
5355 	else
5356 	{
5357 		// TRANSLATORS: angular size
5358 		wutHeader << q_("Ang. Size");
5359 	}
5360 	// TRANSLATORS: IAU Constellation
5361 	wutHeader << qc_("Const.", "IAU Constellation");
5362 	ui->wutMatchingObjectsTreeWidget->setHeaderLabels(wutHeader);
5363 
5364 	adjustWUTColumns();
5365 }
5366 
adjustWUTColumns()5367 void AstroCalcDialog::adjustWUTColumns()
5368 {
5369 	// adjust the column width
5370 	for (int i = 0; i < WUTCount; ++i)
5371 	{
5372 		ui->wutMatchingObjectsTreeWidget->resizeColumnToContents(i);
5373 	}
5374 }
5375 
initListWUT(const bool magnitude,const bool separation)5376 void AstroCalcDialog::initListWUT(const bool magnitude, const bool separation)
5377 {
5378 	ui->wutMatchingObjectsTreeWidget->clear();
5379 	ui->wutMatchingObjectsTreeWidget->setColumnCount(WUTCount);
5380 	setWUTHeaderNames(magnitude, separation);
5381 	ui->wutMatchingObjectsTreeWidget->header()->setSectionsMovable(false);
5382 	ui->wutMatchingObjectsTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
5383 }
5384 
enableAngularLimits(bool enable)5385 void AstroCalcDialog::enableAngularLimits(bool enable)
5386 {
5387 	ui->wutAngularSizeLimitCheckBox->setEnabled(enable);
5388 	ui->wutAngularSizeLimitMinSpinBox->setEnabled(enable);
5389 	ui->wutAngularSizeLimitMaxSpinBox->setEnabled(enable);
5390 	if (!enable)
5391 		ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize); // special case!
5392 }
5393 
fillWUTTable(QString objectName,QString designation,float magnitude,Vec4d RTSTime,double maxElevation,double angularSize,QString constellation,bool decimalDegrees)5394 void AstroCalcDialog::fillWUTTable(QString objectName, QString designation, float magnitude, Vec4d RTSTime, double maxElevation, double angularSize, QString constellation, bool decimalDegrees)
5395 {
5396 	QString sAngularSize = dash;
5397 	QString sRise = dash;
5398 	QString sTransit = dash;
5399 	QString sSet = dash;
5400 	QString sMaxElevation = dash;
5401 	const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
5402 
5403 	WUTTreeWidgetItem* treeItem =  new WUTTreeWidgetItem(ui->wutMatchingObjectsTreeWidget);
5404 	treeItem->setData(WUTObjectName, Qt::DisplayRole, objectName);
5405 	treeItem->setData(WUTObjectName, Qt::UserRole, designation);
5406 	treeItem->setText(WUTMagnitude, magnitude > 98.f ? dash : QString::number(magnitude, 'f', 2));
5407 	treeItem->setTextAlignment(WUTMagnitude, Qt::AlignRight);
5408 
5409 	sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[1] + utcShift), true);
5410 	if (RTSTime[3]==0.)
5411 	{
5412 		sRise = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[0] + utcShift), true);
5413 		sSet  = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[2] + utcShift), true);
5414 	}
5415 
5416 	treeItem->setText(WUTRiseTime, sRise);
5417 	treeItem->setTextAlignment(WUTRiseTime, Qt::AlignRight);
5418 	treeItem->setText(WUTTransitTime, sTransit);
5419 	treeItem->setTextAlignment(WUTTransitTime, Qt::AlignRight);
5420 
5421 	if (decimalDegrees)
5422 		sMaxElevation = StelUtils::radToDecDegStr(maxElevation, 5, false, true);
5423 	else
5424 		sMaxElevation = StelUtils::radToDmsPStr(maxElevation, 2);
5425 	treeItem->setText(WUTMaxElevation, sMaxElevation);
5426 	treeItem->setTextAlignment(WUTMaxElevation, Qt::AlignRight);
5427 
5428 	treeItem->setText(WUTSetTime, sSet);
5429 	treeItem->setTextAlignment(WUTSetTime, Qt::AlignRight);
5430 
5431 	double angularSizeRad = angularSize * M_PI / 180.;
5432 	if (angularSize>0.0)
5433 	{
5434 		if (decimalDegrees)
5435 			sAngularSize = StelUtils::radToDecDegStr(angularSizeRad, 5, false, true);
5436 		else
5437 			sAngularSize = StelUtils::radToDmsPStr(angularSizeRad, 2);
5438 	}
5439 	treeItem->setText(WUTAngularSize, sAngularSize);
5440 	treeItem->setTextAlignment(WUTAngularSize, Qt::AlignRight);
5441 	treeItem->setText(WUTConstellation, constellation);
5442 	treeItem->setTextAlignment(WUTConstellation, Qt::AlignHCenter);
5443 	treeItem->setToolTip(WUTConstellation, q_("IAU Constellation"));
5444 }
5445 
calculateWutObjects()5446 void AstroCalcDialog::calculateWutObjects()
5447 {
5448 	if (ui->wutCategoryListWidget->currentItem())
5449 	{
5450 		QString categoryName = ui->wutCategoryListWidget->currentItem()->text();
5451 		const WUTCategory categoryId = static_cast<WUTCategory> (wutCategories.value(categoryName));
5452 
5453 		QList<PlanetP> allObjects = solarSystem->getAllPlanets();
5454 		QVector<NebulaP> allDSO = dsoMgr->getAllDeepSkyObjects();
5455 		QList<StelObjectP> hipStars = starMgr->getHipparcosStars();
5456 		QList<StelObjectP> carbonStars = starMgr->getHipparcosCarbonStars();
5457 		QList<StelObjectP> bariumStars = starMgr->getHipparcosBariumStars();
5458 		QList<StelACStarData> dblHipStars = starMgr->getHipparcosDoubleStars();
5459 		QList<StelACStarData> varHipStars = starMgr->getHipparcosVariableStars();
5460 		QList<StelACStarData> algolTypeStars = starMgr->getHipparcosAlgolTypeStars();
5461 		QList<StelACStarData> classicalCepheidsTypeStars = starMgr->getHipparcosClassicalCepheidsTypeStars();
5462 		QList<StelACStarData> hpmHipStars = starMgr->getHipparcosHighPMStars();
5463 
5464 		const Nebula::TypeGroup& tflags = dsoMgr->getTypeFilters();
5465 		const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
5466 		const bool angularSizeLimit = ui->wutAngularSizeLimitCheckBox->isChecked();
5467 		bool passByType, visible = true;
5468 		bool enableAngular = true;
5469 		const double angularSizeLimitMin = ui->wutAngularSizeLimitMinSpinBox->valueDegrees();
5470 		const double angularSizeLimitMax = ui->wutAngularSizeLimitMaxSpinBox->valueDegrees();
5471 		const double altitudeLimitMin = ui->wutAltitudeMinSpinBox->valueDegrees();
5472 		const float magLimit = static_cast<float>(ui->wutMagnitudeDoubleSpinBox->value());
5473 		const double JD = core->getJD();
5474 		double alt;
5475 		float mag;
5476 		QSet<QString> objectsList;
5477 		QString designation, starName, constellation;
5478 
5479 		ui->wutAngularSizeLimitCheckBox->setText(q_("Limit angular size:"));
5480 		ui->wutAngularSizeLimitCheckBox->setToolTip(q_("Set limits for angular size for visible celestial objects"));
5481 		ui->wutAngularSizeLimitMinSpinBox->setToolTip(q_("Minimal angular size for visible celestial objects"));
5482 		ui->wutAngularSizeLimitMaxSpinBox->setToolTip(q_("Maximum angular size for visible celestial objects"));
5483 
5484 		// Direct calculate sunrise/sunset (civil twilight)
5485 		Vec4d rts = GETSTELMODULE(SolarSystem)->getSun()->getRTSTime(core, -7.);
5486 		QList<double> wutJDList;
5487 		wutJDList.clear();
5488 
5489 		QComboBox* wut = ui->wutComboBox;
5490 		switch (wut->itemData(wut->currentIndex()).toInt())
5491 		{
5492 			case 1: // Morning
5493 				wutJDList << rts[0];
5494 				break;
5495 			case 2: // Night
5496 				wutJDList << rts[1] + 0.5;
5497 				break;
5498 			case 3:
5499 				wutJDList << rts[0] << rts[1] + 0.5 << rts[2];
5500 				break;
5501 			default: // Evening
5502 				wutJDList << rts[2];
5503 				break;
5504 		}
5505 
5506 		initListWUT();
5507 		ui->wutMatchingObjectsTreeWidget->showColumn(WUTMagnitude);
5508 		ui->wutMatchingObjectsTreeWidget->showColumn(WUTAngularSize);
5509 		objectsList.clear();
5510 		for (int i = 0; i < wutJDList.count(); i++)
5511 		{
5512 			core->setJD(wutJDList.at(i));
5513 			core->update(0);
5514 
5515 			switch (categoryId)
5516 			{
5517 				case EWBrightStars:
5518 				case EWCarbonStars:
5519 				case EWBariumStars:
5520 				{
5521 					enableAngular = false;
5522 					QList<StelObjectP> stars;
5523 					if (categoryId==EWBrightStars)
5524 						stars = hipStars;
5525 					else if (categoryId==EWBariumStars)
5526 						stars = bariumStars;
5527 					else
5528 						stars = carbonStars;
5529 					for (const auto& object : qAsConst(stars))
5530 					{
5531 						// Filter for angular size is not applicable
5532 						mag = object->getVMagnitude(core);
5533 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5534 						{
5535 							designation = object->getEnglishName();
5536 							if (designation.isEmpty())
5537 								designation = object->getID();
5538 
5539 							if (!objectsList.contains(designation))
5540 							{
5541 								starName = object->getNameI18n();
5542 								if (starName.isEmpty())
5543 									starName = designation;
5544 
5545 								rts = object->getRTSTime(core, altitudeLimitMin);
5546 								alt = computeMaxElevation(object);
5547 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5548 
5549 								fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5550 								objectsList.insert(designation);
5551 							}
5552 						}
5553 					}
5554 					break;
5555 				}
5556 				case EWBrightNebulae:
5557 				case EWDarkNebulae:
5558 				case EWGalaxies:
5559 				case EWOpenStarClusters:
5560 				case EWPlanetaryNebulae:
5561 				case EWSymbioticStars:
5562 				case EWEmissionLineStars:
5563 				case EWSupernovaeCandidates:
5564 				case EWSupernovaeRemnantCandidates:
5565 				case EWSupernovaeRemnants:
5566 				case EWClustersOfGalaxies:
5567 				case EWGlobularStarClusters:
5568 				case EWRegionsOfTheSky:
5569 				case EWInteractingGalaxies:
5570 				case EWDeepSkyObjects:
5571 				{
5572 					if (categoryId==EWDarkNebulae)
5573 						initListWUT(false, false); // special case!
5574 					if (categoryId==EWSymbioticStars || categoryId==EWEmissionLineStars || categoryId==EWSupernovaeCandidates)
5575 						enableAngular = false;
5576 
5577 					for (const auto& object : allDSO)
5578 					{
5579 						passByType = false;
5580 						mag = object->getVMagnitude(core);
5581 						Nebula::NebulaType ntype = object->getDSOType();
5582 						switch (categoryId)
5583 						{
5584 							case EWBrightNebulae:
5585 								if (static_cast<bool>(tflags & Nebula::TypeBrightNebulae) && (ntype == Nebula::NebN || ntype == Nebula::NebBn || ntype == Nebula::NebEn || ntype == Nebula::NebRn || ntype == Nebula::NebHII || ntype == Nebula::NebISM || ntype == Nebula::NebCn || ntype == Nebula::NebSNR) && mag <= magLimit)
5586 									passByType = true;
5587 								break;
5588 							case EWDarkNebulae:
5589 								if (static_cast<bool>(tflags & Nebula::TypeDarkNebulae) && (ntype == Nebula::NebDn || ntype == Nebula::NebMolCld	 || ntype == Nebula::NebYSO))
5590 									passByType = true;
5591 								break;
5592 							case EWGalaxies:
5593 								if (static_cast<bool>(tflags & Nebula::TypeGalaxies) && (ntype == Nebula::NebGx) && mag <= magLimit)
5594 									passByType = true;
5595 								break;
5596 							case EWOpenStarClusters:
5597 								if (static_cast<bool>(tflags & Nebula::TypeOpenStarClusters) && (ntype == Nebula::NebCl || ntype == Nebula::NebOc || ntype == Nebula::NebSA || ntype == Nebula::NebSC || ntype == Nebula::NebCn) && mag <= magLimit)
5598 									passByType = true;
5599 								break;
5600 							case EWPlanetaryNebulae:
5601 								if (static_cast<bool>(tflags & Nebula::TypePlanetaryNebulae) && (ntype == Nebula::NebPn || ntype == Nebula::NebPossPN || ntype == Nebula::NebPPN) && mag <= magLimit)
5602 									passByType = true;
5603 								break;
5604 							case EWSymbioticStars:
5605 								if (static_cast<bool>(tflags & Nebula::TypeOther) && (ntype == Nebula::NebSymbioticStar) && mag <= magLimit)
5606 									passByType = true;
5607 								break;
5608 							case EWEmissionLineStars:
5609 								if (static_cast<bool>(tflags & Nebula::TypeOther) && (ntype == Nebula::NebEmissionLineStar) && mag <= magLimit)
5610 									passByType = true;
5611 								break;
5612 							case EWSupernovaeCandidates:
5613 							{
5614 								visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5615 								if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNC) && visible)
5616 									passByType = true;
5617 								break;
5618 							}
5619 							case EWSupernovaeRemnantCandidates:
5620 							{
5621 								visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5622 								if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNRC) && visible)
5623 									passByType = true;
5624 								break;
5625 							}
5626 							case EWSupernovaeRemnants:
5627 							{
5628 								visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5629 								if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNR) && visible)
5630 									passByType = true;
5631 								break;
5632 							}
5633 							case EWClustersOfGalaxies:
5634 								if (static_cast<bool>(tflags & Nebula::TypeGalaxyClusters) && (ntype == Nebula::NebGxCl) && mag <= magLimit)
5635 									passByType = true;
5636 								break;
5637 							case EWGlobularStarClusters:
5638 								if ((static_cast<bool>(tflags & Nebula::TypeGlobularStarClusters) && ntype == Nebula::NebGc) && mag <= magLimit)
5639 									passByType = true;
5640 								break;
5641 							case EWRegionsOfTheSky:
5642 								if (static_cast<bool>(tflags & Nebula::TypeOther) && ntype == Nebula::NebRegion)
5643 									passByType = true;
5644 								break;
5645 							case EWInteractingGalaxies:
5646 								if (static_cast<bool>(tflags & Nebula::TypeInteractingGalaxies) && (ntype == Nebula::NebIGx) && mag <= magLimit)
5647 									passByType = true;
5648 								break;
5649 							case EWDeepSkyObjects:
5650 								if (mag <= magLimit)
5651 									passByType = true;
5652 								if (ntype == Nebula::NebDn)
5653 									mag = 99.f;
5654 								break;
5655 							default:
5656 								break;
5657 						}
5658 
5659 						if (passByType && object->isAboveRealHorizon(core))
5660 						{
5661 							QString d = object->getDSODesignation();
5662 							if (d.isEmpty())
5663 								d = object->getDSODesignationWIC();
5664 							QString n = object->getNameI18n();
5665 
5666 							if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5667 								continue;
5668 
5669 							if (d.isEmpty() && n.isEmpty())
5670 								continue;
5671 
5672 							designation = QString("%1:%2").arg(d, n);
5673 							if (!objectsList.contains(designation))
5674 							{
5675 								rts = object->getRTSTime(core, altitudeLimitMin);
5676 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5677 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5678 
5679 								if (d.isEmpty())
5680 									fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5681 								else if (n.isEmpty())
5682 									fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5683 								else
5684 									fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5685 
5686 								objectsList.insert(designation);
5687 							}
5688 						}
5689 					}
5690 
5691 					if (categoryId==EWRegionsOfTheSky) // special case!
5692 					{
5693 						ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize);
5694 						ui->wutMatchingObjectsTreeWidget->hideColumn(WUTMagnitude);
5695 					}
5696 
5697 					break;
5698 				}
5699 				case EWPlanets:
5700 				case EWAsteroids:
5701 				case EWComets:
5702 				case EWPlutinos:
5703 				case EWDwarfPlanets:
5704 				case EWCubewanos:
5705 				case EWScatteredDiscObjects:
5706 				case EWOortCloudObjects:
5707 				case EWSednoids:
5708 				case EWInterstellarObjects:
5709 				{
5710 					static const QMap<int, Planet::PlanetType>map = {
5711 						{EWPlanets,			Planet::isPlanet},
5712 						{EWAsteroids,			Planet::isAsteroid},
5713 						{EWComets,			Planet::isComet},
5714 						{EWPlutinos,			Planet::isPlutino},
5715 						{EWDwarfPlanets,		Planet::isDwarfPlanet},
5716 						{EWCubewanos,			Planet::isCubewano},
5717 						{EWScatteredDiscObjects,	Planet::isSDO},
5718 						{EWOortCloudObjects,		Planet::isOCO},
5719 						{EWSednoids,			Planet::isSednoid},
5720 						{EWInterstellarObjects,		Planet::isInterstellar}};
5721 					const Planet::PlanetType pType = map.value(categoryId, Planet::isInterstellar);
5722 
5723 					for (const auto& object : allObjects)
5724 					{
5725 						mag = object->getVMagnitude(core);
5726 						if (object->getPlanetType() == pType && mag <= magLimit && object->isAboveRealHorizon(core))
5727 						{
5728 							if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5729 								continue;
5730 
5731 							designation = object->getEnglishName();
5732 							if (!objectsList.contains(designation))
5733 							{
5734 								rts = object->getRTSTime(core, altitudeLimitMin);
5735 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5736 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5737 
5738 								fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 2.0*object->getAngularSize(core), constellation, withDecimalDegree);
5739 								objectsList.insert(designation);
5740 							}
5741 						}
5742 					}
5743 
5744 					if (pType==Planet::isComet)
5745 						ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize); // special case!
5746 
5747 					break;
5748 				}
5749 				case EWBrightDoubleStars:
5750 					// Special case for double stars
5751 					ui->wutAngularSizeLimitCheckBox->setText(q_("Limit angular separation:"));
5752 					ui->wutAngularSizeLimitCheckBox->setToolTip(q_("Set limits for angular separation for visible double stars"));
5753 					ui->wutAngularSizeLimitMinSpinBox->setToolTip(q_("Minimal angular separation for visible double stars"));
5754 					ui->wutAngularSizeLimitMaxSpinBox->setToolTip(q_("Maximum angular separation for visible double stars"));
5755 					if (i==0)
5756 						initListWUT(true, true); // special case!
5757 
5758 					for (const auto& dblStar : dblHipStars)
5759 					{
5760 						StelObjectP object = dblStar.firstKey();
5761 						mag = object->getVMagnitude(core);
5762 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5763 						{
5764 							// convert from arc-seconds to degrees
5765 							if ((angularSizeLimit) && (!StelUtils::isWithin(static_cast<double>(dblStar.value(object))/3600.0, angularSizeLimitMin, angularSizeLimitMax)))
5766 								continue;
5767 
5768 							designation = object->getEnglishName();
5769 							if (designation.isEmpty())
5770 								designation = object->getID();
5771 
5772 							if (!objectsList.contains(designation))
5773 							{
5774 								starName = object->getNameI18n();
5775 								if (starName.isEmpty())
5776 									starName = designation;
5777 
5778 								rts = object->getRTSTime(core, altitudeLimitMin);
5779 								alt = computeMaxElevation(object);
5780 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5781 
5782 								fillWUTTable(starName, designation, mag, rts, alt, dblStar.value(object)/3600.0, constellation, withDecimalDegree);
5783 								objectsList.insert(designation);
5784 							}
5785 						}
5786 					}
5787 					break;
5788 				case EWAlgolTypeVariableStars:
5789 				case EWClassicalCepheidsTypeVariableStars:
5790 				case EWBrightVariableStars:
5791 				{
5792 					enableAngular = false;
5793 					static QMap<int, QList<StelACStarData>>map = {
5794 						{EWAlgolTypeVariableStars,			algolTypeStars},
5795 						{EWClassicalCepheidsTypeVariableStars,	classicalCepheidsTypeStars},
5796 						{EWBrightVariableStars,				varHipStars}};
5797 					for (const auto& varStar : map.value(categoryId, varHipStars))
5798 					{
5799 						StelObjectP object = varStar.firstKey();
5800 						mag = object->getVMagnitude(core);
5801 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5802 						{
5803 							designation = object->getEnglishName();
5804 							if (designation.isEmpty())
5805 								designation = object->getID();
5806 
5807 							if (!objectsList.contains(designation))
5808 							{
5809 								starName = object->getNameI18n();
5810 								if (starName.isEmpty())
5811 									starName = designation;
5812 
5813 								rts = object->getRTSTime(core, altitudeLimitMin);
5814 								alt = computeMaxElevation(object);
5815 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5816 
5817 								fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5818 								objectsList.insert(designation);
5819 							}
5820 						}
5821 					}
5822 					break;
5823 				}
5824 				case EWBrightStarsWithHighProperMotion:
5825 					enableAngular = false;
5826 					for (const auto& hpmStar : hpmHipStars)
5827 					{
5828 						StelObjectP object = hpmStar.firstKey();
5829 						mag = object->getVMagnitude(core);
5830 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5831 						{
5832 							designation = object->getEnglishName();
5833 							if (designation.isEmpty())
5834 								designation = object->getID();
5835 
5836 							if (!objectsList.contains(designation))
5837 							{
5838 								starName = object->getNameI18n();
5839 								if (starName.isEmpty())
5840 									starName = designation;
5841 
5842 								rts = object->getRTSTime(core, altitudeLimitMin);
5843 								alt = computeMaxElevation(object);
5844 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5845 
5846 								fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5847 								objectsList.insert(designation);
5848 							}
5849 						}
5850 					}
5851 					break;
5852 				case EWActiveGalaxies:
5853 					enableAngular = false;
5854 					for (const auto& object : allDSO)
5855 					{
5856 						passByType = false;
5857 						mag = object->getVMagnitude(core);
5858 						Nebula::NebulaType ntype = object->getDSOType();
5859 						if (static_cast<bool>(tflags & Nebula::TypeActiveGalaxies) && (ntype == Nebula::NebQSO || ntype == Nebula::NebPossQSO || ntype == Nebula::NebAGx || ntype == Nebula::NebRGx || ntype == Nebula::NebBLA || ntype == Nebula::NebBLL) && mag <= magLimit && object->isAboveRealHorizon(core))
5860 						{
5861 							QString d = object->getDSODesignation();
5862 							if (d.isEmpty())
5863 								d = object->getDSODesignationWIC();
5864 							QString n = object->getNameI18n();
5865 
5866 							if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5867 								continue;
5868 
5869 							if (d.isEmpty() && n.isEmpty())
5870 								continue;
5871 
5872 							designation = QString("%1:%2").arg(d, n);
5873 							if (!objectsList.contains(designation))
5874 							{
5875 								rts = object->getRTSTime(core, altitudeLimitMin);
5876 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5877 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5878 
5879 								if (d.isEmpty())
5880 									fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5881 								else if (n.isEmpty())
5882 									fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5883 								else
5884 									fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5885 
5886 								objectsList.insert(designation);
5887 							}
5888 						}
5889 					}
5890 
5891 					#ifdef USE_STATIC_PLUGIN_QUASARS
5892 					if (StelApp::getInstance().getModuleMgr().isPluginLoaded("Quasars"))
5893 					{
5894 						if (propMgr->getProperty("Quasars.quasarsVisible")->getValue().toBool())
5895 						{
5896 							for (const auto& object : GETSTELMODULE(Quasars)->getAllQuasars())
5897 							{
5898 								mag = object->getVMagnitude(core);
5899 								if (mag <= magLimit && object->isAboveRealHorizon(core))
5900 								{
5901 									designation = object->getEnglishName();
5902 									if (!objectsList.contains(designation) && !designation.isEmpty())
5903 									{
5904 										rts = object->getRTSTime(core, altitudeLimitMin);
5905 										alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5906 										constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5907 
5908 										fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5909 										objectsList.insert(designation);
5910 									}
5911 								}
5912 							}
5913 						}
5914 					}
5915 					#endif
5916 					break;
5917 				case EWPulsars:
5918 					enableAngular = false;
5919 					#ifdef USE_STATIC_PLUGIN_PULSARS
5920 					for (const auto& object : GETSTELMODULE(Pulsars)->getAllPulsars())
5921 					{
5922 						if (object->isAboveRealHorizon(core))
5923 						{
5924 							designation = object->getEnglishName();
5925 							if (designation.isEmpty())
5926 								designation = object->getDesignation();
5927 
5928 							if (!objectsList.contains(designation) && !designation.isEmpty())
5929 							{
5930 								starName = object->getNameI18n(); // Just re-use variable
5931 								if (starName.isEmpty())
5932 									starName = designation;
5933 
5934 								rts = object->getRTSTime(core, altitudeLimitMin);
5935 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5936 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5937 
5938 								fillWUTTable(starName, designation, 99.f, rts, alt, 0.0, constellation, withDecimalDegree);
5939 								objectsList.insert(designation);
5940 							}
5941 						}
5942 					}
5943 					ui->wutMatchingObjectsTreeWidget->hideColumn(WUTMagnitude); // special case!
5944 					#endif
5945 					break;
5946 				case EWExoplanetarySystems:
5947 					enableAngular = false;
5948 					#ifdef USE_STATIC_PLUGIN_EXOPLANETS
5949 					for (const auto& object : GETSTELMODULE(Exoplanets)->getAllExoplanetarySystems())
5950 					{
5951 						mag = object->getVMagnitude(core);
5952 						if (mag <= magLimit && object->isVMagnitudeDefined() && object->isAboveRealHorizon(core))
5953 						{
5954 							designation = object->getEnglishName();
5955 							if (!objectsList.contains(designation) && !designation.isEmpty())
5956 							{
5957 								rts = object->getRTSTime(core, altitudeLimitMin);
5958 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5959 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5960 
5961 								fillWUTTable(object->getNameI18n().trimmed(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5962 								objectsList.insert(designation);
5963 							}
5964 						}
5965 					}
5966 					#endif
5967 					break;
5968 				case EWBrightNovaStars:
5969 					enableAngular = false;
5970 					#ifdef USE_STATIC_PLUGIN_NOVAE
5971 					for (const auto& object : GETSTELMODULE(Novae)->getAllBrightNovae())
5972 					{
5973 						mag = object->getVMagnitude(core);
5974 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5975 						{
5976 							designation = object->getEnglishName();
5977 							if (!objectsList.contains(designation))
5978 							{
5979 								rts = object->getRTSTime(core, altitudeLimitMin);
5980 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5981 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5982 
5983 								fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5984 								objectsList.insert(designation);
5985 							}
5986 						}
5987 					}
5988 					#endif
5989 					break;
5990 				case EWBrightSupernovaStars:
5991 					enableAngular = false;
5992 					#ifdef USE_STATIC_PLUGIN_SUPERNOVAE
5993 					for (const auto& object : GETSTELMODULE(Supernovae)->getAllBrightSupernovae())
5994 					{
5995 						mag = object->getVMagnitude(core);
5996 						if (mag <= magLimit && object->isAboveRealHorizon(core))
5997 						{
5998 							designation = object->getEnglishName();
5999 							if (!objectsList.contains(designation))
6000 							{
6001 								rts = object->getRTSTime(core, altitudeLimitMin);
6002 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
6003 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
6004 
6005 								fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
6006 								objectsList.insert(designation);
6007 							}
6008 						}
6009 					}
6010 					#endif
6011 					break;
6012 				case EWMessierObjects:
6013 				case EWNGCICObjects:
6014 				case EWCaldwellObjects:
6015 				case EWHerschel400Objects:
6016 				{
6017 					QList<NebulaP> catDSO;
6018 					switch (categoryId)
6019 					{
6020 						case EWMessierObjects:
6021 							catDSO = dsoMgr->getDeepSkyObjectsByType("100");
6022 							break;
6023 						case EWNGCICObjects:
6024 							catDSO = dsoMgr->getDeepSkyObjectsByType("108");
6025 							catDSO.append(dsoMgr->getDeepSkyObjectsByType("109"));
6026 							break;
6027 						case EWCaldwellObjects:
6028 							catDSO = dsoMgr->getDeepSkyObjectsByType("101");
6029 							break;
6030 						case EWHerschel400Objects:
6031 							catDSO = dsoMgr->getDeepSkyObjectsByType("151");
6032 							break;
6033 						default:
6034 							qWarning() << "catDSO: should never come here";
6035 							break;
6036 					}
6037 
6038 					for (const auto& object : catDSO)
6039 					{
6040 						mag = object->getVMagnitude(core);
6041 						if (mag <= magLimit && object->isAboveRealHorizon(core))
6042 						{
6043 							QString d = object->getDSODesignation();
6044 							if (d.isEmpty())
6045 								d = object->getDSODesignationWIC();
6046 							QString n = object->getNameI18n();
6047 
6048 							if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
6049 								continue;
6050 
6051 							if (d.isEmpty() && n.isEmpty())
6052 								continue;
6053 
6054 							designation = QString("%1:%2").arg(d, n);
6055 							if (!objectsList.contains(designation))
6056 							{
6057 								rts = object->getRTSTime(core, altitudeLimitMin);
6058 								alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
6059 								constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
6060 
6061 								if (d.isEmpty())
6062 									fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6063 								else if (n.isEmpty())
6064 									fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6065 								else
6066 									fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6067 
6068 								objectsList.insert(designation);
6069 							}
6070 						}
6071 					}
6072 					break;
6073 				}
6074 				default:
6075 					qWarning() << "unknown WUTCategory " << categoryId;
6076 					break;
6077 			}
6078 		}
6079 
6080 		enableAngularLimits(enableAngular);
6081 		core->setJD(JD);
6082 		adjustWUTColumns();
6083 		objectsList.clear();
6084 	}
6085 }
6086 
selectWutObject(const QModelIndex & index)6087 void AstroCalcDialog::selectWutObject(const QModelIndex &index)
6088 {
6089 	if (index.isValid())
6090 	{
6091 		// Find the object
6092 		QString wutObjectEnglisName = index.sibling(index.row(),WUTObjectName).data(Qt::UserRole).toString();
6093 		if (objectMgr->findAndSelectI18n(wutObjectEnglisName) || objectMgr->findAndSelect(wutObjectEnglisName))
6094 		{
6095 			const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
6096 			if (!newSelected.empty())
6097 			{
6098 				// Can't point to home planet
6099 				if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
6100 				{
6101 					mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
6102 					mvMgr->setFlagTracking(true);
6103 				}
6104 				else
6105 					GETSTELMODULE(StelObjectMgr)->unSelect();
6106 			}
6107 		}
6108 	}
6109 }
6110 
saveWutObjects()6111 void AstroCalcDialog::saveWutObjects()
6112 {
6113 	QString filter = q_("Microsoft Excel Open XML Spreadsheet");
6114 	filter.append(" (*.xlsx);;");
6115 	filter.append(q_("CSV (Comma delimited)"));
6116 	filter.append(" (*.csv)");
6117 	// filter.append(q_("JSON (Stellarium bookmarks)"));
6118 	// filter.append(" (*.json)");
6119 	QString defaultFilter("(*.xlsx)");
6120 	QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
6121 							q_("Save list of objects as..."),
6122 							QDir::homePath() + "/wut-objects.xlsx",
6123 							filter,
6124 							&defaultFilter);
6125 
6126 	if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
6127 		saveTableAsCSV(filePath, ui->wutMatchingObjectsTreeWidget, wutHeader);
6128 	else if (defaultFilter.contains(".json", Qt::CaseInsensitive))
6129 		saveTableAsBookmarks(filePath, ui->wutMatchingObjectsTreeWidget);
6130 	else
6131 	{
6132 		int count = ui->wutMatchingObjectsTreeWidget->topLevelItemCount();
6133 		int columns = wutHeader.size();
6134 
6135 		int *width;
6136 		width = new int[static_cast<unsigned int>(columns)];
6137 		QString sData;
6138 		int w;
6139 
6140 		QXlsx::Document xlsx;
6141 		xlsx.setDocumentProperty("title", q_("What's Up Tonight"));
6142 		xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
6143 		if (ui->wutCategoryListWidget->currentRow()>0) // Fixed crash when category of objects is not selected
6144 			xlsx.addSheet(ui->wutCategoryListWidget->currentItem()->text(), AbstractSheet::ST_WorkSheet);
6145 
6146 		QXlsx::Format header;
6147 		header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
6148 		header.setPatternBackgroundColor(Qt::yellow);
6149 		header.setBorderStyle(QXlsx::Format::BorderThin);
6150 		header.setBorderColor(Qt::black);
6151 		header.setFontBold(true);
6152 		for (int i = 0; i < columns; i++)
6153 		{
6154 			// Row 1: Names of columns
6155 			sData = wutHeader.at(i).trimmed();
6156 			xlsx.write(1, i + 1, sData, header);
6157 			width[i] = sData.size();
6158 		}
6159 
6160 		QXlsx::Format data;
6161 		data.setHorizontalAlignment(QXlsx::Format::AlignRight);
6162 		QXlsx::Format left;
6163 		left.setHorizontalAlignment(QXlsx::Format::AlignLeft);
6164 		for (int i = 0; i < count; i++)
6165 		{
6166 			for (int j = 0; j < columns; j++)
6167 			{
6168 				// Row 2 and next: the data
6169 				sData = ui->wutMatchingObjectsTreeWidget->topLevelItem(i)->text(j).trimmed();
6170 				xlsx.write(i + 2, j + 1, sData, j==0 ? left : data);
6171 				w = sData.size();
6172 				if (w > width[j])
6173 				{
6174 					width[j] = w;
6175 				}
6176 			}
6177 		}
6178 
6179 		for (int i = 0; i < columns; i++)
6180 		{
6181 			xlsx.setColumnWidth(i+1, width[i]+2);
6182 		}
6183 
6184 		delete[] width;
6185 		xlsx.saveAs(filePath);
6186 	}
6187 }
6188 
saveFirstCelestialBody(int index)6189 void AstroCalcDialog::saveFirstCelestialBody(int index)
6190 {
6191 	Q_ASSERT(ui->firstCelestialBodyComboBox);
6192 	QComboBox* celestialBody = ui->firstCelestialBodyComboBox;
6193 	conf->setValue("astrocalc/first_celestial_body", celestialBody->itemData(index).toString());
6194 
6195 	computePlanetaryData();
6196 	drawDistanceGraph();
6197 }
6198 
saveSecondCelestialBody(int index)6199 void AstroCalcDialog::saveSecondCelestialBody(int index)
6200 {
6201 	Q_ASSERT(ui->secondCelestialBodyComboBox);
6202 	QComboBox* celestialBody = ui->secondCelestialBodyComboBox;
6203 	conf->setValue("astrocalc/second_celestial_body", celestialBody->itemData(index).toString());
6204 
6205 	computePlanetaryData();
6206 	drawDistanceGraph();
6207 }
6208 
computePlanetaryData()6209 void AstroCalcDialog::computePlanetaryData()
6210 {
6211 	Q_ASSERT(ui->firstCelestialBodyComboBox);
6212 	Q_ASSERT(ui->secondCelestialBodyComboBox);
6213 
6214 	QComboBox* fbody = ui->firstCelestialBodyComboBox;
6215 	QComboBox* sbody = ui->secondCelestialBodyComboBox;
6216 
6217 	QString firstCelestialBody = fbody->currentData(Qt::UserRole).toString();
6218 	QString secondCelestialBody = sbody->currentData(Qt::UserRole).toString();
6219 	QString currentPlanet = core->getCurrentPlanet()->getEnglishName();
6220 
6221 	PlanetP firstCBId = solarSystem->searchByEnglishName(firstCelestialBody);
6222 	Vec3d posFCB = firstCBId->getJ2000EquatorialPos(core);
6223 	PlanetP secondCBId = solarSystem->searchByEnglishName(secondCelestialBody);
6224 	Vec3d posSCB = secondCBId->getJ2000EquatorialPos(core);
6225 
6226 	const double distanceAu = (posFCB - posSCB).length();
6227 	const double distanceKm = AU * distanceAu;
6228 	QString degree = QChar(0x00B0);
6229 	// TRANSLATORS: Unit of measure for distance - kilometers
6230 	QString km = qc_("km", "distance");
6231 	// TRANSLATORS: Unit of measure for distance - millions kilometers
6232 	QString Mkm = qc_("M km", "distance");
6233 	const bool useKM = (distanceAu < 0.1);
6234 	QString distAU = QString::number(distanceAu, 'f', 5);
6235 	QString distKM = useKM ? QString::number(distanceKm, 'f', 3) : QString::number(distanceKm / 1.0e6, 'f', 3);
6236 
6237 	//const double r = std::acos(sin(posFCB.latitude()) * sin(posSCB.latitude()) + cos(posFCB.latitude()) * cos(posSCB.latitude()) * cos(posFCB.longitude() - posSCB.longitude()));
6238 	const double r= posFCB.angle(posSCB);
6239 
6240 	unsigned int d, m;
6241 	double s, dd;
6242 	bool sign;
6243 
6244 	StelUtils::radToDms(r, sign, d, m, s);
6245 	StelUtils::radToDecDeg(r, sign, dd);
6246 
6247 	double spcb1 = firstCBId->getSiderealPeriod();
6248 	double spcb2 = secondCBId->getSiderealPeriod();
6249 	QString parentFCBName = "";
6250 	if (firstCelestialBody != "Sun")
6251 		parentFCBName = firstCBId->getParent()->getEnglishName();
6252 	QString parentSCBName = "";
6253 	if (secondCelestialBody != "Sun")
6254 		parentSCBName = secondCBId->getParent()->getEnglishName();
6255 
6256 	QString distanceUM = qc_("AU", "distance, astronomical unit");
6257 	ui->labelLinearDistanceValue->setText(QString("%1 %2 (%3 %4)").arg(distAU, distanceUM, distKM, useKM ? km : Mkm));
6258 
6259 	QString angularDistance = dash;
6260 	if (firstCelestialBody != currentPlanet && secondCelestialBody != currentPlanet)
6261 		angularDistance = QString("%1%2 %3' %4\" (%5%2)").arg(d).arg(degree).arg(m).arg(s, 0, 'f', 2).arg(dd, 0, 'f', 5);
6262 	ui->labelAngularDistanceValue->setText(angularDistance);
6263 
6264 	// TRANSLATORS: Part of unit of measure for mean motion - degrees per day
6265 	QString day = qc_("day", "mean motion");
6266 	// TRANSLATORS: Unit of measure for period - days
6267 	QString days = qc_("days", "duration");
6268 	QString synodicPeriod = dash;
6269 	QString orbitalPeriodsRatio = dash;
6270 	if (spcb1 > 0.0 && spcb2 > 0.0 && parentFCBName==parentSCBName && firstCelestialBody!="Sun")
6271 	{
6272 		double sp = qAbs(1/(1/spcb1 - 1/spcb2));
6273 		synodicPeriod = QString("%1 %2 (%3 a)").arg(QString::number(sp, 'f', 3), days, QString::number(sp/365.25, 'f', 5));
6274 
6275 		double minp = spcb2;
6276 		if (qAbs(spcb1)<=qAbs(spcb2)) { minp = spcb1; }
6277 		int a = qRound(qAbs(spcb1/minp)*10);
6278 		int b = qRound(qAbs(spcb2/minp)*10);
6279 		int lcm = qAbs(a*b)/StelUtils::gcd(a, b);
6280 		orbitalPeriodsRatio = QString("%1:%2").arg(lcm/a).arg(lcm/b);
6281 	}
6282 	ui->labelSynodicPeriodValue->setText(synodicPeriod);
6283 	ui->labelOrbitalPeriodsRatioValue->setText(orbitalPeriodsRatio);
6284 
6285 	if (spcb1>0. && firstCelestialBody!="Sun")
6286 		ui->labelMeanMotionFCBValue->setText(QString("%1 %2/%3").arg(QString::number(360./spcb1, 'f', 5), degree, day));
6287 	else
6288 		ui->labelMeanMotionFCBValue->setText(dash);
6289 
6290 	if (spcb2>0. && secondCelestialBody!="Sun")
6291 		ui->labelMeanMotionSCBValue->setText(QString("%1 %2/%3").arg(QString::number(360./spcb2, 'f', 5), degree, day));
6292 	else
6293 		ui->labelMeanMotionSCBValue->setText(dash);
6294 
6295 	// TRANSLATORS: Unit of measure for speed - kilometers per second
6296 	QString kms = qc_("km/s", "speed");
6297 
6298 	double orbVelFCB = firstCBId->getEclipticVelocity().length();
6299 	QString orbitalVelocityFCB = orbVelFCB<=0. ? dash : QString("%1 %2").arg(QString::number(orbVelFCB * AU/86400., 'f', 3), kms);
6300 	ui->labelOrbitalVelocityFCBValue->setText(orbitalVelocityFCB);
6301 
6302 	double orbVelSCB = secondCBId->getEclipticVelocity().length();
6303 	QString orbitalVelocitySCB = orbVelSCB<=0. ? dash : QString("%1 %2").arg(QString::number(orbVelSCB * AU/86400., 'f', 3), kms);
6304 	ui->labelOrbitalVelocitySCBValue->setText(orbitalVelocitySCB);
6305 
6306 	double fcbs = 2.0 * AU * firstCBId->getEquatorialRadius();
6307 	double scbs = 2.0 * AU * secondCBId->getEquatorialRadius();
6308 	double sratio = fcbs/scbs;
6309 	int ss = (sratio < 1.0 ? 6 : 2);
6310 
6311 	QString sizeRatio = QString("%1 (%2 %4 / %3 %4)").arg(QString::number(sratio, 'f', ss), QString::number(fcbs, 'f', 1), QString::number(scbs, 'f', 1), km);
6312 	ui->labelEquatorialRadiiRatioValue->setText(sizeRatio);
6313 }
6314 
prepareDistanceAxesAndGraph()6315 void AstroCalcDialog::prepareDistanceAxesAndGraph()
6316 {
6317 	QString xAxisStr = q_("Days from today");
6318 	QString yAxisLegend1 = QString("%1, %2").arg(q_("Linear distance"), qc_("AU", "distance, astronomical unit"));
6319 	QString yAxisLegend2 = QString("%1, %2").arg(q_("Angular distance"), QChar(0x00B0)); // decimal degrees
6320 
6321 	QColor axisColor(Qt::white);
6322 	QPen axisPen(axisColor, 1);
6323 	QColor axisColorL(Qt::green);
6324 	QPen axisPenL(axisColorL, 1);
6325 	QColor axisColorR(Qt::yellow);
6326 	QPen axisPenR(axisColorR, 1);
6327 
6328 	ui->pcDistanceGraphPlot->xAxis->setLabel(xAxisStr);
6329 	ui->pcDistanceGraphPlot->yAxis->setLabel(yAxisLegend1);
6330 	ui->pcDistanceGraphPlot->yAxis2->setLabel(yAxisLegend2);
6331 
6332 	ui->pcDistanceGraphPlot->xAxis->setRange(-300, 300);
6333 	ui->pcDistanceGraphPlot->xAxis->setScaleType(QCPAxis::stLinear);
6334 	ui->pcDistanceGraphPlot->xAxis->setLabelColor(axisColor);
6335 	ui->pcDistanceGraphPlot->xAxis->setTickLabelColor(axisColor);
6336 	ui->pcDistanceGraphPlot->xAxis->setBasePen(axisPen);
6337 	ui->pcDistanceGraphPlot->xAxis->setTickPen(axisPen);
6338 	ui->pcDistanceGraphPlot->xAxis->setSubTickPen(axisPen);
6339 	ui->pcDistanceGraphPlot->xAxis->setAutoTicks(true);
6340 	ui->pcDistanceGraphPlot->xAxis->setAutoTickCount(15);
6341 
6342 	ui->pcDistanceGraphPlot->yAxis->setRange(minYld, maxYld);
6343 	ui->pcDistanceGraphPlot->yAxis->setScaleType(QCPAxis::stLinear);
6344 	ui->pcDistanceGraphPlot->yAxis->setLabelColor(axisColorL);
6345 	ui->pcDistanceGraphPlot->yAxis->setTickLabelColor(axisColorL);
6346 	ui->pcDistanceGraphPlot->yAxis->setBasePen(axisPenL);
6347 	ui->pcDistanceGraphPlot->yAxis->setTickPen(axisPenL);
6348 	ui->pcDistanceGraphPlot->yAxis->setSubTickPen(axisPenL);
6349 
6350 	ui->pcDistanceGraphPlot->yAxis2->setRange(minYad, maxYad);
6351 	ui->pcDistanceGraphPlot->yAxis2->setScaleType(QCPAxis::stLinear);
6352 	ui->pcDistanceGraphPlot->yAxis2->setLabelColor(axisColorR);
6353 	ui->pcDistanceGraphPlot->yAxis2->setTickLabelColor(axisColorR);
6354 	ui->pcDistanceGraphPlot->yAxis2->setBasePen(axisPenR);
6355 	ui->pcDistanceGraphPlot->yAxis2->setTickPen(axisPenR);
6356 	ui->pcDistanceGraphPlot->yAxis2->setSubTickPen(axisPenR);
6357 	ui->pcDistanceGraphPlot->yAxis2->setVisible(true);
6358 
6359 	ui->pcDistanceGraphPlot->clearGraphs();
6360 	ui->pcDistanceGraphPlot->addGraph(ui->pcDistanceGraphPlot->xAxis, ui->pcDistanceGraphPlot->yAxis);
6361 	ui->pcDistanceGraphPlot->setBackground(QBrush(QColor(86, 87, 90)));
6362 	ui->pcDistanceGraphPlot->graph(0)->setPen(axisPenL);
6363 	ui->pcDistanceGraphPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
6364 
6365 	ui->pcDistanceGraphPlot->addGraph(ui->pcDistanceGraphPlot->xAxis, ui->pcDistanceGraphPlot->yAxis2);
6366 	ui->pcDistanceGraphPlot->setBackground(QBrush(QColor(86, 87, 90)));
6367 	ui->pcDistanceGraphPlot->graph(1)->setPen(axisPenR);
6368 	ui->pcDistanceGraphPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
6369 }
6370 
drawDistanceGraph()6371 void AstroCalcDialog::drawDistanceGraph()
6372 {
6373 	// special case - plot the graph when tab is visible
6374 	if (!plotDistanceGraph || !dialog->isVisible())
6375 		return;
6376 
6377 	Q_ASSERT(ui->firstCelestialBodyComboBox);
6378 	Q_ASSERT(ui->secondCelestialBodyComboBox);
6379 
6380 	QComboBox* fbody = ui->firstCelestialBodyComboBox;
6381 	QComboBox* sbody = ui->secondCelestialBodyComboBox;
6382 
6383 	PlanetP currentPlanet = core->getCurrentPlanet();
6384 	PlanetP firstCBId = solarSystem->searchByEnglishName(fbody->currentData(Qt::UserRole).toString());
6385 	PlanetP secondCBId = solarSystem->searchByEnglishName(sbody->currentData(Qt::UserRole).toString());
6386 
6387 	if (firstCBId==secondCBId)
6388 	{
6389 		ui->pcDistanceGraphPlot->graph(0)->clearData();
6390 		ui->pcDistanceGraphPlot->graph(1)->clearData();
6391 		ui->pcDistanceGraphPlot->replot();
6392 		return;
6393 	}
6394 
6395 	int limit = 76, step = 4;
6396 	if (firstCBId->getParent() == currentPlanet || secondCBId->getParent() == currentPlanet)
6397 	{
6398 		limit = 151; step = 2;
6399 	}
6400 
6401 	QList<double> aX, aY1, aY2;
6402 	const double currentJD = core->getJD();
6403 	for (int i = (-1*limit); i <= limit; i++)
6404 	{
6405 		double JD = currentJD + i*step;
6406 		core->setJD(JD);
6407 		Vec3d posFCB = firstCBId->getJ2000EquatorialPos(core);
6408 		Vec3d posSCB = secondCBId->getJ2000EquatorialPos(core);
6409 		double distanceAu = (posFCB - posSCB).length();
6410 		//r = std::acos(sin(posFCB.latitude()) * sin(posSCB.latitude()) + cos(posFCB.latitude()) * cos(posSCB.latitude()) * cos(posFCB.longitude() - posSCB.longitude()));
6411 		double r= posFCB.angle(posSCB);
6412 		double dd;
6413 		bool sign;
6414 		StelUtils::radToDecDeg(r, sign, dd);
6415 		aX.append(i*step);
6416 		aY1.append(distanceAu);
6417 		if (firstCBId != currentPlanet && secondCBId != currentPlanet)
6418 			aY2.append(dd);
6419 		core->update(0.0);
6420 	}
6421 	core->setJD(currentJD);
6422 
6423 	QVector<double> x = aX.toVector(), y1 = aY1.toVector(), y2;
6424 	minYld = *std::min_element(aY1.begin(), aY1.end());
6425 	maxYld = *std::max_element(aY1.begin(), aY1.end());
6426 
6427 	if (!aY2.isEmpty()) // mistake-proofing!
6428 	{
6429 		y2 = aY2.toVector();
6430 		minYad = *std::min_element(aY2.begin(), aY2.end());
6431 		maxYad = *std::max_element(aY2.begin(), aY2.end());
6432 	}
6433 
6434 	prepareDistanceAxesAndGraph();
6435 
6436 	ui->pcDistanceGraphPlot->graph(0)->setData(x, y1);
6437 	ui->pcDistanceGraphPlot->graph(0)->setName("[LD]");
6438 	if (!aY2.isEmpty()) // mistake-proofing!
6439 	{
6440 		ui->pcDistanceGraphPlot->graph(1)->setData(x, y2);
6441 		ui->pcDistanceGraphPlot->graph(1)->setName("[AD]");
6442 	}
6443 	ui->pcDistanceGraphPlot->replot();
6444 }
6445 
mouseOverDistanceGraph(QMouseEvent * event)6446 void AstroCalcDialog::mouseOverDistanceGraph(QMouseEvent* event)
6447 {
6448 	const double x = ui->pcDistanceGraphPlot->xAxis->pixelToCoord(event->pos().x());
6449 	const double y = ui->pcDistanceGraphPlot->yAxis->pixelToCoord(event->pos().y());
6450 	const double y2 = ui->pcDistanceGraphPlot->yAxis2->pixelToCoord(event->pos().y());
6451 
6452 	QCPAbstractPlottable* abstractGraph = ui->pcDistanceGraphPlot->plottableAt(event->pos(), false);
6453 	QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
6454 
6455 	if (ui->pcDistanceGraphPlot->xAxis->range().contains(x) && ui->pcDistanceGraphPlot->yAxis->range().contains(y))
6456 	{
6457 		QString info;
6458 		if (graph)
6459 		{
6460 			const double currentJD = core->getJD();
6461 			if (graph->name()=="[LD]")
6462 				info = QString("%1<br />%2: %3%4").arg(StelUtils::julianDayToISO8601String(currentJD + x).replace("T", " "), q_("Linear distance"), QString::number(y), qc_("AU", "distance, astronomical unit"));
6463 
6464 			if (graph->name()=="[AD]")
6465 				info = QString("%1<br />%2: %3%4").arg(StelUtils::julianDayToISO8601String(currentJD + x).replace("T", " "), q_("Angular distance"), QString::number(y2), QChar(0x00B0));
6466 		}
6467 		ui->pcDistanceGraphPlot->setToolTip(info);
6468 	}
6469 
6470 	ui->pcDistanceGraphPlot->update();
6471 	ui->pcDistanceGraphPlot->replot();
6472 }
6473 
prepareAngularDistanceAxesAndGraph()6474 void AstroCalcDialog::prepareAngularDistanceAxesAndGraph()
6475 {
6476 	QString xAxisStr = q_("Days from today");
6477 	QString yAxisLegend = QString("%1, %2").arg(q_("Angular distance"), QChar(0x00B0)); // decimal degrees
6478 
6479 	QColor axisColor(Qt::white);
6480 	QPen axisPen(axisColor, 1);
6481 
6482 	ui->angularDistancePlot->xAxis->setLabel(xAxisStr);
6483 	ui->angularDistancePlot->yAxis->setLabel(yAxisLegend);
6484 
6485 	ui->angularDistancePlot->xAxis->setRange(-2, 31);
6486 	ui->angularDistancePlot->xAxis->setScaleType(QCPAxis::stLinear);
6487 	ui->angularDistancePlot->xAxis->setLabelColor(axisColor);
6488 	ui->angularDistancePlot->xAxis->setTickLabelColor(axisColor);
6489 	ui->angularDistancePlot->xAxis->setBasePen(axisPen);
6490 	ui->angularDistancePlot->xAxis->setTickPen(axisPen);
6491 	ui->angularDistancePlot->xAxis->setSubTickPen(axisPen);
6492 	ui->angularDistancePlot->xAxis->setAutoTicks(true);
6493 
6494 	ui->angularDistancePlot->yAxis->setRange(minYadm, maxYadm);
6495 	ui->angularDistancePlot->yAxis->setScaleType(QCPAxis::stLinear);
6496 	ui->angularDistancePlot->yAxis->setLabelColor(axisColor);
6497 	ui->angularDistancePlot->yAxis->setTickLabelColor(axisColor);
6498 	ui->angularDistancePlot->yAxis->setBasePen(axisPen);
6499 	ui->angularDistancePlot->yAxis->setTickPen(axisPen);
6500 	ui->angularDistancePlot->yAxis->setSubTickPen(axisPen);
6501 
6502 	ui->angularDistancePlot->clearGraphs();
6503 	ui->angularDistancePlot->addGraph(ui->angularDistancePlot->xAxis, ui->angularDistancePlot->yAxis);
6504 	ui->angularDistancePlot->setBackground(QBrush(QColor(86, 87, 90)));
6505 	ui->angularDistancePlot->graph(0)->setPen(QPen(Qt::red, 1));
6506 	ui->angularDistancePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
6507 	ui->angularDistancePlot->graph(0)->rescaleAxes(true);
6508 
6509 	ui->angularDistancePlot->addGraph(ui->angularDistancePlot->xAxis, ui->angularDistancePlot->yAxis);
6510 	ui->angularDistancePlot->setBackground(QBrush(QColor(86, 87, 90)));
6511 	ui->angularDistancePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
6512 	ui->angularDistancePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
6513 	ui->angularDistancePlot->graph(1)->rescaleAxes(true);
6514 }
6515 
drawAngularDistanceGraph()6516 void AstroCalcDialog::drawAngularDistanceGraph()
6517 {
6518 	QString label = q_("Angular distance between the Moon and selected object");
6519 	ui->angularDistancePlot->setToolTip(label);
6520 
6521 	// special case - plot the graph when tab is visible
6522 	//..
6523 	// we got notified about a reason to redraw the plot, but dialog was
6524 	// not visible. which means we must redraw when becoming visible again!
6525 	if (!dialog->isVisible() && !plotAngularDistanceGraph)
6526 	{
6527 		graphPlotNeedsRefresh = true;
6528 		return;
6529 	}
6530 
6531 	if (!plotAngularDistanceGraph) return;
6532 
6533 	// special case - the tool is not applicable on non-Earth locations
6534 	if (core->getCurrentPlanet()!=solarSystem->getEarth())
6535 		return;
6536 
6537 	QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
6538 	if (!selectedObjects.isEmpty())
6539 	{
6540 		PlanetP moon = solarSystem->getMoon();
6541 		StelObjectP selectedObject = selectedObjects[0];
6542 		if (selectedObject==moon || selectedObject->getType() == "Satellite")
6543 		{
6544 			ui->angularDistancePlot->graph(0)->clearData();
6545 			ui->angularDistancePlot->replot();
6546 			return;
6547 		}
6548 
6549 		QList<double> aX, aY;
6550 		Vec3d selectedObjectPosition, moonPosition;
6551 		const double currentJD = core->getJD();
6552 		double JD, distance, dd;
6553 		bool sign;
6554 		for (int i = -5; i <= 35; i++)
6555 		{
6556 			JD = currentJD + i;
6557 			core->setJD(JD);
6558 			moonPosition = moon->getJ2000EquatorialPos(core);
6559 			selectedObjectPosition = selectedObject->getJ2000EquatorialPos(core);
6560 			distance = moonPosition.angle(selectedObjectPosition);
6561 			StelUtils::radToDecDeg(distance, sign, dd);
6562 			aX.append(i);
6563 			aY.append(dd);
6564 			core->update(0.0);
6565 		}
6566 		core->setJD(currentJD);
6567 
6568 		QVector<double> x = aX.toVector(), y = aY.toVector();
6569 		minYadm = *std::min_element(aY.begin(), aY.end()) - 5.0;
6570 		maxYadm = *std::max_element(aY.begin(), aY.end()) + 5.0;
6571 		int limit = ui->angularDistanceLimitSpinBox->value();
6572 		if (minYadm > limit)
6573 			minYadm = limit - 5.0;
6574 		if (maxYadm < limit)
6575 			maxYadm = limit + 5.0;
6576 
6577 		QString name = selectedObject->getNameI18n();
6578 		if (name.isEmpty())
6579 		{
6580 			QString otype = selectedObject->getType();
6581 			if (otype == "Nebula")
6582 			{
6583 				name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
6584 				if (name.isEmpty())
6585 					name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
6586 			}
6587 			if (otype == "Star" || otype=="Pulsar")
6588 				selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
6589 		}
6590 		ui->angularDistancePlot->setToolTip(QString("%1 (%2)").arg(label, name));
6591 		ui->angularDistanceTitle->setText(QString("%1 (%2)").arg(label, name));
6592 
6593 		prepareAngularDistanceAxesAndGraph();
6594 
6595 		ui->angularDistancePlot->graph(0)->setData(x, y);
6596 		ui->angularDistancePlot->graph(0)->setName("[Angular distance]");
6597 		ui->angularDistancePlot->replot();
6598 	}
6599 
6600 	// clean up the data when selection is removed
6601 	if (!objectMgr->getWasSelected())
6602 	{
6603 		ui->angularDistancePlot->graph(0)->clearData();
6604 		ui->angularDistancePlot->replot();
6605 	}
6606 	drawAngularDistanceLimitLine();
6607 }
6608 
saveAngularDistanceLimit(int limit)6609 void AstroCalcDialog::saveAngularDistanceLimit(int limit)
6610 {
6611 	conf->setValue("astrocalc/angular_distance_limit", limit);
6612 	drawAngularDistanceLimitLine();
6613 }
6614 
drawAngularDistanceLimitLine()6615 void AstroCalcDialog::drawAngularDistanceLimitLine()
6616 {
6617 	// special case - plot the graph when tab is visible
6618 	if (!plotAngularDistanceGraph || !dialog->isVisible())
6619 		return;
6620 
6621 	double limit = ui->angularDistanceLimitSpinBox->value();
6622 	QVector<double> x = {-5, 35};
6623 	QVector<double> y = {limit, limit};
6624 	ui->angularDistancePlot->graph(1)->setData(x, y);
6625 	ui->angularDistancePlot->replot();
6626 }
6627 
saveTableAsCSV(const QString & fileName,QTreeWidget * tWidget,QStringList & headers)6628 void AstroCalcDialog::saveTableAsCSV(const QString &fileName, QTreeWidget* tWidget, QStringList &headers)
6629 {
6630 	const int count = tWidget->topLevelItemCount();
6631 	const int columns = headers.size();
6632 
6633 	QFile table(fileName);
6634 	if (!table.open(QFile::WriteOnly | QFile::Truncate))
6635 	{
6636 		qWarning() << "[AstroCalc] Unable to open file" << QDir::toNativeSeparators(fileName);
6637 		return;
6638 	}
6639 
6640 	QTextStream tableData(&table);
6641 	tableData.setCodec("UTF-8");
6642 
6643 	for (int i = 0; i < columns; i++)
6644 	{
6645 		QString h = headers.at(i).trimmed();
6646 		tableData << ((h.contains(",")) ? QString("\"%1\"").arg(h) : h);
6647 		tableData << ((i < columns - 1) ? delimiter : StelUtils::getEndLineChar());
6648 	}
6649 
6650 	for (int i = 0; i < count; i++)
6651 	{
6652 		for (int j = 0; j < columns; j++)
6653 		{
6654 			tableData << tWidget->topLevelItem(i)->text(j);
6655 			tableData << ((j < columns - 1) ? delimiter : StelUtils::getEndLineChar());
6656 		}
6657 	}
6658 
6659 	table.close();
6660 }
6661 
saveTableAsBookmarks(const QString & fileName,QTreeWidget * tWidget)6662 void AstroCalcDialog::saveTableAsBookmarks(const QString &fileName, QTreeWidget* tWidget)
6663 {
6664 	const int count = tWidget->topLevelItemCount();
6665 
6666 	QFile bookmarksFile(fileName);
6667 	if (!bookmarksFile.open(QFile::WriteOnly | QFile::Truncate))
6668 	{
6669 		qWarning() << "[AstroCalc] Unable to open file" << QDir::toNativeSeparators(fileName);
6670 		return;
6671 	}
6672 
6673 	QVariantMap bookmarksDataList;
6674 	double fov = GETSTELMODULE(StelMovementMgr)->getCurrentFov();
6675 	for (int i = 0; i < count; i++)
6676 	{
6677 		QString uuid = QUuid::createUuid().toString();
6678 		QVariantMap bm;
6679 		bm.insert("name", tWidget->topLevelItem(i)->data(0, Qt::UserRole).toString());
6680 		bm.insert("nameI18n", tWidget->topLevelItem(i)->data(0, Qt::DisplayRole).toString());
6681 		bm.insert("fov", fov);
6682 		bookmarksDataList.insert(uuid, bm);
6683 	}
6684 
6685 	QVariantMap bmList;
6686 	bmList.insert("bookmarks", bookmarksDataList);
6687 
6688 	//Convert the tree to JSON
6689 	StelJsonParser::write(bmList, &bookmarksFile);
6690 	bookmarksFile.flush();
6691 	bookmarksFile.close();
6692 }
6693