1 /*
2  * Copyright (C) 2009 Timothy Reaves
3  * Copyright (C) 2011 Bogdan Marinov
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #include "Oculars.hpp"
21 #include "OcularsGuiPanel.hpp"
22 
23 #include "GridLinesMgr.hpp"
24 #include "LabelMgr.hpp"
25 #include "ConstellationMgr.hpp"
26 #include "AsterismMgr.hpp"
27 #include "MilkyWay.hpp"
28 #include "SkyGui.hpp"
29 #include "StelActionMgr.hpp"
30 #include "StelApp.hpp"
31 #include "StelCore.hpp"
32 #include "StelFileMgr.hpp"
33 #include "StelGui.hpp"
34 #include "StelGuiItems.hpp"
35 #include "StelLocaleMgr.hpp"
36 #include "StelMainView.hpp"
37 #include "StelModuleMgr.hpp"
38 #include "StelMovementMgr.hpp"
39 #include "StelObjectMgr.hpp"
40 #include "StelPainter.hpp"
41 #include "StelProjector.hpp"
42 #include "StelTextureMgr.hpp"
43 #include "StelTranslator.hpp"
44 #include "SolarSystem.hpp"
45 #include "NebulaMgr.hpp"
46 #include "StelUtils.hpp"
47 #include "StelPropertyMgr.hpp"
48 #include "LandscapeMgr.hpp"
49 
50 #include <QAction>
51 #include <QDebug>
52 #include <QDir>
53 #include <QGraphicsWidget>
54 #include <QKeyEvent>
55 #include <QMenu>
56 #include <QMouseEvent>
57 #include <QPixmap>
58 #include <QMessageBox>
59 
60 #include <cmath>
61 #include <stdexcept>
62 
63 extern void qt_set_sequence_auto_mnemonic(bool b);
64 
65 static QSettings *settings; //!< The settings as read in from the ini file.
66 
67 /* ****************************************************************************************************************** */
68 #if 0
69 #pragma mark -
70 #pragma mark StelModuleMgr Methods
71 #endif
72 /* ****************************************************************************************************************** */
73 //! This method is the one called automatically by the StelModuleMgr just
74 //! after loading the dynamic library
getStelModule() const75 StelModule* OcularsStelPluginInterface::getStelModule() const
76 {
77 	return new Oculars();
78 }
79 
getPluginInfo() const80 StelPluginInfo OcularsStelPluginInterface::getPluginInfo() const
81 {
82 	// Allow to load the resources when used as a static plugin
83 	Q_INIT_RESOURCE(Oculars);
84 
85 	StelPluginInfo info;
86 	info.id = "Oculars";
87 	info.displayedName = N_("Oculars");
88 	info.authors = "Timothy Reaves";
89 	info.contact = STELLARIUM_DEV_URL;
90 	info.description = N_("Shows the sky as if looking through a telescope eyepiece. (Only magnification and field of view are simulated.) It can also show a sensor frame and a Telrad sight.");
91 	info.version = OCULARS_PLUGIN_VERSION;
92 	info.license = OCULARS_PLUGIN_LICENSE;
93 	return info;
94 }
95 
96 
97 /* ****************************************************************************************************************** */
98 #if 0
99 #pragma mark -
100 #pragma mark Instance Methods
101 #endif
102 /* ****************************************************************************************************************** */
Oculars()103 Oculars::Oculars()
104 	: selectedCCDIndex(-1)
105 	, selectedOcularIndex(-1)
106 	, selectedTelescopeIndex(-1)
107 	, selectedLensIndex(-1)
108 	, selectedCCDRotationAngle(0.0)
109 	, selectedCCDPrismPositionAngle(0.0)
110 	, arrowButtonScale(150)
111 	, flagShowCCD(false)
112 	, flagShowOculars(false)
113 	, flagShowCrosshairs(false)
114 	, flagShowTelrad(false)
115 	, usageMessageLabelID(-1)
116 	, flagCardinalPointsMain(false)
117 	, flagAdaptationMain(false)
118 	, flagLimitStarsMain(false)
119 	, magLimitStarsMain(0.0)
120 	, flagLimitStarsOculars(false)
121 	, magLimitStarsOculars(0.0)
122 	, flagAutoLimitMagnitude(false)
123 	, flagLimitDSOsMain(false)
124 	, magLimitDSOsMain(0.0)
125 	, flagLimitPlanetsMain(false)
126 	, magLimitPlanetsMain(0.0)
127 	, relativeStarScaleMain(1.0)
128 	, absoluteStarScaleMain(1.0)
129 	, relativeStarScaleOculars(1.0)
130 	, absoluteStarScaleOculars(1.0)
131 	, relativeStarScaleCCD(1.0)
132 	, absoluteStarScaleCCD(1.0)
133 	, flagMoonScaleMain(false)
134 	, flagMinorBodiesScaleMain(false)
135 	, flagSunScaleMain(false)
136 	, flagPlanetsScaleMain(false)
137 	, flagDSOPropHintMain(false)
138 	, milkyWaySaturation(1.0)
139 	, maxEyepieceAngle(0.0)
140 	, flagRequireSelection(true)
141 	, flagScaleImageCircle(true)
142 	, flagGuiPanelEnabled(false)
143 	, flagDMSDegrees(false)
144 	, flagSemiTransparency(false)
145 	, transparencyMask(85)
146 	, flagHideGridsLines(false)
147 	, flagGridLinesDisplayedMain(true)
148 	, flagConstellationLinesMain(true)
149 	, flagConstellationBoundariesMain(true)
150 	, flagAsterismLinesMain(true)
151 	, flagRayHelpersLinesMain(true)
152 	, flipVertMain(false)
153 	, flipHorzMain(false)
154 	, pxmapGlow(Q_NULLPTR)
155 	, pxmapOnIcon(Q_NULLPTR)
156 	, pxmapOffIcon(Q_NULLPTR)
157 	, toolbarButton(Q_NULLPTR)
158 	, flagShowOcularsButton(false)
159 	, ocularDialog(Q_NULLPTR)
160 	, ready(false)
161 	, actionShowOcular(Q_NULLPTR)
162 	, actionShowCrosshairs(Q_NULLPTR)
163 	, actionShowSensor(Q_NULLPTR)
164 	, actionShowTelrad(Q_NULLPTR)
165 	, actionConfiguration(Q_NULLPTR)
166 	, actionMenu(Q_NULLPTR)
167 	, actionTelescopeIncrement(Q_NULLPTR)
168 	, actionTelescopeDecrement(Q_NULLPTR)
169 	, actionOcularIncrement(Q_NULLPTR)
170 	, actionOcularDecrement(Q_NULLPTR)
171 	, guiPanel(Q_NULLPTR)
172 	, guiPanelFontSize(12)
173 	, textColor(0.)
174 	, lineColor(0.)
175 	, focuserColor(0.)
176 	, actualFOV(0.)
177 	, initialFOV(0.)
178 	, flagInitFOVUsage(false)
179 	, flagInitDirectionUsage(false)
180 	, flagAutosetMountForCCD(false)
181 	, flagScalingFOVForTelrad(false)
182 	, flagScalingFOVForCCD(true)
183 	, flagShowResolutionCriteria(false)
184 	, equatorialMountEnabledMain(false)
185 	, reticleRotation(0.)
186 	, flagShowCcdCropOverlay(false)
187 	, flagShowCcdCropOverlayPixelGrid(false)
188 	, ccdCropOverlayHSize(DEFAULT_CCD_CROP_OVERLAY_SIZE)
189 	, ccdCropOverlayVSize(DEFAULT_CCD_CROP_OVERLAY_SIZE)
190 	, flagShowContour(false)
191 	, flagShowCardinals(false)
192 	, flagAlignCrosshair(false)
193 	, telradFOV(0.5f,2.f,4.f)
194 	, flagShowFocuserOverlay(false)
195 	, flagUseSmallFocuserOverlay(false)
196 	, flagUseMediumFocuserOverlay(true)
197 	, flagUseLargeFocuserOverlay(true)
198 {
199 	setObjectName("Oculars");
200 	// Design font size is 14, based on default app fontsize 13.
201 	setFontSizeFromApp(StelApp::getInstance().getScreenFontSize());
202 	connect(&StelApp::getInstance(), SIGNAL(screenFontSizeChanged(int)), this, SLOT(setFontSizeFromApp(int)));
203 
204 	ccds = QList<CCD *>();
205 	oculars = QList<Ocular *>();
206 	telescopes = QList<Telescope *>();
207 	lenses = QList<Lens *> ();
208 
209 #ifdef Q_OS_MAC
210 	qt_set_sequence_auto_mnemonic(true);
211 #endif
212 }
213 
~Oculars()214 Oculars::~Oculars()
215 {
216 	delete ocularDialog;
217 	ocularDialog = Q_NULLPTR;
218 	if (guiPanel)
219 		delete guiPanel;
220 	if (pxmapGlow)
221 		delete pxmapGlow;
222 	if (pxmapOnIcon)
223 		delete pxmapOnIcon;
224 	if (pxmapOffIcon)
225 		delete pxmapOffIcon;
226 
227 	qDeleteAll(ccds);
228 	ccds.clear();
229 	qDeleteAll(telescopes);
230 	telescopes.clear();
231 	qDeleteAll(oculars);
232 	oculars.clear();
233 	qDeleteAll(lenses);
234 	lenses.clear();
235 }
236 
getSettings()237 QSettings* Oculars::getSettings()
238 {
239 	return settings;
240 }
241 
242 
243 /* ****************************************************************************************************************** */
244 #if 0
245 #pragma mark -
246 #pragma mark StelModule Methods
247 #endif
248 /* ****************************************************************************************************************** */
configureGui(bool show)249 bool Oculars::configureGui(bool show)
250 {
251 	if (show)
252 	{
253 		ocularDialog->setVisible(true);
254 	}
255 
256 	return ready;
257 }
258 
deinit()259 void Oculars::deinit()
260 {
261 	// update the ini file.
262 	settings->remove("ccd");
263 	settings->remove("ocular");
264 	settings->remove("telescope");
265 	settings->remove("lens");
266 	int index = 0;
267 	for (auto* ccd : qAsConst(ccds))
268 	{
269 		ccd->writeToSettings(settings, index);
270 		index++;
271 	}
272 	index = 0;
273 	for (auto* ocular : qAsConst(oculars))
274 	{
275 		ocular->writeToSettings(settings, index);
276 		index++;
277 	}
278 	index = 0;
279 	for (auto* telescope : qAsConst(telescopes))
280 	{
281 		telescope->writeToSettings(settings, index);
282 		index++;
283 	}
284 	index = 0;
285 	for (auto* lens : qAsConst(lenses))
286 	{
287 		lens->writeToSettings(settings, index);
288 		index++;
289 	}
290 
291 	settings->setValue("ocular_count", oculars.count());
292 	settings->setValue("telescope_count", telescopes.count());
293 	settings->setValue("ccd_count", ccds.count());
294 	settings->setValue("lens_count", lenses.count());
295 	settings->setValue("ocular_index", selectedOcularIndex);
296 	settings->setValue("telescope_index", selectedTelescopeIndex);
297 	settings->setValue("ccd_index", selectedCCDIndex);
298 	settings->setValue("lens_index", selectedLensIndex);
299 
300 	StelCore *core = StelApp::getInstance().getCore();
301 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
302 	disconnect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double)));
303 	disconnect(skyDrawer, SIGNAL(flagStarMagnitudeLimitChanged(bool)), this, SLOT(handleStarMagLimitToggle(bool)));
304 	if (flagShowCCD)
305 	{
306 		// Retrieve and restore star scales
307 		relativeStarScaleCCD=skyDrawer->getRelativeStarScale();
308 		absoluteStarScaleCCD=skyDrawer->getAbsoluteStarScale();
309 		skyDrawer->setRelativeStarScale(relativeStarScaleMain);
310 		skyDrawer->setAbsoluteStarScale(absoluteStarScaleMain);
311 	}
312 	else if (flagShowOculars)
313 	{
314 		qDebug() << "Oculars::deinit() .. restoring skyDrawer values while ocular view is active";
315 
316 		if (!getFlagAutoLimitMagnitude())
317 		{
318 			flagLimitStarsOculars=skyDrawer->getFlagStarMagnitudeLimit();
319 			magLimitStarsOculars=skyDrawer->getCustomStarMagnitudeLimit();
320 		}
321 		skyDrawer->setCustomStarMagnitudeLimit(magLimitStarsMain);
322 		skyDrawer->setFlagStarMagnitudeLimit(flagLimitStarsMain);
323 		// Retrieve and restore star scales
324 		relativeStarScaleOculars=skyDrawer->getRelativeStarScale();
325 		absoluteStarScaleOculars=skyDrawer->getAbsoluteStarScale();
326 		skyDrawer->setRelativeStarScale(relativeStarScaleMain);
327 		skyDrawer->setAbsoluteStarScale(absoluteStarScaleMain);
328 	}
329 
330 	settings->setValue("stars_scale_relative", QString::number(relativeStarScaleOculars, 'f', 2));
331 	settings->setValue("stars_scale_absolute", QString::number(absoluteStarScaleOculars, 'f', 2));
332 	settings->setValue("stars_scale_relative_ccd", QString::number(relativeStarScaleCCD, 'f', 2));
333 	settings->setValue("stars_scale_absolute_ccd", QString::number(absoluteStarScaleCCD, 'f', 2));
334 	settings->setValue("limit_stellar_magnitude_oculars_val", QString::number(magLimitStarsOculars, 'f', 2));
335 	settings->setValue("limit_stellar_magnitude_oculars", flagLimitStarsOculars);
336 	settings->setValue("text_color", textColor.toStr());
337 	settings->setValue("line_color", lineColor.toStr());
338 	settings->setValue("focuser_color", focuserColor.toStr());
339 	settings->sync();
340 
341 	disconnect(this, SIGNAL(selectedOcularChanged(int)), this, SLOT(updateOcularReticle()));
342 	//disconnect(&StelApp::getInstance(), SIGNAL(colorSchemeChanged(const QString&)), this, SLOT(setStelStyle(const QString&)));
343 	disconnect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslateGui()));
344 
345 	protractorTexture.clear();
346 	protractorFlipVTexture.clear();
347 	protractorFlipHTexture.clear();
348 	protractorFlipHVTexture.clear();
349 }
350 
351 //! Draw any parts on the screen which are for our module
draw(StelCore * core)352 void Oculars::draw(StelCore* core)
353 {
354 	if (flagShowTelrad)
355 	{
356 		paintTelrad();
357 	}
358 	else if (flagShowOculars)
359 	{
360 		if (selectedOcularIndex >= 0)
361 		{
362 			paintOcularMask(core);
363 			if (flagShowCrosshairs)
364 				paintCrosshairs();
365 
366 			if (!flagGuiPanelEnabled)
367 			{
368 				// Paint the information in the upper-right hand corner
369 				paintText(core);
370 			}
371 		}
372 		else
373 		{
374 			qWarning() << "Oculars: the selected ocular index of "
375 				   << selectedOcularIndex << " is greater than the ocular count of "
376 				   << oculars.count() << ". Module disabled!";
377 		}
378 	}
379 	else if (flagShowCCD)
380 	{
381 		paintCCDBounds();
382 		if (!flagGuiPanelEnabled)
383 		{
384 			// Paint the information in the upper-right hand corner
385 			paintText(core);
386 		}
387 	}
388 }
389 
390 //! Determine which "layer" the plugin's drawing will happen on.
getCallOrder(StelModuleActionName actionName) const391 double Oculars::getCallOrder(StelModuleActionName actionName) const
392 {
393 	double order = 1000.0; // Very low priority, unless we interact.
394 
395 	if (actionName==StelModule::ActionHandleMouseMoves ||
396 	    actionName==StelModule::ActionHandleMouseClicks)
397 	{
398 		// Make sure we are called before MovementMgr (we need to even call it once!)
399 		order = StelApp::getInstance().getModuleMgr().getModule("StelMovementMgr")->getCallOrder(actionName) - 1.0;
400 	}
401 	else if (actionName==StelModule::ActionDraw)
402 	{
403 		order = GETSTELMODULE(LabelMgr)->getCallOrder(actionName) + 100.0;
404 	}
405 	return order;
406 }
407 
handleMouseClicks(class QMouseEvent * event)408 void Oculars::handleMouseClicks(class QMouseEvent* event)
409 {
410 	StelCore *core = StelApp::getInstance().getCore();
411 	const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000, StelCore::RefractionAuto);
412 	StelProjector::StelProjectorParams params = core->getCurrentStelProjectorParams();
413 	qreal ppx = params.devicePixelsPerPixel;
414 
415 	if (guiPanel)
416 	{
417 		// Remove all events on the sky within Ocular GUI Panel.
418 		if (event->x()>guiPanel->pos().x() && event->y()>(prj->getViewportHeight()-guiPanel->size().height()))
419 		{
420 			event->setAccepted(true);
421 			return;
422 		}
423 	}
424 
425 	// In case we show oculars with black circle, ignore mouse presses outside image circle:
426 	// https://sourceforge.net/p/stellarium/discussion/278769/thread/57893bb3/?limit=25#75c0
427 	if ((flagShowOculars) ) //&& !getFlagUseSemiTransparency()) // Not sure: ignore or allow selection of semi-hidden stars?
428 	{
429 		float wh = prj->getViewportWidth()*0.5f; // get half of width of the screen
430 		float hh = prj->getViewportHeight()*0.5f; // get half of height of the screen
431 		float mx = event->x()-wh; // point 0 in center of the screen, axis X directed to right
432 		float my = event->y()-hh; // point 0 in center of the screen, axis Y directed to bottom
433 
434 		double inner = 0.5 * params.viewportFovDiameter * ppx;
435 		// See if we need to scale the mask
436 		if (flagScaleImageCircle && oculars[selectedOcularIndex]->apparentFOV() > 0.0 && !oculars[selectedOcularIndex]->isBinoculars())
437 		{
438 			inner = oculars[selectedOcularIndex]->apparentFOV() * inner / maxEyepieceAngle;
439 		}
440 
441 		if (mx*mx+my*my>static_cast<float>(inner*inner)) // click outside ocular circle? Gobble event.
442 		{
443 			event->setAccepted(true);
444 			return;
445 		}
446 	}
447 
448 	StelMovementMgr *movementManager = core->getMovementMgr();
449 
450 	if (flagShowOculars)
451 		movementManager->handleMouseClicks(event); // force it here for selection!
452 
453 	if (StelApp::getInstance().getStelObjectMgr().getWasSelected())
454 	{
455 		if (flagShowOculars)
456 		{
457 			// center the selected object in the ocular, and track.
458 			movementManager->setFlagTracking(true);
459 		}
460 		else
461 		{
462 			// remove the usage label if it is being displayed.
463 			hideUsageMessageIfDisplayed();
464 		}
465 	}
466 	else if(flagShowOculars)
467 	{
468 		// The ocular is displayed, but no object is selected.  So don't track the stars.  We may have locked
469 		// the position of the screen if the movement keys were used.  so call this to be on the safe side.
470 		movementManager->setFlagLockEquPos(false);
471 	}
472 	event->setAccepted(false);
473 }
474 
init()475 void Oculars::init()
476 {
477 	// Load settings from ocular.ini
478 	try {
479 		validateAndLoadIniFile();
480 		// assume all is well
481 		ready = true;
482 
483 		setFlagRequireSelection(settings->value("require_selection_to_zoom", true).toBool());
484 		flagScaleImageCircle = settings->value("use_max_exit_circle", false).toBool();
485 		int ocularCount = settings->value("ocular_count", 0).toInt();
486 		int actualOcularCount = ocularCount;
487 		for (int index = 0; index < ocularCount; index++)
488 		{
489 			Ocular *newOcular = Ocular::ocularFromSettings(settings, index);
490 			if (newOcular != Q_NULLPTR)
491 			{
492 				oculars.append(newOcular);
493 			}
494 			else
495 			{
496 				actualOcularCount--;
497 			}
498 		}
499 		if (actualOcularCount < 1)
500 		{
501 			if (actualOcularCount < ocularCount)
502 			{
503 				qWarning() << "The Oculars ini file appears to be corrupt; delete it.";
504 			}
505 			else
506 			{
507 				qWarning() << "There are no oculars defined for the Oculars plugin; plugin will be disabled.";
508 			}
509 			ready = false;
510 		}
511 		else
512 		{
513 			selectedOcularIndex = settings->value("ocular_index", 0).toInt();
514 		}
515 
516 		int ccdCount = settings->value("ccd_count", 0).toInt();
517 		int actualCcdCount = ccdCount;
518 		for (int index = 0; index < ccdCount; index++)
519 		{
520 			CCD *newCCD = CCD::ccdFromSettings(settings, index);
521 			if (newCCD != Q_NULLPTR)
522 			{
523 				ccds.append(newCCD);
524 			}
525 			else
526 			{
527 				actualCcdCount--;
528 			}
529 		}
530 		if (actualCcdCount < ccdCount)
531 		{
532 			qWarning() << "The Oculars ini file appears to be corrupt; delete it.";
533 			ready = false;
534 		}
535 		selectedCCDIndex = settings->value("ccd_index", 0).toInt();
536 
537 		int telescopeCount = settings->value("telescope_count", 0).toInt();
538 		int actualTelescopeCount = telescopeCount;
539 		for (int index = 0; index < telescopeCount; index++)
540 		{
541 			Telescope *newTelescope = Telescope::telescopeFromSettings(settings, index);
542 			if (newTelescope != Q_NULLPTR)
543 			{
544 				telescopes.append(newTelescope);
545 			}
546 			else
547 			{
548 				actualTelescopeCount--;
549 			}
550 		}
551 		if (actualTelescopeCount < 1)
552 		{
553 			if (actualTelescopeCount < telescopeCount)
554 			{
555 				qWarning() << "The Oculars ini file appears to be corrupt; delete it.";
556 			}
557 			else
558 			{
559 				qWarning() << "There are no telescopes defined for the Oculars plugin; plugin will be disabled.";
560 			}
561 			ready = false;
562 		}
563 		else
564 		{
565 			selectedTelescopeIndex = settings->value("telescope_index", 0).toInt();
566 		}
567 
568 		int lensCount = settings->value("lens_count", 0).toInt();
569 		int actualLensCount = lensCount;
570 		for (int index = 0; index<lensCount; index++)
571 		{
572 			Lens *newLens = Lens::lensFromSettings(settings, index);
573 			if (newLens != Q_NULLPTR)
574 			{
575 				lenses.append(newLens);
576 			}
577 			else
578 			{
579 				actualLensCount--;
580 			}
581 		}
582 		if (lensCount > 0 && actualLensCount < lensCount)
583 		{
584 			qWarning() << "The Oculars ini file appears to be corrupt; delete it.";
585 		}
586 		selectedLensIndex=settings->value("lens_index", -1).toInt(); // Lens is not selected by default!
587 
588 		pxmapGlow = new QPixmap(":/graphicGui/miscGlow32x32.png");
589 		pxmapOnIcon = new QPixmap(":/ocular/bt_ocular_on.png");
590 		pxmapOffIcon = new QPixmap(":/ocular/bt_ocular_off.png");
591 
592 		ocularDialog = new OcularDialog(this, &ccds, &oculars, &telescopes, &lenses);
593 		initializeActivationActions();
594 		determineMaxEyepieceAngle();
595 
596 		guiPanelFontSize=settings->value("gui_panel_fontsize", 12).toInt();
597 		enableGuiPanel(settings->value("enable_control_panel", true).toBool());
598 		textColor=Vec3f(settings->value("text_color", "0.8,0.48,0.0").toString());
599 		lineColor=Vec3f(settings->value("line_color", "0.77,0.14,0.16").toString());
600 		telradFOV=Vec4f(settings->value("telrad_fov", "0.5,2.0,4.0,0.0").toString());
601 		focuserColor=Vec3f(settings->value("focuser_color", "0.0,0.67,1.0").toString());
602 
603 		// This must come ahead of setFlagAutosetMountForCCD (GH #505)
604 		StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
605 		equatorialMountEnabledMain = propMgr->getStelPropertyValue("StelMovementMgr.equatorialMount").toBool();
606 
607 		// For historical reasons, name of .ini entry and description of checkbox (and therefore flag name) are reversed.
608 		setFlagDMSDegrees( ! settings->value("use_decimal_degrees", false).toBool());
609 		setFlagAutoLimitMagnitude(settings->value("autolimit_stellar_magnitude", true).toBool());
610 		flagLimitStarsOculars=settings->value("limit_stellar_magnitude_oculars", false).toBool();
611 		magLimitStarsOculars=settings->value("limit_stellar_magnitude_oculars_val", 12.).toDouble();
612 		connect(this, SIGNAL(flagAutoLimitMagnitudeChanged(bool)), this, SLOT(handleAutoLimitToggle(bool))); // only after first initialisation!
613 		setFlagInitFovUsage(settings->value("use_initial_fov", false).toBool());
614 		setFlagInitDirectionUsage(settings->value("use_initial_direction", false).toBool());
615 		setFlagUseSemiTransparency(settings->value("use_semi_transparency", false).toBool());
616 		setTransparencyMask(settings->value("transparency_mask", 85).toInt());
617 		setFlagHideGridsLines(settings->value("hide_grids_and_lines", true).toBool());
618 		setFlagAutosetMountForCCD(settings->value("use_mount_autoset", false).toBool());
619 		setFlagScalingFOVForTelrad(settings->value("use_telrad_fov_scaling", true).toBool());
620 		setFlagScalingFOVForCCD(settings->value("use_ccd_fov_scaling", true).toBool());
621 		setFlagShowResolutionCriteria(settings->value("show_resolution_criteria", false).toBool());
622 		// TODO: Remove this conversion tool in version 0.21 or 0.22
623 		if (settings->value("arrow_scale").toDouble()<100.) // convert old value and type
624 			setArrowButtonScale(static_cast<int>(settings->value("arrow_scale", 1.5).toDouble()*100.));
625 		else
626 			setArrowButtonScale(settings->value("arrow_scale", 150).toInt());
627 		setFlagShowOcularsButton(settings->value("show_toolbar_button", false).toBool());
628 		relativeStarScaleOculars=settings->value("stars_scale_relative", 1.0).toDouble();
629 		absoluteStarScaleOculars=settings->value("stars_scale_absolute", 1.0).toDouble();
630 		relativeStarScaleCCD=settings->value("stars_scale_relative_ccd", 1.0).toDouble();
631 		absoluteStarScaleCCD=settings->value("stars_scale_absolute_ccd", 1.0).toDouble();
632 		setFlagShowCcdCropOverlay(settings->value("show_ccd_crop_overlay", false).toBool());
633 		setFlagShowCcdCropOverlayPixelGrid(settings-> value("ccd_crop_overlay_pixel_grid",false).toBool());
634 		setCcdCropOverlayHSize(settings->value("ccd_crop_overlay_hsize", DEFAULT_CCD_CROP_OVERLAY_SIZE).toInt());
635 		setCcdCropOverlayVSize(settings->value("ccd_crop_overlay_vsize", DEFAULT_CCD_CROP_OVERLAY_SIZE).toInt());
636 		setFlagShowContour(settings->value("show_ocular_contour", false).toBool());
637 		setFlagShowCardinals(settings->value("show_ocular_cardinals", false).toBool());
638 		setFlagAlignCrosshair(settings->value("align_crosshair", false).toBool());
639 		setFlagShowFocuserOverlay(settings->value("show_focuser_overlay", false).toBool());
640 		setFlagUseSmallFocuserOverlay(settings->value("use_small_focuser_overlay", false).toBool());
641 		setFlagUseMediumFocuserOverlay(settings->value("use_medium_focuser_overlay", true).toBool());
642 		setFlagUseLargeFocuserOverlay(settings->value("use_large_focuser_overlay", false).toBool());
643 	}
644 	catch (std::runtime_error& e)
645 	{
646 		qWarning() << "WARNING: unable to locate ocular.ini file or create a default one for Ocular plugin: " << e.what();
647 		ready = false;
648 	}
649 
650 	protractorTexture = StelApp::getInstance().getTextureManager().createTexture(":/ocular/Protractor.png");
651 	protractorFlipHTexture = StelApp::getInstance().getTextureManager().createTexture(":/ocular/ProtractorFlipH.png");
652 	protractorFlipVTexture = StelApp::getInstance().getTextureManager().createTexture(":/ocular/ProtractorFlipV.png");
653 	protractorFlipHVTexture = StelApp::getInstance().getTextureManager().createTexture(":/ocular/ProtractorFlipHV.png");
654 	// enforce check existence of reticle for the current eyepiece
655 	updateOcularReticle();
656 
657 	connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslateGui()));
658 	connect(this, SIGNAL(selectedOcularChanged(int)), this, SLOT(updateOcularReticle()));
659 	StelCore *core = StelApp::getInstance().getCore();
660 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
661 	connect(skyDrawer, SIGNAL(flagStarMagnitudeLimitChanged(bool)), this, SLOT(handleStarMagLimitToggle(bool)));
662 }
663 
664 /* ****************************************************************************************************************** */
665 #if 0
666 #pragma mark -
667 #pragma mark Private slots Methods
668 #endif
669 /* ****************************************************************************************************************** */
determineMaxEyepieceAngle()670 void Oculars::determineMaxEyepieceAngle()
671 {
672 	if (ready)
673 	{
674 		for (const auto* ocular : oculars)
675 		{
676 			if (ocular->apparentFOV() > maxEyepieceAngle)
677 			{
678 				maxEyepieceAngle = ocular->apparentFOV();
679 			}
680 		}
681 	}
682 	// ensure it is not zero
683 	if (maxEyepieceAngle == 0.0)
684 	{
685 		maxEyepieceAngle = 1.0;
686 	}
687 }
688 
instrumentChanged()689 void Oculars::instrumentChanged()
690 {
691 	// We only zoom if in ocular mode.
692 	if (flagShowOculars)
693 	{
694 		// If we are already in Ocular mode, we must reset scalings because zoom() also resets.
695 		StelSkyDrawer *skyDrawer = StelApp::getInstance().getCore()->getSkyDrawer();
696 		skyDrawer->setRelativeStarScale(relativeStarScaleMain);
697 		skyDrawer->setAbsoluteStarScale(absoluteStarScaleMain);
698 		zoom(true);
699 	}
700 	else if (flagShowCCD)
701 		setScreenFOVForCCD();
702 }
703 
setFlagScaleImageCircle(bool state)704 void Oculars::setFlagScaleImageCircle(bool state)
705 {
706 	if (state)
707 	{
708 		determineMaxEyepieceAngle();
709 	}
710 	flagScaleImageCircle = state;
711 	settings->setValue("use_max_exit_circle", state);
712 	settings->sync();
713 	emit flagScaleImageCircleChanged(state);
714 }
715 
setScreenFOVForCCD()716 void Oculars::setScreenFOVForCCD()
717 {
718 	// CCD is not shown and FOV scaling is disabled, but telescope is changed - do not change FOV!
719 	if (!(getFlagScalingFOVForCCD() && flagShowCCD))
720 		return;
721 
722 	Lens * lens = selectedLensIndex >=0  ? lenses[selectedLensIndex] : Q_NULLPTR;
723 	if (selectedCCDIndex > -1 && selectedTelescopeIndex > -1)
724 	{
725 		StelCore *core = StelApp::getInstance().getCore();
726 		StelMovementMgr *movementManager = core->getMovementMgr();
727 		double actualFOVx = ccds[selectedCCDIndex]->getActualFOVx(telescopes[selectedTelescopeIndex], lens);
728 		double actualFOVy = ccds[selectedCCDIndex]->getActualFOVy(telescopes[selectedTelescopeIndex], lens);
729 		if (actualFOVx < actualFOVy)
730 		{
731 			actualFOVx = actualFOVy;
732 		}
733 		double factor = 1.75;
734 		if (ccds[selectedCCDIndex]->hasOAG()) factor *= 2;
735 		movementManager->setFlagTracking(true);
736 		movementManager->zoomTo(actualFOVx * factor, 0.f);
737 	}
738 }
739 
enableGuiPanel(bool enable)740 void Oculars::enableGuiPanel(bool enable)
741 {
742 	if (enable)
743 	{
744 		if (!guiPanel)
745 		{
746 			StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
747 			if (gui)
748 			{
749 				guiPanel = new OcularsGuiPanel(this, gui->getSkyGui());
750 				if (flagShowOculars)
751 					guiPanel->showOcularGui();
752 				else if (flagShowCCD)
753 					guiPanel->showCcdGui();
754 			}
755 		}
756 	}
757 	else
758 	{
759 		if (guiPanel)
760 		{
761 			guiPanel->hide();
762 			delete guiPanel;
763 			guiPanel = Q_NULLPTR;
764 		}
765 	}
766 	flagGuiPanelEnabled = enable;
767 	settings->setValue("enable_control_panel", enable);
768 	settings->sync();
769 	emit flagGuiPanelEnabledChanged(enable);
770 }
771 
retranslateGui()772 void Oculars::retranslateGui()
773 {
774 	if (guiPanel)
775 	{
776 		// TODO: Fix this hack!
777 
778 		// Delete and re-create the panel to retranslate its trings
779 		guiPanel->hide();
780 		delete guiPanel;
781 		guiPanel = Q_NULLPTR;
782 
783 		StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
784 		if (gui)
785 		{
786 			guiPanel = new OcularsGuiPanel(this, gui->getSkyGui());
787 			if (flagShowOculars)
788 				guiPanel->showOcularGui();
789 			else if (flagShowCCD)
790 				guiPanel->showCcdGui();
791 		}
792 	}
793 }
794 
updateOcularReticle(void)795 void Oculars::updateOcularReticle(void)
796 {
797 	reticleRotation = 0.0;
798 	QString reticleTexturePath=oculars[selectedOcularIndex]->reticlePath();
799 	if (reticleTexturePath.length()==0)
800 		reticleTexture=StelTextureSP();
801 	else
802 	{
803 		StelTextureMgr& manager = StelApp::getInstance().getTextureManager();
804 		//Load OpenGL textures
805 		StelTexture::StelTextureParams params;
806 		params.generateMipmaps = true;
807 		reticleTexture = manager.createTexture(reticleTexturePath, params);
808 	}
809 }
810 
811 /* ****************************************************************************************************************** */
812 #if 0
813 #pragma mark -
814 #pragma mark Slots Methods
815 #endif
816 /* ****************************************************************************************************************** */
updateLists()817 void Oculars::updateLists()
818 {
819 	if (oculars.isEmpty())
820 	{
821 		selectedOcularIndex = -1;
822 		enableOcular(false);
823 	}
824 	else
825 	{
826 		if (selectedOcularIndex >= oculars.count())
827 			selectedOcularIndex = oculars.count() - 1;
828 
829 		if (flagShowOculars)
830 			emit selectedOcularChanged(selectedOcularIndex);
831 	}
832 
833 	if (telescopes.isEmpty())
834 	{
835 		selectedTelescopeIndex = -1;
836 		enableOcular(false);
837 		toggleCCD(false);
838 	}
839 	else
840 	{
841 		if (selectedTelescopeIndex >= telescopes.count())
842 			selectedTelescopeIndex = telescopes.count() - 1;
843 
844 		if (flagShowOculars || flagShowCCD)
845 			emit selectedTelescopeChanged(selectedTelescopeIndex);
846 	}
847 
848 	if (ccds.isEmpty())
849 	{
850 		selectedCCDIndex = -1;
851 		toggleCCD(false);
852 	}
853 	else
854 	{
855 		if (selectedCCDIndex >= ccds.count())
856 			selectedCCDIndex = ccds.count() - 1;
857 
858 		if (flagShowCCD)
859 			emit selectedCCDChanged(selectedCCDIndex);
860 	}
861 
862 	if (lenses.isEmpty())
863 		selectedLensIndex = -1;
864 	else
865 	{
866 		if (selectedLensIndex >= lenses.count())
867 			selectedLensIndex = lenses.count() - 1;
868 	}
869 }
870 
ccdRotationReset()871 void Oculars::ccdRotationReset()
872 {
873 	if (selectedCCDIndex<0)
874 		return;
875 	CCD *ccd = ccds[selectedCCDIndex];
876 	if (ccd)
877 	{
878 		ccd->setChipRotAngle(0.0);
879 		emit(selectedCCDChanged(selectedCCDIndex));
880 		emit selectedCCDRotationAngleChanged(0.0);
881 	}
882 }
883 
prismPositionAngleReset()884 void Oculars::prismPositionAngleReset()
885 {
886 	if (selectedCCDIndex<0)
887 		return;
888 	CCD *ccd = ccds[selectedCCDIndex];
889 	if (ccd)
890 	{
891 		ccd->setPrismPosAngle(0.0);
892 		emit(selectedCCDChanged(selectedCCDIndex));
893 		emit selectedCCDPrismPositionAngleChanged(0.0);
894 	}
895 }
896 
setSelectedCCDRotationAngle(double angle)897 void Oculars::setSelectedCCDRotationAngle(double angle)
898 {
899 	if (selectedCCDIndex<0)
900 		return;
901 
902 	CCD *ccd = ccds[selectedCCDIndex];
903 	if (ccd)
904 	{
905 		ccd->setChipRotAngle(angle);
906 		emit selectedCCDRotationAngleChanged(angle);
907 	}
908 }
909 
getSelectedCCDRotationAngle() const910 double Oculars::getSelectedCCDRotationAngle() const
911 {
912 	if (selectedCCDIndex<0)
913 		return 0.0;
914 	CCD *ccd = ccds[selectedCCDIndex];
915 	if (ccd) return ccd->chipRotAngle();
916 	else return 0.0;
917 }
918 
setSelectedCCDPrismPositionAngle(double angle)919 void Oculars::setSelectedCCDPrismPositionAngle(double angle)
920 {
921 	if (selectedCCDIndex<0)
922 		return;
923 
924 	CCD *ccd = ccds[selectedCCDIndex];
925 	if (ccd)
926 	{
927 		ccd->setPrismPosAngle(angle);
928 		emit selectedCCDPrismPositionAngleChanged(angle);
929 	}
930 }
931 
getSelectedCCDPrismPositionAngle() const932 double Oculars::getSelectedCCDPrismPositionAngle() const
933 {
934 	if (selectedCCDIndex<0)
935 		return 0.0;
936 	CCD *ccd = ccds[selectedCCDIndex];
937 	if (ccd) return ccd->prismPosAngle();
938 	else return 0.0;
939 }
940 
enableOcular(bool enableOcularMode)941 void Oculars::enableOcular(bool enableOcularMode)
942 {
943 	if (enableOcularMode)
944 	{
945 		// Close the sensor view if it's displayed
946 		if (flagShowCCD)
947 		{
948 			toggleCCD(false);
949 			flagShowCCD = false;
950 			selectedCCDIndex = -1;
951 		}
952 
953 		// Close the Telrad sight if it's displayed
954 		if (flagShowTelrad)
955 		{
956 			toggleTelrad(false);
957 		}
958 
959 		// Check to ensure that we have enough oculars & telescopes, as they may have been edited in the config dialog
960 		if (oculars.count() == 0)
961 		{
962 			selectedOcularIndex = -1;
963 			qWarning() << "No oculars found";
964 		}
965 		else if (oculars.count() > 0 && selectedOcularIndex == -1)
966 		{
967 			selectedOcularIndex = 0;
968 		}
969 		if (telescopes.count() == 0)
970 		{
971 			selectedTelescopeIndex = -1;
972 			qWarning() << "No telescopes found";
973 		}
974 		else if (telescopes.count() > 0 && selectedTelescopeIndex == -1)
975 		{
976 			selectedTelescopeIndex = 0;
977 		}
978 	}
979 
980 	if (!ready  || selectedOcularIndex == -1 ||  (selectedTelescopeIndex == -1 && !isBinocularDefined()))
981 	{
982 		qWarning() << "The Oculars module has been disabled.";
983 		return;
984 	}
985 
986 	StelCore *core = StelApp::getInstance().getCore();
987 	LabelMgr* labelManager = GETSTELMODULE(LabelMgr);
988 
989 	// Toggle the ocular view on & off. To toggle on, we want to ensure there is a selected object.
990 	if (!flagShowOculars && !flagShowTelrad && flagRequireSelection && !StelApp::getInstance().getStelObjectMgr().getWasSelected() )
991 	{
992 		if (usageMessageLabelID == -1)
993 		{
994 			QFontMetrics metrics(font);
995 			QString labelText = q_("Please select an object before switching to ocular view.");
996 			StelProjector::StelProjectorParams projectorParams = core->getCurrentStelProjectorParams();
997 			int yPositionOffset = qRound(projectorParams.viewportXywh[3]*projectorParams.viewportCenterOffset[1]);
998 			int xPosition = qRound(projectorParams.viewportCenter[0] - 0.5 * metrics.boundingRect(labelText).width());
999 			int yPosition = qRound(projectorParams.viewportCenter[1] - yPositionOffset - 0.5 * metrics.height());
1000 			const char *tcolor = "#99FF99";
1001 			usageMessageLabelID = labelManager->labelScreen(labelText, xPosition, yPosition,
1002 									true, font.pixelSize(), tcolor);
1003 		}
1004 	}
1005 	else
1006 	{
1007 		if (selectedOcularIndex != -1)
1008 		{
1009 			// remove the usage label if it is being displayed.
1010 			hideUsageMessageIfDisplayed();
1011 			flagShowOculars = enableOcularMode;
1012 			zoom(false);
1013 			//BM: I hope this is the right place...
1014 			if (guiPanel)
1015 				guiPanel->showOcularGui();
1016 		}
1017 	}
1018 
1019 	emit enableOcularChanged(flagShowOculars);
1020 }
1021 
decrementCCDIndex()1022 void Oculars::decrementCCDIndex()
1023 {
1024 	selectedCCDIndex--;
1025 	if (selectedCCDIndex == -1)
1026 	{
1027 		selectedCCDIndex = ccds.count() - 1;
1028 	}
1029 	emit(selectedCCDChanged(selectedCCDIndex));
1030 }
1031 
decrementOcularIndex()1032 void Oculars::decrementOcularIndex()
1033 {
1034 	selectedOcularIndex--;
1035 	if (selectedOcularIndex == -1)
1036 	{
1037 		selectedOcularIndex = oculars.count() - 1;
1038 	}
1039 	// validate the new selection
1040 	if (selectedOcularIndex > -1 && !oculars[selectedOcularIndex]->isBinoculars())
1041 	{
1042 		if ( selectedTelescopeIndex == -1 && telescopes.count() == 0)
1043 		{
1044 			// reject the change
1045 			selectedOcularIndex++;
1046 		}
1047 
1048 		if (selectedTelescopeIndex == -1)
1049 			selectedTelescopeIndex = 0;
1050 	}
1051 	emit(selectedOcularChanged(selectedOcularIndex));
1052 }
1053 
decrementTelescopeIndex()1054 void Oculars::decrementTelescopeIndex()
1055 {
1056 	selectedTelescopeIndex--;
1057 	if (selectedTelescopeIndex == -1)
1058 	{
1059 		selectedTelescopeIndex = telescopes.count() - 1;
1060 	}
1061 	emit(selectedTelescopeChanged(selectedTelescopeIndex));
1062 }
1063 
decrementLensIndex()1064 void Oculars::decrementLensIndex()
1065 {
1066 	selectedLensIndex--;
1067 	if (selectedLensIndex == lenses.count())
1068 	{
1069 		selectedLensIndex = -1;
1070 	}
1071 	if (selectedLensIndex == -2)
1072 	{
1073 		selectedLensIndex = lenses.count() - 1;
1074 	}
1075 	emit(selectedLensChanged(selectedLensIndex));
1076 }
1077 
rotateReticleClockwise()1078 void Oculars::rotateReticleClockwise()
1079 {
1080 	// Step: 5 degrees
1081 	reticleRotation -= 5.0;
1082 }
1083 
rotateReticleCounterclockwise()1084 void Oculars::rotateReticleCounterclockwise()
1085 {
1086 	// Step: 5 degrees
1087 	reticleRotation += 5.0;
1088 }
1089 
displayPopupMenu()1090 void Oculars::displayPopupMenu()
1091 {
1092 	QMenu * popup = new QMenu(&StelMainView::getInstance());
1093 
1094 	if (flagShowOculars)
1095 	{
1096 		// We are in Oculars mode
1097 		// We want to show all of the Oculars, and if the current ocular is not a binocular,
1098 		// we will also show the telescopes.
1099 		if (!oculars.isEmpty())
1100 		{
1101 			popup->addAction(q_("&Previous ocular"), this, SLOT(decrementOcularIndex()));
1102 			popup->addAction(q_("&Next ocular"), this, SLOT(incrementOcularIndex()));
1103 			QMenu* submenu = new QMenu(q_("Select &ocular"), popup);
1104 			int availableOcularCount = 0;
1105 			for (int index = 0; index < oculars.count(); ++index)
1106 			{
1107 				QString label;
1108 				if (availableOcularCount < 10)
1109 				{
1110 					label = QString("&%1: %2").arg(availableOcularCount).arg(oculars[index]->name());
1111 				}
1112 				else
1113 				{
1114 					label = oculars[index]->name();
1115 				}
1116 				//BM: Does this happen at all any more?
1117 				QAction* action = Q_NULLPTR;
1118 				if (selectedTelescopeIndex != -1 || oculars[index]->isBinoculars())
1119 				{
1120 					action = submenu->addAction(label, [=](){selectOcularAtIndex(index);});
1121 					availableOcularCount++;
1122 				}
1123 
1124 				if (action && index == selectedOcularIndex)
1125 				{
1126 					action->setCheckable(true);
1127 					action->setChecked(true);
1128 				}
1129 			}
1130 			popup->addMenu(submenu);
1131 			popup->addSeparator();
1132 		}
1133 
1134 		// If there is more than one telescope, show the prev/next/list complex.
1135 		// If the selected ocular is a binoculars, show nothing.
1136 		if (telescopes.count() > 1 && (selectedOcularIndex > -1 && !oculars[selectedOcularIndex]->isBinoculars()))
1137 		{
1138 			QMenu* submenu = addTelescopeSubmenu(popup);
1139 			popup->addMenu(submenu);
1140 			submenu = addLensSubmenu(popup);
1141 			popup->addMenu(submenu);
1142 			popup->addSeparator();
1143 		}
1144 
1145 		QAction* action = popup->addAction(q_("Toggle &crosshair"));
1146 		action->setCheckable(true);
1147 		action->setChecked(flagShowCrosshairs);
1148 		connect(action, SIGNAL(toggled(bool)), actionShowCrosshairs, SLOT(setChecked(bool)));
1149 	}
1150 	else
1151 	{
1152 		// We are not in ocular mode
1153 		// We want to show the CCD's, and if a CCD is selected, the telescopes
1154 		//(as a CCD requires a telescope) and the general menu items.
1155 		QAction* action = new QAction(q_("Configure &Oculars"), popup);
1156 		action->setCheckable(true);
1157 		action->setChecked(ocularDialog->visible());
1158 		connect(action, SIGNAL(triggered(bool)), ocularDialog, SLOT(setVisible(bool)));
1159 		popup->addAction(action);
1160 		popup->addSeparator();
1161 
1162 		if (!flagShowTelrad)
1163 		{
1164 			QAction* action = popup->addAction(q_("Toggle &CCD"));
1165 			action->setCheckable(true);
1166 			action->setChecked(flagShowCCD);
1167 			connect(action, SIGNAL(toggled(bool)), actionShowSensor, SLOT(setChecked(bool)));
1168 		}
1169 
1170 		if (!flagShowCCD)
1171 		{
1172 			QAction* action = popup->addAction(q_("Toggle &Telrad"));
1173 			action->setCheckable(true);
1174 			action->setChecked(flagShowTelrad);
1175 			connect(action, SIGNAL(toggled(bool)), actionShowTelrad, SLOT(setChecked(bool)));
1176 		}
1177 
1178 		popup->addSeparator();
1179 		if (flagShowCCD && selectedCCDIndex > -1 && selectedTelescopeIndex > -1)
1180 		{
1181 			popup->addAction(q_("&Previous CCD"), this, SLOT(decrementCCDIndex()));
1182 			popup->addAction(q_("&Next CCD"), this, SLOT(incrementCCDIndex()));
1183 			QMenu* submenu = new QMenu(q_("&Select CCD"), popup);
1184 			for (int index = 0; index < ccds.count(); ++index)
1185 			{
1186 				QString label;
1187 				if (index < 10)
1188 				{
1189 					label = QString("&%1: %2").arg(index).arg(ccds[index]->name());
1190 				}
1191 				else
1192 				{
1193 					label = ccds[index]->name();
1194 				}
1195 				QAction* action = submenu->addAction(label, [=](){selectCCDAtIndex(index);});
1196 				if (index == selectedCCDIndex)
1197 				{
1198 					action->setCheckable(true);
1199 					action->setChecked(true);
1200 				}
1201 			}
1202 			popup->addMenu(submenu);
1203 
1204 			submenu = new QMenu(q_("&Rotate CCD"), popup);
1205 			submenu->addAction(QString("&1: -90") + QChar(0x00B0), [=](){rotateCCD(-90);});
1206 			submenu->addAction(QString("&2: -45") + QChar(0x00B0), [=](){rotateCCD(-45);});
1207 			submenu->addAction(QString("&3: -15") + QChar(0x00B0), [=](){rotateCCD(-15);});
1208 			submenu->addAction(QString("&4: -5") + QChar(0x00B0),  [=](){rotateCCD(-5);});
1209 			submenu->addAction(QString("&5: -1") + QChar(0x00B0),  [=](){rotateCCD(-1);});
1210 			submenu->addAction(QString("&6: +1") + QChar(0x00B0),  [=](){rotateCCD(1);});
1211 			submenu->addAction(QString("&7: +5") + QChar(0x00B0),  [=](){rotateCCD(5);});
1212 			submenu->addAction(QString("&8: +15") + QChar(0x00B0), [=](){rotateCCD(15);});
1213 			submenu->addAction(QString("&9: +45") + QChar(0x00B0), [=](){rotateCCD(45);});
1214 			submenu->addAction(QString("&0: +90") + QChar(0x00B0), [=](){rotateCCD(90);});
1215 
1216 			submenu->addAction(q_("&Reset rotation"), this, SLOT(ccdRotationReset()));
1217 			popup->addMenu(submenu);
1218 			popup->addSeparator();
1219 		}
1220 		if (flagShowCCD && selectedCCDIndex > -1 && telescopes.count() > 1)
1221 		{
1222 			QMenu* submenu = addTelescopeSubmenu(popup);
1223 			popup->addMenu(submenu);
1224 			submenu = addLensSubmenu(popup);
1225 			popup->addMenu(submenu);
1226 			popup->addSeparator();
1227 		}
1228 	}
1229 
1230 #ifdef Q_OS_WIN
1231 	popup->showTearOffMenu(QCursor::pos());
1232 #endif
1233 	popup->exec(QCursor::pos());
1234 	delete popup;
1235 }
1236 
incrementCCDIndex()1237 void Oculars::incrementCCDIndex()
1238 {
1239 	selectedCCDIndex++;
1240 	if (selectedCCDIndex == ccds.count())
1241 	{
1242 		selectedCCDIndex = 0;
1243 	}
1244 	emit selectedCCDChanged(selectedCCDIndex);
1245 }
1246 
incrementOcularIndex()1247 void Oculars::incrementOcularIndex()
1248 {
1249 	selectedOcularIndex++;
1250 	if (selectedOcularIndex == oculars.count())
1251 	{
1252 		selectedOcularIndex = 0;
1253 	}
1254 	// validate the new selection
1255 	if (selectedOcularIndex > -1 && !oculars[selectedOcularIndex]->isBinoculars())
1256 	{
1257 		if ( selectedTelescopeIndex == -1 && telescopes.count() == 0)
1258 		{
1259 			// reject the change
1260 			selectedOcularIndex++;
1261 		}
1262 
1263 		if (selectedTelescopeIndex == -1)
1264 			selectTelescopeAtIndex(0);
1265 	}
1266 	emit selectedOcularChanged(selectedOcularIndex);
1267 }
1268 
incrementTelescopeIndex()1269 void Oculars::incrementTelescopeIndex()
1270 {
1271 	selectedTelescopeIndex++;
1272 	if (selectedTelescopeIndex == telescopes.count())
1273 	{
1274 		selectedTelescopeIndex = 0;
1275 	}
1276 	emit selectedTelescopeChanged(selectedTelescopeIndex);
1277 }
1278 
incrementLensIndex()1279 void Oculars::incrementLensIndex()
1280 {
1281 	selectedLensIndex++;
1282 	if (selectedLensIndex == lenses.count())
1283 	{
1284 		selectedLensIndex = -1;
1285 	}
1286 	emit selectedLensChanged(selectedLensIndex);
1287 }
1288 
disableLens()1289 void Oculars::disableLens()
1290 {
1291 	selectedLensIndex = -1;
1292 	emit selectedLensChanged(selectedLensIndex);
1293 }
1294 
rotateCCD(int amount)1295 void Oculars::rotateCCD(int amount)
1296 {
1297 	CCD *ccd = ccds[selectedCCDIndex];
1298 	if (!ccd) return;
1299 	double angle = ccd->chipRotAngle();
1300 	angle += amount;
1301 	if (angle >= 360)
1302 	{
1303 		angle -= 360;
1304 	}
1305 	else if (angle <= -360)
1306 	{
1307 		angle += 360;
1308 	}
1309 	ccd->setChipRotAngle(angle);
1310 	emit selectedCCDRotationAngleChanged(angle);
1311 }
1312 
rotatePrism(int amount)1313 void Oculars::rotatePrism(int amount)
1314 {
1315 	CCD *ccd = ccds[selectedCCDIndex];
1316 	if (!ccd) return;
1317 	double angle = ccd->prismPosAngle();
1318 	angle += amount;
1319 	if (angle >= 360)
1320 	{
1321 		angle -= 360;
1322 	}
1323 	else if (angle <= -360)
1324 	{
1325 		angle += 360;
1326 	}
1327 	ccd->setPrismPosAngle(angle);
1328 	emit selectedCCDPrismPositionAngleChanged(angle);
1329 }
1330 
selectCCDAtIndex(int index)1331 void Oculars::selectCCDAtIndex(int index)
1332 {
1333 	if (index > -1 && index < ccds.count())
1334 	{
1335 		selectedCCDIndex = index;
1336 		emit selectedCCDChanged(index);
1337 	}
1338 }
1339 
selectOcularAtIndex(int index)1340 void Oculars::selectOcularAtIndex(int index)
1341 {
1342 	if (selectedTelescopeIndex == -1)
1343 		selectTelescopeAtIndex(0);
1344 
1345 	if (index > -1 && index < oculars.count() && (telescopes.count() >= 0 || oculars[index]->isBinoculars()))
1346 	{
1347 		selectedOcularIndex = index;
1348 		emit selectedOcularChanged(index);
1349 	}
1350 }
1351 
selectTelescopeAtIndex(int index)1352 void Oculars::selectTelescopeAtIndex(int index)
1353 {
1354 	if (index > -1 && index < telescopes.count())
1355 	{
1356 		selectedTelescopeIndex = index;
1357 		emit selectedTelescopeChanged(index);
1358 	}
1359 }
1360 
selectLensAtIndex(int index)1361 void Oculars::selectLensAtIndex(int index)
1362 {
1363 	if (index > -2 && index < lenses.count())
1364 	{
1365 		selectedLensIndex = index;
1366 		emit selectedLensChanged(index);
1367 	}
1368 }
1369 
toggleCCD(bool show)1370 void Oculars::toggleCCD(bool show)
1371 {
1372 	//If there are no sensors...
1373 	if (ccds.isEmpty() || telescopes.isEmpty())
1374 	{
1375 		//TODO: BM: Make this an on-screen message and/or disable the button
1376 		//if there are no sensors.
1377 		if (show)
1378 		{
1379 			qWarning() << "Oculars plugin: Unable to display a sensor boundary: No sensors or telescopes are defined.";
1380 			QMessageBox::warning(&StelMainView::getInstance(), q_("Warning!"), q_("Unable to display a sensor boundary: No sensors or telescopes are defined."), QMessageBox::Ok);
1381 		}
1382 		flagShowCCD = false;
1383 		selectedCCDIndex = -1;
1384 		show = false;
1385 	}
1386 
1387 	StelCore *core = StelApp::getInstance().getCore();
1388 	StelMovementMgr *movementManager = core->getMovementMgr();
1389 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
1390 	if (show)
1391 	{
1392 		initialFOV = movementManager->getCurrentFov();
1393 		//Mutually exclusive with the ocular mode
1394 		hideUsageMessageIfDisplayed();
1395 		if (flagShowOculars)
1396 			enableOcular(false);
1397 
1398 		if (flagShowTelrad) {
1399 			toggleTelrad(false);
1400 		}
1401 
1402 		if (selectedTelescopeIndex < 0)
1403 		{
1404 			selectedTelescopeIndex = 0;
1405 		}
1406 		if (selectedCCDIndex < 0)
1407 		{
1408 			selectedCCDIndex = 0;
1409 		}
1410 		flagShowCCD = true;
1411 		setScreenFOVForCCD();
1412 
1413 		// Change scales for stars. (Even restoring from ocular view has restored main program's values at this point.)
1414 		relativeStarScaleMain=skyDrawer->getRelativeStarScale();
1415 		absoluteStarScaleMain=skyDrawer->getAbsoluteStarScale();
1416 		skyDrawer->setRelativeStarScale(relativeStarScaleCCD);
1417 		skyDrawer->setAbsoluteStarScale(absoluteStarScaleCCD);
1418 
1419 		if (guiPanel)
1420 		{
1421 			guiPanel->showCcdGui();
1422 		}
1423 	}
1424 	else
1425 	{
1426 		flagShowCCD = false;
1427 
1428 		// Restore star scales
1429 		relativeStarScaleCCD=skyDrawer->getRelativeStarScale();
1430 		absoluteStarScaleCCD=skyDrawer->getAbsoluteStarScale();
1431 		skyDrawer->setRelativeStarScale(relativeStarScaleMain);
1432 		skyDrawer->setAbsoluteStarScale(absoluteStarScaleMain);
1433 		//Zoom out
1434 		if (getFlagInitFovUsage())
1435 			movementManager->zoomTo(movementManager->getInitFov());
1436 		else if (!flagShowTelrad)
1437 			movementManager->zoomTo(initialFOV);
1438 
1439 		if (getFlagInitDirectionUsage())
1440 			movementManager->setViewDirectionJ2000(core->altAzToJ2000(movementManager->getInitViewingDirection(), StelCore::RefractionOff));
1441 
1442 		if (getFlagAutosetMountForCCD())
1443 		{
1444 			StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
1445 			propMgr->setStelPropertyValue("StelMovementMgr.equatorialMount", equatorialMountEnabledMain);
1446 		}
1447 
1448 		if (guiPanel)
1449 		{
1450 			guiPanel->foldGui();
1451 		}
1452 	}
1453 
1454 	emit enableCCDChanged(flagShowCCD);
1455 }
1456 
toggleCCD()1457 void Oculars::toggleCCD()
1458 {
1459 	toggleCCD(!flagShowCCD);
1460 }
1461 
toggleCrosshairs(bool show)1462 void Oculars::toggleCrosshairs(bool show)
1463 {
1464 	if(show != flagShowCrosshairs)
1465 	{
1466 		flagShowCrosshairs = show;
1467 		emit enableCrosshairsChanged(show);
1468 	}
1469 }
1470 
toggleTelrad(bool show)1471 void Oculars::toggleTelrad(bool show)
1472 {
1473 	if(show!=flagShowTelrad)
1474 	{
1475 		flagShowTelrad = show;
1476 
1477 		StelMovementMgr* movementMgr = StelApp::getInstance().getCore()->getMovementMgr();
1478 		if (show)
1479 		{
1480 			hideUsageMessageIfDisplayed();
1481 			enableOcular(false);
1482 			toggleCCD(false);
1483 			// NOTE: Added special zoom level for Telrad
1484 			if (flagScalingFOVForTelrad)
1485 			{
1486 				float fov = qMax(qMax(telradFOV[0], telradFOV[1]), qMax(telradFOV[2], telradFOV[3]));
1487 				movementMgr->zoomTo(static_cast<double>(fov)*2.);
1488 			}
1489 		}
1490 		else if (getFlagInitFovUsage()) // Restoration of FOV is needed?
1491 			movementMgr->zoomTo(movementMgr->getInitFov());
1492 
1493 		if (getFlagInitDirectionUsage())
1494 			movementMgr->setViewDirectionJ2000(StelApp::getInstance().getCore()->altAzToJ2000(movementMgr->getInitViewingDirection(), StelCore::RefractionOff));
1495 
1496 		emit enableTelradChanged(flagShowTelrad);
1497 	}
1498 }
1499 
toggleTelrad()1500 void Oculars::toggleTelrad()
1501 {
1502 	toggleTelrad(!flagShowTelrad);
1503 }
1504 
1505 /* ****************************************************************************************************************** */
1506 #if 0
1507 #pragma mark -
1508 #pragma mark Private Methods
1509 #endif
1510 /* ****************************************************************************************************************** */
initializeActivationActions()1511 void Oculars::initializeActivationActions()
1512 {
1513 	QString ocularsGroup = N_("Oculars");
1514 	actionShowOcular = addAction("actionShow_Oculars", ocularsGroup, N_("Ocular view"), "enableOcular", "Ctrl+O");
1515 	actionMenu = addAction("actionShow_Ocular_Menu", ocularsGroup, N_("Oculars popup menu"), "displayPopupMenu()", "Alt+O");
1516 	actionShowCrosshairs = addAction("actionShow_Ocular_Crosshairs", ocularsGroup, N_("Show crosshairs"), "enableCrosshairs", "Alt+C");
1517 	actionShowSensor = addAction("actionShow_Sensor", ocularsGroup, N_("Image sensor frame"), "enableCCD");
1518 	actionShowTelrad = addAction("actionShow_Telrad", ocularsGroup, N_("Telrad sight"), "enableTelrad", "Ctrl+B");
1519 	actionConfiguration = addAction("actionShow_Oculars_dialog", ocularsGroup, N_("Show settings dialog"), ocularDialog, "visible", ""); // Allow assign shortkey
1520 	addAction("actionShow_Oculars_GUI", ocularsGroup, N_("Toggle Oculars button bar"), "flagGuiPanelEnabled"); // Allow assign shortkey
1521 	// Select next telescope via keyboard
1522 	addAction("actionShow_Telescope_Increment", ocularsGroup, N_("Select next telescope"), "incrementTelescopeIndex()");
1523 	// Select previous telescope via keyboard
1524 	addAction("actionShow_Telescope_Decrement", ocularsGroup, N_("Select previous telescope"), "decrementTelescopeIndex()");
1525 	// Select next eyepiece via keyboard
1526 	addAction("actionShow_Ocular_Increment", ocularsGroup, N_("Select next eyepiece"), "incrementOcularIndex()");
1527 	// Select previous eyepiece via keyboard
1528 	addAction("actionShow_Ocular_Decrement", ocularsGroup, N_("Select previous eyepiece"), "decrementOcularIndex()");
1529 	addAction("actionShow_Ocular_Rotate_Reticle_Clockwise", ocularsGroup, N_("Rotate reticle pattern of the eyepiece clockwise"), "rotateReticleClockwise()", "Alt+M");
1530 	addAction("actionShow_Ocular_Rotate_Reticle_Counterclockwise", ocularsGroup, N_("Rotate reticle pattern of the eyepiece counterclockwise"), "rotateReticleCounterclockwise()", "Shift+Alt+M");
1531 	addAction("actionShow_Sensor_Crop_Overlay", ocularsGroup, N_("Toggle sensor crop overlay"), "toggleCropOverlay()");
1532 	addAction("actionShow_Sensor_Pixel_Grid", ocularsGroup, N_("Toggle sensor pixel grid"), "togglePixelGrid()");
1533 	addAction("actionShow_Sensor_Focuser_Overlay", ocularsGroup, N_("Toggle focuser overlay"), "toggleFocuserOverlay()");
1534 
1535 	connect(this, SIGNAL(selectedCCDChanged(int)),       this, SLOT(instrumentChanged()));
1536 	connect(this, SIGNAL(selectedOcularChanged(int)),    this, SLOT(instrumentChanged()));
1537 	connect(this, SIGNAL(selectedTelescopeChanged(int)), this, SLOT(instrumentChanged()));
1538 	connect(this, SIGNAL(selectedLensChanged(int)),      this, SLOT(instrumentChanged()));
1539 }
1540 
isBinocularDefined()1541 bool Oculars::isBinocularDefined()
1542 {
1543 	bool binocularFound = false;
1544 	for (auto* ocular : oculars)
1545 	{
1546 		if (ocular->isBinoculars())
1547 		{
1548 			binocularFound = true;
1549 			break;
1550 		}
1551 	}
1552 	return binocularFound;
1553 }
1554 
paintCCDBounds()1555 void Oculars::paintCCDBounds()
1556 {
1557 	int fontSize = StelApp::getInstance().getScreenFontSize();
1558 	StelCore *core = StelApp::getInstance().getCore();
1559 	StelProjector::StelProjectorParams params = core->getCurrentStelProjectorParams();
1560 	Lens *lens = selectedLensIndex >=0  ? lenses[selectedLensIndex] : Q_NULLPTR;
1561 
1562 	const StelProjectorP projector = core->getProjection(StelCore::FrameEquinoxEqu);
1563 	double screenFOV = static_cast<double>(params.fov);
1564 	Vec2i centerScreen(projector->getViewportPosX() + projector->getViewportWidth() / 2,
1565 			   projector->getViewportPosY() + projector->getViewportHeight() / 2);
1566 
1567 	// draw sensor rectangle
1568 	if (selectedCCDIndex > -1 && selectedTelescopeIndex > -1)
1569 	{
1570 		CCD *ccd = ccds[selectedCCDIndex];
1571 		if (ccd)
1572 		{
1573 			StelPainter painter(projector);
1574 			painter.setColor(lineColor);
1575 			painter.setFont(font);
1576 			Telescope *telescope = telescopes[selectedTelescopeIndex];
1577 
1578 			const double ccdXRatio = ccd->getActualFOVx(telescope, lens) / screenFOV;
1579 			const double ccdYRatio = ccd->getActualFOVy(telescope, lens) / screenFOV;
1580 
1581 			const double fovX = ccd->getActualFOVx(telescope, lens);
1582 			const double fovY = ccd->getActualFOVy(telescope, lens);
1583 
1584 			// As the FOV is based on the narrow aspect of the screen, we need to calculate
1585 			// height & width based soley off of that dimension.
1586 			int aspectIndex = 2;
1587 			if (params.viewportXywh[2] > params.viewportXywh[3])
1588 			{
1589 				aspectIndex = 3;
1590 			}
1591 			const float width = params.viewportXywh[aspectIndex] * static_cast<float>(ccdXRatio * params.devicePixelsPerPixel);
1592 			const float height = params.viewportXywh[aspectIndex] * static_cast<float>(ccdYRatio * params.devicePixelsPerPixel);
1593 
1594 			// Get Crop size taking into account the binning rounded to the lower limit and limiting it to sensor size
1595 			const float actualCropOverlayX = (std::min(ccd->resolutionX(), ccdCropOverlayHSize) / ccd->binningX()) * ccd->binningX();
1596 			const float actualCropOverlayY = (std::min(ccd->resolutionY(), ccdCropOverlayVSize)  / ccd->binningY()) * ccd->binningY();
1597 			// Calculate the size of the CCD crop overlay
1598 			const float overlayWidth = width * actualCropOverlayX / ccd->resolutionX();
1599 			const float overlayHeight = height * actualCropOverlayY / ccd->resolutionY();
1600 
1601 			//calculate the size of a pixel in the image
1602 			float pixelProjectedWidth = width /ccd->resolutionX() * ccd->binningX();
1603 			float pixelProjectedHeight = height /ccd->resolutionY()* ccd->binningY();
1604 
1605 			double polarAngle = 0;
1606 			// if the telescope is Equatorial derotate the field
1607 			if (telescope->isEquatorial())
1608 			{
1609 				Vec3d CPos;
1610 				Vector2<qreal> cpos = projector->getViewportCenter();
1611 				projector->unProject(cpos[0], cpos[1], CPos);
1612 				Vec3d CPrel(CPos);
1613 				CPrel[2]*=0.2;
1614 				Vec3d crel;
1615 				projector->project(CPrel, crel);
1616 				polarAngle = atan2(cpos[1] - crel[1], cpos[0] - crel[0]) * (-180.0)/M_PI; // convert to degrees
1617 				if (CPos[2] > 0) polarAngle += 90.0;
1618 				else polarAngle -= 90.0;
1619 			}
1620 
1621 			if (getFlagAutosetMountForCCD())
1622 			{
1623 				StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
1624 				propMgr->setStelPropertyValue("actionSwitch_Equatorial_Mount", telescope->isEquatorial());
1625 				polarAngle = 0;
1626 			}
1627 
1628 			if (width > 0.0f && height > 0.0f)
1629 			{
1630 				QPoint a, b;
1631 				QTransform transform = QTransform().translate(centerScreen[0], centerScreen[1]).rotate(-(ccd->chipRotAngle() + polarAngle));
1632 				// bottom line
1633 				a = transform.map(QPoint(static_cast<int>(-width*0.5f), static_cast<int>(-height*0.5f)));
1634 				b = transform.map(QPoint(static_cast<int>(width*0.5f), static_cast<int>(-height*0.5f)));
1635 				painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1636 				// top line
1637 				a = transform.map(QPoint(static_cast<int>(-width*0.5f), static_cast<int>(height*0.5f)));
1638 				b = transform.map(QPoint(static_cast<int>(width*0.5f), static_cast<int>(height*0.5f)));
1639 				painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1640 				// left line
1641 				a = transform.map(QPoint(static_cast<int>(-width*0.5f), static_cast<int>(-height*0.5f)));
1642 				b = transform.map(QPoint(static_cast<int>(-width*0.5f), static_cast<int>(height*0.5f)));
1643 				painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1644 				// right line
1645 				a = transform.map(QPoint(static_cast<int>(width*0.5f), static_cast<int>(height*0.50f)));
1646 				b = transform.map(QPoint(static_cast<int>(width*0.5f), static_cast<int>(-height*0.5f)));
1647 				painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1648 
1649 				// Tool for showing a resolution box overlay
1650 				if (flagShowCcdCropOverlay)
1651 				{
1652 					// bottom line
1653 					a = transform.map(QPoint(static_cast<int>(-overlayWidth*0.5f), static_cast<int>(-overlayHeight*0.5f)));
1654 					b = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f), static_cast<int>(-overlayHeight*0.5f)));
1655 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1656 					// top line
1657 					a = transform.map(QPoint(static_cast<int>(-overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f)));
1658 					b = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f)));
1659 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1660 					// left line
1661 					a = transform.map(QPoint(static_cast<int>(-overlayWidth*0.5f), static_cast<int>(-overlayHeight*0.5f)));
1662 					b = transform.map(QPoint(static_cast<int>(-overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f)));
1663 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1664 					// right line
1665 					a = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f)));
1666 					b = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f), static_cast<int>(-overlayHeight*0.5f)));
1667 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1668 
1669 					// Tool to show full CCD grid overlay
1670 					if (flagShowCcdCropOverlayPixelGrid)
1671 					{
1672 						// vertical lines
1673 						for (int l =1 ; l< actualCropOverlayX/ccd->binningX(); l++ )
1674 						{
1675 							a = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f- l*pixelProjectedWidth), static_cast<int>(-overlayHeight*0.5f)));
1676 							b = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f- l*pixelProjectedWidth), static_cast<int>(overlayHeight*0.5f)));
1677 							painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1678 						}
1679 						// horizontal lines
1680 						for (int l =1 ; l< actualCropOverlayY/ccd->binningY(); l++ )
1681 						{
1682 							a = transform.map(QPoint(static_cast<int>(-overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f - l*pixelProjectedHeight)));
1683 							b = transform.map(QPoint(static_cast<int>(overlayWidth*0.5f), static_cast<int>(overlayHeight*0.5f - l*pixelProjectedHeight)));
1684 							painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1685 						}
1686 					}
1687 				}
1688 				if(ccd->hasOAG())
1689 				{
1690 					const double InnerOAGRatio = ccd->getInnerOAGRadius(telescope, lens) / screenFOV;
1691 					const double OuterOAGRatio = ccd->getOuterOAGRadius(telescope, lens) / screenFOV;
1692 					const double prismXRatio = ccd->getOAGActualFOVx(telescope, lens) / screenFOV;
1693 					const int in_oag_r = qRound(params.viewportXywh[aspectIndex] * InnerOAGRatio * params.devicePixelsPerPixel);
1694 					const int out_oag_r = qRound(params.viewportXywh[aspectIndex] * OuterOAGRatio * params.devicePixelsPerPixel);
1695 					const int h_width = qRound(params.viewportXywh[aspectIndex] * prismXRatio * params.devicePixelsPerPixel * 0.5);
1696 
1697 					painter.drawCircle(centerScreen[0], centerScreen[1], in_oag_r);
1698 					painter.drawCircle(centerScreen[0], centerScreen[1], out_oag_r);
1699 
1700 					QTransform oag_transform = QTransform().translate(centerScreen[0], centerScreen[1]).rotate(-(ccd->chipRotAngle() + polarAngle + ccd->prismPosAngle()));
1701 
1702 					// bottom line
1703 					a = oag_transform.map(QPoint(-h_width, in_oag_r));
1704 					b = oag_transform.map(QPoint(h_width, in_oag_r));
1705 					painter.drawLine2d(a.x(),a.y(), b.x(), b.y());
1706 					// top line
1707 					a = oag_transform.map(QPoint(-h_width, out_oag_r));
1708 					b = oag_transform.map(QPoint(h_width, out_oag_r));
1709 					painter.drawLine2d(a.x(),a.y(), b.x(), b.y());
1710 					// left line
1711 					a = oag_transform.map(QPoint(-h_width, out_oag_r));
1712 					b = oag_transform.map(QPoint(-h_width, in_oag_r));
1713 					painter.drawLine2d(a.x(),a.y(), b.x(), b.y());
1714 					// right line
1715 					a = oag_transform.map(QPoint(h_width, out_oag_r));
1716 					b = oag_transform.map(QPoint(h_width, in_oag_r));
1717 					painter.drawLine2d(a.x(),a.y(), b.x(), b.y());
1718 				}
1719 
1720 				// Tool for planning a mosaic astrophotography: shows a small cross at center of CCD's
1721 				// frame and equatorial coordinates for epoch J2000.0 of that center.
1722 				// Details: https://bugs.launchpad.net/stellarium/+bug/1404695
1723 
1724 				const double ratioLimit = 0.25;
1725 				const double ratioLimitCrop = 0.75;
1726 				if (ccdXRatio>=ratioLimit || ccdYRatio>=ratioLimit)
1727 				{
1728 					// draw cross at center
1729 					const int cross = qRound(10 * params.devicePixelsPerPixel); // use permanent size of cross (10px)
1730 					a = transform.map(QPoint(-cross, -cross));
1731 					b = transform.map(QPoint(cross, cross));
1732 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1733 					a = transform.map(QPoint(-cross, cross));
1734 					b = transform.map(QPoint(cross, -cross));
1735 					painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1736 					// calculate coordinates of the center and show it
1737 					Vec3d centerPosition;
1738 					projector->unProject(centerScreen[0], centerScreen[1], centerPosition);
1739 					double cx, cy;
1740 					QString cxt, cyt;
1741 					StelUtils::rectToSphe(&cx,&cy,core->equinoxEquToJ2000(centerPosition, StelCore::RefractionOff)); // Calculate RA/DE (J2000.0) and show it...
1742 					bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1743 					if (withDecimalDegree)
1744 					{
1745 						cxt = StelUtils::radToDecDegStr(cx, 5, false, true);
1746 						cyt = StelUtils::radToDecDegStr(cy);
1747 					}
1748 					else
1749 					{
1750 						cxt = StelUtils::radToHmsStr(cx, true);
1751 						cyt = StelUtils::radToDmsStr(cy, true);
1752 					}
1753 					float scaleFactor = static_cast<float>(1.2 * params.devicePixelsPerPixel);
1754 					// Coordinates of center of visible field of view for CCD (red rectangle)
1755 					QString coords = QString("%1:").arg(qc_("RA/Dec (J2000.0) of cross", "abbreviated in the plugin"));
1756 					a = transform.map(QPoint(qRound(-width*0.5f), qRound(height*0.5f + 5.f + fontSize*scaleFactor)));
1757 					painter.drawText(a.x(), a.y(), coords, static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1758 					coords = QString("%1/%2").arg(cxt.simplified()).arg(cyt);
1759 					a = transform.map(QPoint(qRound(-width*0.5f), qRound(height*0.5f + 5.f)));
1760 					painter.drawText(a.x(), a.y(), coords, static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1761 					// Dimensions of visible field of view for CCD (red rectangle)
1762 					a = transform.map(QPoint(qRound(-width*0.5f), qRound(-height*0.5f - fontSize*scaleFactor)));
1763 					painter.drawText(a.x(), a.y(), getDimensionsString(fovX, fovY), static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1764 					// Horizontal and vertical scales of visible field of view for CCD (red rectangle)
1765 					//TRANSLATORS: Unit of measure for scale - arc-seconds per pixel
1766 					QString unit = q_("\"/px");
1767 					QString scales = QString("%1%3 %4 %2%3")
1768 							.arg(QString::number(fovX*3600*ccd->binningX()/ccd->resolutionX(), 'f', 4))
1769 							.arg(QString::number(fovY*3600*ccd->binningY()/ccd->resolutionY(), 'f', 4))
1770 							.arg(unit)
1771 							.arg(QChar(0x00D7));
1772 					a = transform.map(QPoint(qRound(width*0.5f - painter.getFontMetrics().boundingRect(scales).width()*params.devicePixelsPerPixel), qRound(-height*0.5f - fontSize*scaleFactor)));
1773 					painter.drawText(a.x(), a.y(), scales, static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1774 					// Rotation angle of visible field of view for CCD (red rectangle)
1775 					QString angle = QString("%1%2").arg(QString::number(ccd->chipRotAngle(), 'f', 1)).arg(QChar(0x00B0));
1776 					a = transform.map(QPoint(qRound(width*0.5f - painter.getFontMetrics().boundingRect(angle).width()*params.devicePixelsPerPixel), qRound(height*0.5f + 5.f)));
1777 					painter.drawText(a.x(), a.y(), angle, static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1778 
1779 					if(flagShowCcdCropOverlay && (ccdXRatio>=ratioLimitCrop || ccdYRatio>=ratioLimitCrop))
1780 					{
1781 						// show the CCD crop overlay text
1782 						QString resolutionOverlayText = QString("%1%3 %4 %2%3")
1783 								.arg(QString::number(actualCropOverlayX, 'd', 0))
1784 								.arg(QString::number(actualCropOverlayY, 'd', 0))
1785 								.arg(qc_("px", "pixel"))
1786 								.arg(QChar(0x00D7));
1787 						if(actualCropOverlayX!=ccdCropOverlayHSize || actualCropOverlayY!=ccdCropOverlayVSize)
1788 							resolutionOverlayText.append(" [*]");
1789 						a = transform.map(QPoint(qRound(overlayWidth*0.5f - painter.getFontMetrics().boundingRect(resolutionOverlayText).width()*params.devicePixelsPerPixel), qRound(-overlayHeight*0.5f - fontSize*scaleFactor)));
1790 						painter.drawText(a.x(), a.y(), resolutionOverlayText, static_cast<float>(-(ccd->chipRotAngle() + polarAngle)));
1791 					}
1792 				}
1793 
1794 				if (getFlagShowFocuserOverlay())
1795 				{
1796 					painter.setColor(focuserColor);
1797 					if (getFlagUseSmallFocuserOverlay())
1798 						painter.drawCircle(centerScreen[0], centerScreen[1], qRound(params.viewportXywh[aspectIndex] * (0.5*ccd->getFocuserFOV(telescope, lens, 1.25)/ screenFOV) * params.devicePixelsPerPixel));
1799 					if (getFlagUseMediumFocuserOverlay())
1800 						painter.drawCircle(centerScreen[0], centerScreen[1], qRound(params.viewportXywh[aspectIndex] * (0.5*ccd->getFocuserFOV(telescope, lens, 2.)/ screenFOV) * params.devicePixelsPerPixel));
1801 					if (getFlagUseLargeFocuserOverlay())
1802 						painter.drawCircle(centerScreen[0], centerScreen[1], qRound(params.viewportXywh[aspectIndex] * (0.5*ccd->getFocuserFOV(telescope, lens, 3.3)/ screenFOV) * params.devicePixelsPerPixel));
1803 				}
1804 			}
1805 		}
1806 	}
1807 }
1808 
paintCrosshairs()1809 void Oculars::paintCrosshairs()
1810 {
1811 	StelCore *core = StelApp::getInstance().getCore();
1812 	const StelProjectorP projector = core->getProjection(StelCore::FrameEquinoxEqu);
1813 	StelProjector::StelProjectorParams params = core->getCurrentStelProjectorParams();
1814 	// Center of screen
1815 	Vec2i centerScreen(projector->getViewportPosX()+projector->getViewportWidth()/2,
1816 			   projector->getViewportPosY()+projector->getViewportHeight()/2);
1817 	float length = 0.5f * static_cast<float>(params.viewportFovDiameter);
1818 	// See if we need to scale the length
1819 	if (flagScaleImageCircle && oculars[selectedOcularIndex]->apparentFOV() > 0.0 && !oculars[selectedOcularIndex]->isBinoculars())
1820 	{
1821 		length *= static_cast<float>(oculars[selectedOcularIndex]->apparentFOV() / maxEyepieceAngle);
1822 	}
1823 	length *= static_cast<float>(params.devicePixelsPerPixel);
1824 	double polarAngle = 0.;
1825 	if (getFlagAlignCrosshair())
1826 	{
1827 		Vec3d CPos;
1828 		Vector2<qreal> cpos = projector->getViewportCenter();
1829 		projector->unProject(cpos[0], cpos[1], CPos);
1830 		Vec3d CPrel(CPos);
1831 		CPrel[2]*=0.2;
1832 		Vec3d crel;
1833 		projector->project(CPrel, crel);
1834 		polarAngle = atan2(cpos[1] - crel[1], cpos[0] - crel[0]) * (-180.0)/M_PI; // convert to degrees
1835 		if (CPos[2] > 0) polarAngle += 90.0;
1836 		else polarAngle -= 90.0;
1837 	}
1838 	// Draw the lines
1839 	StelPainter painter(projector);
1840 	painter.setColor(lineColor);
1841 	QPoint a, b;
1842 	int hw = qRound(length);
1843 	QTransform ch_transform = QTransform().translate(centerScreen[0], centerScreen[1]).rotate(-polarAngle);
1844 	a = ch_transform.map(QPoint(0, -hw));
1845 	b = ch_transform.map(QPoint(0, hw));
1846 	painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1847 	a = ch_transform.map(QPoint(-hw, 0));
1848 	b = ch_transform.map(QPoint(hw, 0));
1849 	painter.drawLine2d(a.x(), a.y(), b.x(), b.y());
1850 }
1851 
paintTelrad()1852 void Oculars::paintTelrad()
1853 {
1854 	if (!flagShowOculars)
1855 	{
1856 		StelCore *core = StelApp::getInstance().getCore();
1857 		const StelProjectorP projector = core->getProjection(StelCore::FrameEquinoxEqu);
1858 		// StelPainter drawing
1859 		StelPainter painter(projector);
1860 		painter.setColor(lineColor);
1861 		Vec2i centerScreen(projector->getViewportPosX()+projector->getViewportWidth()/2,
1862 				   projector->getViewportPosY()+projector->getViewportHeight()/2);
1863 		const float pixelsPerRad = projector->getPixelPerRadAtCenter(); // * params.devicePixelsPerPixel;
1864 		if (telradFOV[0]>0.f) painter.drawCircle(centerScreen[0], centerScreen[1], 0.5f * pixelsPerRad * static_cast<float>(M_PI/180) * (telradFOV[0]));
1865 		if (telradFOV[1]>0.f) painter.drawCircle(centerScreen[0], centerScreen[1], 0.5f * pixelsPerRad * static_cast<float>(M_PI/180) * (telradFOV[1]));
1866 		if (telradFOV[2]>0.f) painter.drawCircle(centerScreen[0], centerScreen[1], 0.5f * pixelsPerRad * static_cast<float>(M_PI/180) * (telradFOV[2]));
1867 		if (telradFOV[3]>0.f) painter.drawCircle(centerScreen[0], centerScreen[1], 0.5f * pixelsPerRad * static_cast<float>(M_PI/180) * (telradFOV[3]));
1868 	}
1869 }
1870 
paintOcularMask(const StelCore * core)1871 void Oculars::paintOcularMask(const StelCore *core)
1872 {
1873 	if (oculars[selectedOcularIndex]->hasPermanentCrosshair())
1874 		paintCrosshairs();
1875 
1876 	const StelProjectorP prj = core->getProjection(StelCore::FrameAltAz);
1877 	StelPainter painter(prj);
1878 	StelProjector::StelProjectorParams params = core->getCurrentStelProjectorParams();
1879 
1880 	double inner = 0.5 * params.viewportFovDiameter * params.devicePixelsPerPixel;
1881 	// See if we need to scale the mask
1882 	if (flagScaleImageCircle && oculars[selectedOcularIndex]->apparentFOV() > 0.0 && !oculars[selectedOcularIndex]->isBinoculars())
1883 	{
1884 		inner = oculars[selectedOcularIndex]->apparentFOV() * inner / maxEyepieceAngle;
1885 	}
1886 	Vec2i centerScreen(prj->getViewportPosX()+prj->getViewportWidth()/2, prj->getViewportPosY()+prj->getViewportHeight()/2);
1887 
1888 	painter.setBlending(true);
1889 	// Paint the reticale, if needed
1890 	if (!reticleTexture.isNull())
1891 	{
1892 		painter.setColor(lineColor);
1893 		reticleTexture->bind();
1894 		/* Why it need?
1895 		int textureHeight;
1896 		int textureWidth;
1897 		reticleTexture->getDimensions(textureWidth, textureHeight);
1898 		*/
1899 		painter.drawSprite2dMode(centerScreen[0], centerScreen[1], static_cast<float>(inner / params.devicePixelsPerPixel), static_cast<float>(reticleRotation));
1900 	}
1901 
1902 	const float alpha = getFlagUseSemiTransparency() ? getTransparencyMask()*0.01f : 1.f;
1903 	painter.setColor(0.f,0.f,0.f,alpha);
1904 
1905 	GLfloat outerRadius = static_cast<GLfloat>(params.viewportXywh[2] * params.devicePixelsPerPixel + params.viewportXywh[3] * params.devicePixelsPerPixel);
1906 	GLint slices = 239;
1907 
1908 	GLfloat sinCache[240];
1909 	GLfloat cosCache[240];
1910 	GLfloat vertices[(240+1)*2][3];
1911 	GLfloat deltaRadius;
1912 	GLfloat radiusHigh;
1913 
1914 	/* Compute length (needed for normal calculations) */
1915 	deltaRadius=outerRadius-static_cast<GLfloat>(inner);
1916 
1917 	/* Cache is the vertex locations cache */
1918 	for (int i=0; i<=slices; i++)
1919 	{
1920 		GLfloat angle=static_cast<GLfloat>(M_PI*2.0)*i/slices;
1921 		sinCache[i]=static_cast<GLfloat>(sinf(angle));
1922 		cosCache[i]=static_cast<GLfloat>(cosf(angle));
1923 	}
1924 
1925 	sinCache[slices]=sinCache[0];
1926 	cosCache[slices]=cosCache[0];
1927 
1928 	/* Enable arrays */
1929 	painter.enableClientStates(true);
1930 	painter.setVertexPointer(3, GL_FLOAT, vertices);
1931 
1932 	radiusHigh=outerRadius-deltaRadius;
1933 	for (int i=0; i<=slices; i++)
1934 	{
1935 		vertices[i*2][0]= centerScreen[0] + outerRadius*sinCache[i];
1936 		vertices[i*2][1]= centerScreen[1] + outerRadius*cosCache[i];
1937 		vertices[i*2][2] = 0.0;
1938 		vertices[i*2+1][0]= centerScreen[0] + radiusHigh*sinCache[i];
1939 		vertices[i*2+1][1]= centerScreen[1] + radiusHigh*cosCache[i];
1940 		vertices[i*2+1][2] = 0.0;
1941 	}
1942 	painter.drawFromArray(StelPainter::TriangleStrip, (slices+1)*2, 0, false);
1943 	painter.enableClientStates(false);
1944 
1945 	if (getFlagShowContour())
1946 	{
1947 		painter.setColor(lineColor);
1948 		painter.drawCircle(centerScreen[0], centerScreen[1], static_cast<float>(inner));
1949 	}
1950 
1951 	if (getFlagShowCardinals())
1952 	{
1953 		// Compute polar angle for cardinals and show it
1954 		const StelProjectorP projector = core->getProjection(StelCore::FrameEquinoxEqu);
1955 		Vec3d CPos;
1956 		Vector2<qreal> cpos = projector->getViewportCenter();
1957 		projector->unProject(cpos[0], cpos[1], CPos);
1958 		Vec3d CPrel(CPos);
1959 		CPrel[2]*=0.2;
1960 		Vec3d crel;
1961 		projector->project(CPrel, crel);
1962 		double polarAngle = atan2(cpos[1] - crel[1], cpos[0] - crel[0]) * (-180.0)/M_PI; // convert to degrees
1963 		if (CPos[2] > 0)
1964 			polarAngle += 90.0;
1965 		else
1966 			polarAngle -= 90.0;
1967 
1968 		painter.setColor(lineColor);
1969 		bool flipH = core->getFlipHorz();
1970 		bool flipV = core->getFlipVert();
1971 		if (flipH && flipV)
1972 			protractorFlipHVTexture->bind();
1973 		else if (flipH && !flipV)
1974 			protractorFlipHTexture->bind();
1975 		else if (!flipH && flipV)
1976 			protractorFlipVTexture->bind();
1977 		else
1978 			protractorTexture->bind();
1979 		painter.drawSprite2dMode(centerScreen[0], centerScreen[1], static_cast<float>(inner / params.devicePixelsPerPixel), static_cast<float>(-polarAngle));
1980 	}
1981 }
1982 
paintText(const StelCore * core)1983 void Oculars::paintText(const StelCore* core)
1984 {
1985 	const StelProjectorP prj = core->getProjection(StelCore::FrameAltAz);
1986 	StelPainter painter(prj);
1987 
1988 	// Get the current instruments
1989 	CCD *ccd = Q_NULLPTR;
1990 	if(selectedCCDIndex != -1)
1991 	{
1992 		ccd = ccds[selectedCCDIndex];
1993 	}
1994 	Ocular *ocular = Q_NULLPTR;
1995 	if(selectedOcularIndex !=-1)
1996 	{
1997 		ocular = oculars[selectedOcularIndex];
1998 	}
1999 	Telescope *telescope = Q_NULLPTR;
2000 	if(selectedTelescopeIndex != -1)
2001 	{
2002 		telescope = telescopes[selectedTelescopeIndex];
2003 	}
2004 	Lens *lens = selectedLens();
2005 
2006 	// set up the color and the GL state
2007 	painter.setColor(textColor);
2008 	painter.setBlending(true);
2009 
2010 	// Get the X & Y positions, and the line height
2011 	painter.setFont(font);
2012 	QString widthString = "MMMMMMMMMMMMMMMMMMMMM";
2013 	const double insetFromRHS = painter.getFontMetrics().boundingRect(widthString).width();
2014 	StelProjector::StelProjectorParams projectorParams = core->getCurrentStelProjectorParams();
2015 	int yPositionOffset = qRound(projectorParams.viewportXywh[3]*projectorParams.viewportCenterOffset[1]);
2016 	int xPosition = qRound(projectorParams.devicePixelsPerPixel*projectorParams.viewportXywh[2] - insetFromRHS);
2017 	int yPosition = qRound(projectorParams.devicePixelsPerPixel*projectorParams.viewportXywh[3] - yPositionOffset - 20);
2018 	const int lineHeight = painter.getFontMetrics().height();
2019 
2020 	// The Ocular
2021 	if (flagShowOculars && ocular!=Q_NULLPTR)
2022 	{
2023 		QString ocularNumberLabel;
2024 		QString name = ocular->name();
2025 		QString ocularI18n = q_("Ocular");
2026 		if (ocular->isBinoculars())
2027 			ocularI18n = q_("Binocular");
2028 		if (name.isEmpty())
2029 		{
2030 			ocularNumberLabel = QString("%1 #%2").arg(ocularI18n).arg(selectedOcularIndex);
2031 		}
2032 		else
2033 		{
2034 			ocularNumberLabel = QString("%1 #%2: %3").arg(ocularI18n).arg(selectedOcularIndex).arg(name);
2035 		}
2036 		// The name of the ocular could be really long.
2037 		if (name.length() > widthString.length())
2038 		{
2039 			xPosition -= qRound(insetFromRHS*0.5);
2040 		}
2041 		painter.drawText(xPosition, yPosition, ocularNumberLabel);
2042 		yPosition-=lineHeight;
2043 
2044 		if (!ocular->isBinoculars())
2045 		{
2046 			// TRANSLATORS: FL = Focal length
2047 			QString eFocalLengthLabel = QString(q_("Ocular FL: %1 mm")).arg(QString::number(ocular->effectiveFocalLength(), 'f', 1));
2048 			painter.drawText(xPosition, yPosition, eFocalLengthLabel);
2049 			yPosition-=lineHeight;
2050 
2051 			QString ocularFov = QString::number(ocular->apparentFOV(), 'f', 2);
2052 			ocularFov.append(QChar(0x00B0));//Degree sign
2053 			// TRANSLATORS: aFOV = apparent field of view
2054 			QString ocularFOVLabel = QString(q_("Ocular aFOV: %1")).arg(ocularFov);
2055 			painter.drawText(xPosition, yPosition, ocularFOVLabel);
2056 			yPosition-=lineHeight;
2057 
2058 			QString lensNumberLabel;
2059 			// Barlow and Shapley lens
2060 			if (lens != Q_NULLPTR) // it's null if lens is not selected (lens index = -1)
2061 			{
2062 				QString lensName = lens->getName();
2063 				if (lensName.isEmpty())
2064 				{
2065 					lensNumberLabel = QString(q_("Lens #%1")).arg(selectedLensIndex);
2066 				}
2067 				else
2068 				{
2069 					lensNumberLabel = QString (q_("Lens #%1: %2")).arg(selectedLensIndex).arg(lensName);
2070 				}
2071 			}
2072 			else
2073 			{
2074 				lensNumberLabel = QString (q_("Lens: none"));
2075 			}
2076 			painter.drawText(xPosition, yPosition, lensNumberLabel);
2077 			yPosition-=lineHeight;
2078 
2079 			if (telescope!=Q_NULLPTR)
2080 			{
2081 				QString telescopeName = telescope->name();
2082 				QString telescopeString = "";
2083 
2084 				if (telescopeName.isEmpty())
2085 					telescopeString = QString("%1").arg(selectedTelescopeIndex);
2086 				else
2087 					telescopeString = QString("%1: %2").arg(selectedTelescopeIndex).arg(telescopeName);
2088 
2089 				painter.drawText(xPosition, yPosition, QString(q_("Telescope #%1")).arg(telescopeString));
2090 				yPosition-=lineHeight;
2091 
2092 				// General info
2093 				double mag = ocular->magnification(telescope, lens);
2094 				QString magString = QString::number(mag, 'f', 1);
2095 				magString.append(QChar(0x02E3)); // Was 0x00D7
2096 				magString.append(QString(" (%1D)").arg(QString::number(mag/telescope->diameter(), 'f', 2)));
2097 
2098 				painter.drawText(xPosition, yPosition, QString(q_("Magnification: %1")).arg(magString));
2099 				yPosition-=lineHeight;
2100 
2101 				if (mag>0)
2102 				{
2103 					QString exitPupil = QString::number(telescope->diameter()/mag, 'f', 2);
2104 
2105 					painter.drawText(xPosition, yPosition, QString(q_("Exit pupil: %1 mm")).arg(exitPupil));
2106 					yPosition-=lineHeight;
2107 				}
2108 
2109 				QString fovString = QString::number(ocular->actualFOV(telescope, lens), 'f', 5);
2110 				fovString.append(QChar(0x00B0));//Degree sign
2111 
2112 				painter.drawText(xPosition, yPosition, QString(q_("FOV: %1")).arg(fovString));
2113 			}
2114 		}
2115 	}
2116 
2117 	// The CCD
2118 	if (flagShowCCD && ccd!=Q_NULLPTR)
2119 	{
2120 		QString ccdSensorLabel, ccdInfoLabel, ccdBinningInfo;
2121 		QString name = "";
2122 		QString telescopeName = "";
2123 		double fovX = 0.0;
2124 		double fovY = 0.0;
2125 		if (telescope!=Q_NULLPTR)
2126 		{
2127 			fovX = ccd->getActualFOVx(telescope, lens);
2128 			fovY = ccd->getActualFOVy(telescope, lens);
2129 			name = ccd->name();
2130 			telescopeName = telescope->name();
2131 		}
2132 
2133 		ccdInfoLabel = QString(q_("Dimensions: %1")).arg(getDimensionsString(fovX, fovY));
2134 		ccdBinningInfo = QString("%1: %2 %4 %3").arg(q_("Binning")).arg(ccd->binningX()).arg(ccd->binningY()).arg(QChar(0x00D7));
2135 
2136 		if (name.isEmpty())
2137 		{
2138 			ccdSensorLabel = QString(q_("Sensor #%1")).arg(selectedCCDIndex);
2139 		}
2140 		else
2141 		{
2142 			ccdSensorLabel = QString(q_("Sensor #%1: %2"))
2143 					.arg(selectedCCDIndex)
2144 					.arg(name);
2145 		}
2146 		// The telescope
2147 		QString telescopeNumberLabel;
2148 		if (telescopeName.isEmpty())
2149 		{
2150 			telescopeNumberLabel = QString(q_("Telescope #%1"))
2151 					.arg(selectedTelescopeIndex);
2152 		}
2153 		else
2154 		{
2155 			telescopeNumberLabel = QString(q_("Telescope #%1: %2"))
2156 					.arg(selectedTelescopeIndex)
2157 					.arg(telescopeName);
2158 		}
2159 		painter.drawText(xPosition, yPosition, ccdSensorLabel);
2160 		yPosition-=lineHeight;
2161 		painter.drawText(xPosition, yPosition, ccdInfoLabel);
2162 		yPosition-=lineHeight;
2163 		painter.drawText(xPosition, yPosition, ccdBinningInfo);
2164 		yPosition-=lineHeight;
2165 		painter.drawText(xPosition, yPosition, telescopeNumberLabel);
2166 	}
2167 }
2168 
validateAndLoadIniFile()2169 void Oculars::validateAndLoadIniFile()
2170 {
2171 	// Ensure the module directory exists
2172 	StelFileMgr::makeSureDirExistsAndIsWritable(StelFileMgr::getUserDir()+"/modules/Oculars");
2173 	StelFileMgr::Flags flags = static_cast<StelFileMgr::Flags>(StelFileMgr::Directory|StelFileMgr::Writable);
2174 	QString ocularIniPath = StelFileMgr::findFile("modules/Oculars/", flags) + "ocular.ini";
2175 	if (ocularIniPath.isEmpty())
2176 		return;
2177 
2178 	// If the ini file does not already exist, create it from the resource in the QT resource
2179 	if(!QFileInfo(ocularIniPath).exists())
2180 	{
2181 		QFile src(":/ocular/default_ocular.ini");
2182 		if (!src.copy(ocularIniPath))
2183 		{
2184 			qWarning() << "Oculars::validateIniFile cannot copy default_ocular.ini resource to [non-existing] "
2185 				      + ocularIniPath;
2186 		}
2187 		else
2188 		{
2189 			qDebug() << "Oculars::validateAndLoadIniFile() copied default_ocular.ini to " << QDir::toNativeSeparators(ocularIniPath);
2190 			// The resource is read only, and the new file inherits this, so set write-able.
2191 			QFile dest(ocularIniPath);
2192 			dest.setPermissions(dest.permissions() | QFile::WriteOwner);
2193 		}
2194 	}
2195 	else
2196 	{
2197 		qDebug() << "Oculars::validateAndLoadIniFile() ocular.ini exists at: " << QDir::toNativeSeparators(ocularIniPath) << ". Checking version...";
2198 		QSettings mySettings(ocularIniPath, QSettings::IniFormat);
2199 		const float ocularsVersion = mySettings.value("oculars_version", 0.0).toFloat();
2200 		qWarning() << "Oculars::validateAndLoadIniFile() found existing ini file version " << ocularsVersion;
2201 
2202 		if (ocularsVersion < MIN_OCULARS_INI_VERSION)
2203 		{
2204 			qWarning() << "Oculars::validateAndLoadIniFile() existing ini file version " << ocularsVersion
2205 				   << " too old to use; required version is " << MIN_OCULARS_INI_VERSION << ". Copying over new one.";
2206 			// delete last "old" file, if it exists
2207 			QFile deleteFile(ocularIniPath + ".old");
2208 			deleteFile.remove();
2209 
2210 			// Rename the old one, and copy over a new one
2211 			QFile oldFile(ocularIniPath);
2212 			if (!oldFile.rename(ocularIniPath + ".old"))
2213 			{
2214 				qWarning() << "Oculars::validateAndLoadIniFile() cannot move ocular.ini resource to ocular.ini.old at path  " + QDir::toNativeSeparators(ocularIniPath);
2215 			}
2216 			else
2217 			{
2218 				qWarning() << "Oculars::validateAndLoadIniFile() ocular.ini resource renamed to ocular.ini.old at path  " + QDir::toNativeSeparators(ocularIniPath);
2219 				QFile src(":/ocular/default_ocular.ini");
2220 				if (!src.copy(ocularIniPath))
2221 				{
2222 					qWarning() << "Oculars::validateIniFile cannot copy default_ocular.ini resource to [non-existing] " + QDir::toNativeSeparators(ocularIniPath);
2223 				}
2224 				else
2225 				{
2226 					qDebug() << "Oculars::validateAndLoadIniFile() copied default_ocular.ini to " << QDir::toNativeSeparators(ocularIniPath);
2227 					// The resource is read only, and the new file inherits this...  make sure the new file
2228 					// is writable by the Stellarium process so that updates can be done.
2229 					QFile dest(ocularIniPath);
2230 					dest.setPermissions(dest.permissions() | QFile::WriteOwner);
2231 				}
2232 			}
2233 		}
2234 	}
2235 	settings = new QSettings(ocularIniPath, QSettings::IniFormat, this);
2236 }
2237 
unzoomOcular()2238 void Oculars::unzoomOcular()
2239 {
2240 	Q_ASSERT(flagShowOculars == false);
2241 	StelCore *core = StelApp::getInstance().getCore();
2242 	StelMovementMgr *movementManager = core->getMovementMgr();
2243 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
2244 
2245 	if (flagHideGridsLines)
2246 		toggleLines(true);
2247 
2248 	StelApp::getInstance().getStelPropertyManager()->setStelPropertyValue("MilkyWay.saturation", milkyWaySaturation);
2249 	disconnect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double)));
2250 	// restore values, but keep current to enable toggling.
2251 	if (!getFlagAutoLimitMagnitude())
2252 	{
2253 		flagLimitStarsOculars=skyDrawer->getFlagStarMagnitudeLimit();
2254 		magLimitStarsOculars=skyDrawer->getCustomStarMagnitudeLimit();
2255 	}
2256 	skyDrawer->setCustomStarMagnitudeLimit(magLimitStarsMain);
2257 	skyDrawer->setFlagStarMagnitudeLimit(flagLimitStarsMain);
2258 	relativeStarScaleOculars=skyDrawer->getRelativeStarScale();
2259 	absoluteStarScaleOculars=skyDrawer->getAbsoluteStarScale();
2260 	skyDrawer->setRelativeStarScale(relativeStarScaleMain);
2261 	skyDrawer->setAbsoluteStarScale(absoluteStarScaleMain);
2262 	skyDrawer->setFlagLuminanceAdaptation(flagAdaptationMain);
2263 	skyDrawer->setFlagPlanetMagnitudeLimit(flagLimitPlanetsMain);
2264 	skyDrawer->setFlagNebulaMagnitudeLimit(flagLimitDSOsMain);
2265 	skyDrawer->setCustomPlanetMagnitudeLimit(magLimitPlanetsMain);
2266 	skyDrawer->setCustomNebulaMagnitudeLimit(magLimitDSOsMain);
2267 	movementManager->setFlagEnableZoomKeys(true);
2268 	movementManager->setFlagEnableMouseZooming(true);
2269 
2270 	GETSTELMODULE(SolarSystem)->setFlagMoonScale(flagMoonScaleMain);
2271 	GETSTELMODULE(SolarSystem)->setFlagMinorBodyScale(flagMinorBodiesScaleMain);
2272 	GETSTELMODULE(SolarSystem)->setFlagSunScale(flagSunScaleMain);
2273 	GETSTELMODULE(SolarSystem)->setFlagPlanetScale(flagPlanetsScaleMain);
2274 	GETSTELMODULE(NebulaMgr)->setHintsProportional(flagDSOPropHintMain);
2275 
2276 	// Set the screen display
2277 	core->setFlipHorz(flipHorzMain);
2278 	core->setFlipVert(flipVertMain);
2279 
2280 	if (getFlagInitFovUsage())
2281 		movementManager->zoomTo(movementManager->getInitFov());
2282 	else if (!flagShowTelrad)
2283 		movementManager->zoomTo(initialFOV);
2284 
2285 	if (getFlagInitDirectionUsage())
2286 		movementManager->setViewDirectionJ2000(core->altAzToJ2000(movementManager->getInitViewingDirection(), StelCore::RefractionOff));
2287 }
2288 
zoom(bool zoomedIn)2289 void Oculars::zoom(bool zoomedIn)
2290 {
2291 	if (flagShowOculars && selectedOcularIndex == -1)
2292 	{
2293 		// The user cycled out the selected ocular
2294 		flagShowOculars = false;
2295 	}
2296 
2297 	if (flagShowOculars)
2298 	{
2299 		if (!zoomedIn)
2300 		{
2301 			StelCore *core = StelApp::getInstance().getCore();
2302 			StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
2303 
2304 			if (flagHideGridsLines)
2305 			{
2306 				// Store current state for later resetting
2307 				flagGridLinesDisplayedMain		= propMgr->getStelPropertyValue("GridLinesMgr.gridlinesDisplayed").toBool();
2308 				flagCardinalPointsMain			= propMgr->getStelPropertyValue("LandscapeMgr.cardinalsPointsDisplayed").toBool();
2309 				flagConstellationLinesMain		= propMgr->getStelPropertyValue("ConstellationMgr.linesDisplayed").toBool();
2310 				flagConstellationBoundariesMain	= propMgr->getStelPropertyValue("ConstellationMgr.boundariesDisplayed").toBool();
2311 				flagAsterismLinesMain			= propMgr->getStelPropertyValue("AsterismMgr.linesDisplayed").toBool();
2312 				flagRayHelpersLinesMain		= propMgr->getStelPropertyValue("AsterismMgr.rayHelpersDisplayed").toBool();
2313 			}
2314 
2315 			StelSkyDrawer *skyDrawer = core->getSkyDrawer();
2316 			// Current state
2317 			flagAdaptationMain	= skyDrawer->getFlagLuminanceAdaptation();
2318 			flagLimitStarsMain	= skyDrawer->getFlagStarMagnitudeLimit();
2319 			flagLimitPlanetsMain	= skyDrawer->getFlagPlanetMagnitudeLimit();
2320 			flagLimitDSOsMain	= skyDrawer->getFlagNebulaMagnitudeLimit();
2321 			magLimitStarsMain	= skyDrawer->getCustomStarMagnitudeLimit();
2322 			magLimitPlanetsMain	= skyDrawer->getCustomPlanetMagnitudeLimit();
2323 			magLimitDSOsMain	= skyDrawer->getCustomNebulaMagnitudeLimit();
2324 			relativeStarScaleMain	= skyDrawer->getRelativeStarScale();
2325 			absoluteStarScaleMain	= skyDrawer->getAbsoluteStarScale();
2326 
2327 			flagMoonScaleMain	= propMgr->getStelPropertyValue("SolarSystem.flagMoonScale").toBool();
2328 			flagMinorBodiesScaleMain = propMgr->getStelPropertyValue("SolarSystem.flagMinorBodyScale").toBool();
2329 			flagSunScaleMain	= propMgr->getStelPropertyValue("SolarSystem.flagSunScale").toBool();
2330 			flagPlanetsScaleMain	= propMgr->getStelPropertyValue("SolarSystem.flagPlanetScale").toBool();
2331 
2332 			flagDSOPropHintMain	= propMgr->getStelPropertyValue("NebulaMgr.hintsProportional").toBool();
2333 			milkyWaySaturation	= propMgr->getStelPropertyValue("MilkyWay.saturation").toDouble();
2334 
2335 			flipHorzMain = core->getFlipHorz();
2336 			flipVertMain = core->getFlipVert();
2337 
2338 			StelMovementMgr *movementManager = core->getMovementMgr();
2339 			initialFOV = movementManager->getCurrentFov();
2340 		}
2341 
2342 		// set new state
2343 		zoomOcular();
2344 	}
2345 	else
2346 	{
2347 		//reset to original state
2348 		unzoomOcular();
2349 	}
2350 }
2351 
toggleLines(bool visible)2352 void Oculars::toggleLines(bool visible)
2353 {
2354 	if (flagShowTelrad)
2355 		return;
2356 
2357 	StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
2358 
2359 	if (visible)
2360 	{
2361 		propMgr->setStelPropertyValue("GridLinesMgr.gridlinesDisplayed", flagGridLinesDisplayedMain);
2362 		propMgr->setStelPropertyValue("LandscapeMgr.cardinalsPointsDisplayed", flagCardinalPointsMain);
2363 		propMgr->setStelPropertyValue("ConstellationMgr.linesDisplayed", flagConstellationLinesMain);
2364 		propMgr->setStelPropertyValue("ConstellationMgr.boundariesDisplayed", flagConstellationBoundariesMain);
2365 		propMgr->setStelPropertyValue("AsterismMgr.linesDisplayed", flagAsterismLinesMain);
2366 		propMgr->setStelPropertyValue("AsterismMgr.rayHelpersDisplayed", flagRayHelpersLinesMain);
2367 	}
2368 	else
2369 	{
2370 		propMgr->setStelPropertyValue("GridLinesMgr.gridlinesDisplayed", false);
2371 		propMgr->setStelPropertyValue("LandscapeMgr.cardinalsPointsDisplayed", false);
2372 		propMgr->setStelPropertyValue("ConstellationMgr.linesDisplayed", false);
2373 		propMgr->setStelPropertyValue("ConstellationMgr.boundariesDisplayed", false);
2374 		propMgr->setStelPropertyValue("AsterismMgr.linesDisplayed", false);
2375 		propMgr->setStelPropertyValue("AsterismMgr.rayHelpersDisplayed", false);
2376 	}
2377 }
2378 
zoomOcular()2379 void Oculars::zoomOcular()
2380 {
2381 	Q_ASSERT(flagShowOculars == true);
2382 	StelCore *core = StelApp::getInstance().getCore();
2383 	StelMovementMgr *movementManager = core->getMovementMgr();
2384 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
2385 
2386 	if (flagHideGridsLines)
2387 		toggleLines(false);
2388 
2389 	skyDrawer->setFlagLuminanceAdaptation(false);
2390 	StelApp::getInstance().getStelPropertyManager()->setStelPropertyValue("MilkyWay.saturation", 0.f);
2391 
2392 	GETSTELMODULE(SolarSystem)->setFlagMoonScale(false);
2393 	GETSTELMODULE(SolarSystem)->setFlagMinorBodyScale(false);
2394 	GETSTELMODULE(SolarSystem)->setFlagSunScale(false);
2395 	GETSTELMODULE(SolarSystem)->setFlagPlanetScale(false);
2396 	GETSTELMODULE(NebulaMgr)->setHintsProportional(false);
2397 
2398 	movementManager->setFlagTracking(true);
2399 	movementManager->setFlagEnableZoomKeys(false);
2400 	movementManager->setFlagEnableMouseZooming(false);
2401 
2402 	// We won't always have a selected object
2403 	if (StelApp::getInstance().getStelObjectMgr().getWasSelected())
2404 	{
2405 		StelObjectP selectedObject = StelApp::getInstance().getStelObjectMgr().getSelectedObject()[0];
2406 		movementManager->moveToJ2000(selectedObject->getEquinoxEquatorialPos(core), movementManager->mountFrameToJ2000(Vec3d(0., 0., 1.)), 0.0, StelMovementMgr::ZoomIn);
2407 	}
2408 
2409 	// Set the screen display
2410 	Ocular * ocular = oculars[selectedOcularIndex];
2411 	Telescope * telescope = Q_NULLPTR;
2412 	Lens * lens = Q_NULLPTR;
2413 	// Only consider flip is we're not binoculars
2414 	if (ocular->isBinoculars())
2415 	{
2416 		core->setFlipHorz(false);
2417 		core->setFlipVert(false);
2418 	}
2419 	else
2420 	{
2421 		if (selectedLensIndex >= 0)
2422 		{
2423 			lens = lenses[selectedLensIndex];
2424 		}
2425 		telescope = telescopes[selectedTelescopeIndex];
2426 		core->setFlipHorz(telescope->isHFlipped());
2427 		core->setFlipVert(telescope->isVFlipped());
2428 	}
2429 
2430 	// Change relative and absolute scales for stars
2431 	relativeStarScaleMain=skyDrawer->getRelativeStarScale();
2432 	skyDrawer->setRelativeStarScale(relativeStarScaleOculars);
2433 	absoluteStarScaleMain=skyDrawer->getAbsoluteStarScale();
2434 	skyDrawer->setAbsoluteStarScale(absoluteStarScaleOculars);
2435 
2436 	// Limit stars and DSOs	magnitude. Either compute limiting magnitude for the telescope/ocular,
2437 	// or just use the custom oculars mode value.
2438 
2439 	// TODO: set lim. mag without also activating flag it if it should not be activated.
2440 
2441 	double limitMag=magLimitStarsOculars;
2442 	if (getFlagAutoLimitMagnitude() || flagLimitStarsOculars )
2443 	{
2444 		if (getFlagAutoLimitMagnitude())
2445 		{
2446 			disconnect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double))); // we want to keep the old manual value.
2447 			limitMag = computeLimitMagnitude(ocular, telescope);
2448 			// TODO: Is it really good to apply the star formula to DSO?
2449 			skyDrawer->setFlagNebulaMagnitudeLimit(true);
2450 			skyDrawer->setCustomNebulaMagnitudeLimit(limitMag);
2451 		}
2452 		else
2453 		{	// It's possible that the user changes the custom magnitude while viewing, and then changes the ocular.
2454 			// Therefore we need a temporary connection.
2455 			connect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double)));
2456 		}
2457 		skyDrawer->setFlagStarMagnitudeLimit(true);
2458 	}
2459 	skyDrawer->setCustomStarMagnitudeLimit(limitMag);
2460 
2461 	actualFOV = ocular->actualFOV(telescope, lens);
2462 	// See if the mask was scaled; if so, correct the actualFOV.
2463 	if (flagScaleImageCircle && ocular->apparentFOV() > 0.0 && !ocular->isBinoculars())
2464 	{
2465 		actualFOV = maxEyepieceAngle * actualFOV / ocular->apparentFOV();
2466 	}
2467 	movementManager->zoomTo(actualFOV, 0.f);
2468 }
2469 
hideUsageMessageIfDisplayed()2470 void Oculars::hideUsageMessageIfDisplayed()
2471 {
2472 	if (usageMessageLabelID > -1)
2473 	{
2474 		LabelMgr *labelManager = GETSTELMODULE(LabelMgr);
2475 		labelManager->setLabelShow(usageMessageLabelID, false);
2476 		labelManager->deleteLabel(usageMessageLabelID);
2477 		usageMessageLabelID = -1;
2478 	}
2479 }
2480 
selectedLens()2481 Lens* Oculars::selectedLens()
2482 {
2483 	if (selectedLensIndex >= 0 && selectedLensIndex < lenses.count())
2484 	{
2485 		return lenses[selectedLensIndex];
2486 	}
2487 	return Q_NULLPTR;
2488 }
2489 
addLensSubmenu(QMenu * parent)2490 QMenu* Oculars::addLensSubmenu(QMenu* parent)
2491 {
2492 	Q_ASSERT(parent);
2493 
2494 	QMenu *submenu = new QMenu(q_("&Lens"), parent);
2495 	submenu->addAction(q_("&Previous lens"), this, SLOT(decrementLensIndex()));
2496 	submenu->addAction(q_("&Next lens"), this, SLOT(incrementLensIndex()));
2497 	submenu->addSeparator();
2498 	submenu->addAction(q_("None"), this, SLOT(disableLens()));
2499 
2500 	for (int index = 0; index < lenses.count(); ++index)
2501 	{
2502 		QString label;
2503 		if (index < 10)
2504 		{
2505 			label = QString("&%1: %2").arg(index).arg(lenses[index]->getName());
2506 		}
2507 		else
2508 		{
2509 			label = lenses[index]->getName();
2510 		}
2511 		QAction* action = submenu->addAction(label, [=](){selectLensAtIndex(index);});
2512 		if (index == selectedLensIndex)
2513 		{
2514 			action->setCheckable(true);
2515 			action->setChecked(true);
2516 		}
2517 	}
2518 	return submenu;
2519 }
2520 
addTelescopeSubmenu(QMenu * parent)2521 QMenu* Oculars::addTelescopeSubmenu(QMenu *parent)
2522 {
2523 	Q_ASSERT(parent);
2524 
2525 	QMenu* submenu = new QMenu(q_("&Telescope"), parent);
2526 	submenu->addAction(q_("&Previous telescope"), this, SLOT(decrementTelescopeIndex()));
2527 	submenu->addAction(q_("&Next telescope"), this, SLOT(incrementTelescopeIndex()));
2528 	submenu->addSeparator();
2529 	for (int index = 0; index < telescopes.count(); ++index)
2530 	{
2531 		QString label;
2532 		if (index < 10)
2533 		{
2534 			label = QString("&%1: %2").arg(index).arg(telescopes[index]->name());
2535 		}
2536 		else
2537 		{
2538 			label = telescopes[index]->name();
2539 		}
2540 		QAction* action = submenu->addAction(label, [=](){selectTelescopeAtIndex(index);});
2541 		if (index == selectedTelescopeIndex)
2542 		{
2543 			action->setCheckable(true);
2544 			action->setChecked(true);
2545 		}
2546 	}
2547 
2548 	return submenu;
2549 }
2550 
setFlagDMSDegrees(const bool b)2551 void Oculars::setFlagDMSDegrees(const bool b)
2552 {
2553 	flagDMSDegrees = b;
2554 	settings->setValue("use_decimal_degrees", !b);
2555 	settings->sync();
2556 	emit flagDMSDegreesChanged(b);
2557 }
2558 
getFlagDMSDegrees() const2559 bool Oculars::getFlagDMSDegrees() const
2560 {
2561 	return flagDMSDegrees;
2562 }
2563 
setFlagRequireSelection(const bool b)2564 void Oculars::setFlagRequireSelection(const bool b)
2565 {
2566 	flagRequireSelection = b;
2567 	settings->setValue("require_selection_to_zoom", b);
2568 	settings->sync();
2569 	emit flagRequireSelectionChanged(b);
2570 }
2571 
getFlagRequireSelection() const2572 bool Oculars::getFlagRequireSelection() const
2573 {
2574 	return flagRequireSelection;
2575 }
2576 
setFlagAutoLimitMagnitude(const bool b)2577 void Oculars::setFlagAutoLimitMagnitude(const bool b)
2578 {
2579 	flagAutoLimitMagnitude = b;
2580 	settings->setValue("autolimit_stellar_magnitude", b);
2581 	settings->sync();
2582 	emit flagAutoLimitMagnitudeChanged(b);
2583 }
2584 
getFlagAutoLimitMagnitude() const2585 bool Oculars::getFlagAutoLimitMagnitude() const
2586 {
2587 	return flagAutoLimitMagnitude;
2588 }
2589 
setMagLimitStarsOcularsManual(double mag)2590 void Oculars::setMagLimitStarsOcularsManual(double mag)
2591 {
2592 	magLimitStarsOculars = mag;
2593 	settings->setValue("limit_stellar_magnitude_oculars_val", mag);
2594 	settings->sync();
2595 	// This is no property, no need to emit a signal.
2596 }
2597 
getMagLimitStarsOcularsManual() const2598 double Oculars::getMagLimitStarsOcularsManual() const
2599 {
2600 	return magLimitStarsOculars;
2601 }
2602 
setFlagInitFovUsage(const bool b)2603 void Oculars::setFlagInitFovUsage(const bool b)
2604 {
2605 	flagInitFOVUsage = b;
2606 	settings->setValue("use_initial_fov", b);
2607 	settings->sync();
2608 	emit flagInitFOVUsageChanged(b);
2609 }
2610 
getFlagInitFovUsage() const2611 bool Oculars::getFlagInitFovUsage() const
2612 {
2613 	return flagInitFOVUsage;
2614 }
2615 
setFlagInitDirectionUsage(const bool b)2616 void Oculars::setFlagInitDirectionUsage(const bool b)
2617 {
2618 	flagInitDirectionUsage = b;
2619 	settings->setValue("use_initial_direction", b);
2620 	settings->sync();
2621 	emit flagInitDirectionUsageChanged(b);
2622 }
2623 
getFlagInitDirectionUsage() const2624 bool Oculars::getFlagInitDirectionUsage() const
2625 {
2626 	return flagInitDirectionUsage;
2627 }
2628 
setFlagAutosetMountForCCD(const bool b)2629 void Oculars::setFlagAutosetMountForCCD(const bool b)
2630 {
2631 	flagAutosetMountForCCD = b;
2632 	settings->setValue("use_mount_autoset", b);
2633 	settings->sync();
2634 
2635 	if (!b)
2636 	{
2637 		StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
2638 		propMgr->setStelPropertyValue("StelMovementMgr.equatorialMount", equatorialMountEnabledMain);
2639 	}
2640 	emit flagAutosetMountForCCDChanged(b);
2641 }
2642 
getFlagAutosetMountForCCD() const2643 bool Oculars::getFlagAutosetMountForCCD() const
2644 {
2645 	return  flagAutosetMountForCCD;
2646 }
2647 
setFlagScalingFOVForTelrad(const bool b)2648 void Oculars::setFlagScalingFOVForTelrad(const bool b)
2649 {
2650 	flagScalingFOVForTelrad = b;
2651 	settings->setValue("use_telrad_fov_scaling", b);
2652 	settings->sync();
2653 	emit flagScalingFOVForTelradChanged(b);
2654 }
2655 
getFlagScalingFOVForTelrad() const2656 bool Oculars::getFlagScalingFOVForTelrad() const
2657 {
2658 	return  flagScalingFOVForTelrad;
2659 }
2660 
setTelradFOV(Vec4f fov)2661 void Oculars::setTelradFOV(Vec4f fov)
2662 {
2663 	telradFOV = fov;
2664 	settings->setValue("telrad_fov", fov.toStr());
2665 	settings->sync();
2666 	emit telradFOVChanged(fov);
2667 }
2668 
getTelradFOV() const2669 Vec4f Oculars::getTelradFOV() const
2670 {
2671 	return  telradFOV;
2672 }
2673 
setFlagScalingFOVForCCD(const bool b)2674 void Oculars::setFlagScalingFOVForCCD(const bool b)
2675 {
2676 	flagScalingFOVForCCD = b;
2677 	settings->setValue("use_ccd_fov_scaling", b);
2678 	settings->sync();
2679 	emit flagScalingFOVForCCDChanged(b);
2680 }
2681 
getFlagScalingFOVForCCD() const2682 bool Oculars::getFlagScalingFOVForCCD() const
2683 {
2684 	return  flagScalingFOVForCCD;
2685 }
2686 
setFlagUseSemiTransparency(const bool b)2687 void Oculars::setFlagUseSemiTransparency(const bool b)
2688 {
2689 	flagSemiTransparency = b;
2690 	settings->setValue("use_semi_transparency", b);
2691 	settings->sync();
2692 	emit flagUseSemiTransparencyChanged(b);
2693 }
2694 
getFlagUseSemiTransparency() const2695 bool Oculars::getFlagUseSemiTransparency() const
2696 {
2697 	return flagSemiTransparency;
2698 }
2699 
setTransparencyMask(const int v)2700 void Oculars::setTransparencyMask(const int v)
2701 {
2702 	transparencyMask = v;
2703 	settings->setValue("transparency_mask", v);
2704 	settings->sync();
2705 	emit transparencyMaskChanged(v);
2706 }
2707 
getTransparencyMask() const2708 int Oculars::getTransparencyMask() const
2709 {
2710 	return transparencyMask;
2711 }
2712 
setFlagShowResolutionCriteria(const bool b)2713 void Oculars::setFlagShowResolutionCriteria(const bool b)
2714 {
2715 	flagShowResolutionCriteria = b;
2716 	settings->setValue("show_resolution_criteria", b);
2717 	settings->sync();
2718 	emit flagShowResolutionCriteriaChanged(b);
2719 }
2720 
getFlagShowResolutionCriteria() const2721 bool Oculars::getFlagShowResolutionCriteria() const
2722 {
2723 	return flagShowResolutionCriteria;
2724 }
2725 
setCcdCropOverlayHSize(int size)2726 void Oculars::setCcdCropOverlayHSize(int size) {
2727 	ccdCropOverlayHSize = size;
2728 	settings->setValue("ccd_crop_overlay_hsize", size);
2729 	settings->sync();
2730 	emit ccdCropOverlayHSizeChanged(size);
2731 }
2732 
setCcdCropOverlayVSize(int size)2733 void Oculars::setCcdCropOverlayVSize(int size) {
2734 	ccdCropOverlayVSize = size;
2735 	settings->setValue("ccd_crop_overlay_vsize", size);
2736 	settings->sync();
2737 	emit ccdCropOverlayVSizeChanged(size);
2738 }
2739 
setFlagShowCcdCropOverlay(const bool b)2740 void Oculars::setFlagShowCcdCropOverlay(const bool b)
2741 {
2742 	flagShowCcdCropOverlay = b;
2743 	settings->setValue("show_ccd_crop_overlay", b);
2744 	settings->sync();
2745 	emit flagShowCcdCropOverlayChanged(b);
2746 }
2747 
getFlagShowCcdCropOverlay(void) const2748 bool Oculars::getFlagShowCcdCropOverlay(void) const
2749 {
2750 	return flagShowCcdCropOverlay;
2751 }
2752 
setFlagShowCcdCropOverlayPixelGrid(const bool b)2753 void Oculars::setFlagShowCcdCropOverlayPixelGrid(const bool b)
2754 {
2755 	flagShowCcdCropOverlayPixelGrid = b;
2756 	settings->setValue("ccd_crop_overlay_pixel_grid", b);
2757 	settings->sync();
2758 	emit flagShowCcdCropOverlayPixelGridChanged(b);
2759 }
2760 
getFlagShowCcdCropOverlayPixelGrid(void) const2761 bool Oculars::getFlagShowCcdCropOverlayPixelGrid(void) const
2762 {
2763 	return flagShowCcdCropOverlayPixelGrid;
2764 }
2765 
2766 
setFlagShowFocuserOverlay(const bool b)2767 void Oculars::setFlagShowFocuserOverlay(const bool b)
2768 {
2769 	flagShowFocuserOverlay = b;
2770 	settings->setValue("show_focuser_overlay", b);
2771 	settings->sync();
2772 	emit flagShowFocuserOverlayChanged(b);
2773 }
2774 
getFlagShowFocuserOverlay(void) const2775 bool Oculars::getFlagShowFocuserOverlay(void) const
2776 {
2777 	return flagShowFocuserOverlay;
2778 }
2779 
setFlagUseSmallFocuserOverlay(const bool b)2780 void Oculars::setFlagUseSmallFocuserOverlay(const bool b)
2781 {
2782 	flagUseSmallFocuserOverlay = b;
2783 	settings->setValue("use_small_focuser_overlay", b);
2784 	settings->sync();
2785 	emit flagUseSmallFocuserOverlayChanged(b);
2786 }
2787 
getFlagUseSmallFocuserOverlay(void) const2788 bool Oculars::getFlagUseSmallFocuserOverlay(void) const
2789 {
2790 	return flagUseSmallFocuserOverlay;
2791 }
2792 
setFlagUseMediumFocuserOverlay(const bool b)2793 void Oculars::setFlagUseMediumFocuserOverlay(const bool b)
2794 {
2795 	flagUseMediumFocuserOverlay = b;
2796 	settings->setValue("use_medium_focuser_overlay", b);
2797 	settings->sync();
2798 	emit flagUseMediumFocuserOverlayChanged(b);
2799 }
2800 
getFlagUseMediumFocuserOverlay(void) const2801 bool Oculars::getFlagUseMediumFocuserOverlay(void) const
2802 {
2803 	return flagUseMediumFocuserOverlay;
2804 }
2805 
setFlagUseLargeFocuserOverlay(const bool b)2806 void Oculars::setFlagUseLargeFocuserOverlay(const bool b)
2807 {
2808 	flagUseLargeFocuserOverlay = b;
2809 	settings->setValue("use_large_focuser_overlay", b);
2810 	settings->sync();
2811 	emit flagUseLargeFocuserOverlayChanged(b);
2812 }
2813 
getFlagUseLargeFocuserOverlay(void) const2814 bool Oculars::getFlagUseLargeFocuserOverlay(void) const
2815 {
2816 	return flagUseLargeFocuserOverlay;
2817 }
2818 
setFlagShowContour(const bool b)2819 void Oculars::setFlagShowContour(const bool b)
2820 {
2821 	flagShowContour = b;
2822 	settings->setValue("show_ocular_contour", b);
2823 	settings->sync();
2824 	emit flagShowContourChanged(b);
2825 }
2826 
getFlagShowContour(void) const2827 bool Oculars::getFlagShowContour(void) const
2828 {
2829 	return flagShowContour;
2830 }
2831 
setFlagShowCardinals(const bool b)2832 void Oculars::setFlagShowCardinals(const bool b)
2833 {
2834 	flagShowCardinals = b;
2835 	settings->setValue("show_ocular_cardinals", b);
2836 	settings->sync();
2837 	emit flagShowCardinalsChanged(b);
2838 }
2839 
getFlagShowCardinals(void) const2840 bool Oculars::getFlagShowCardinals(void) const
2841 {
2842 	return flagShowCardinals;
2843 }
2844 
setFlagAlignCrosshair(const bool b)2845 void Oculars::setFlagAlignCrosshair(const bool b)
2846 {
2847 	flagAlignCrosshair = b;
2848 	settings->setValue("align_crosshair", b);
2849 	settings->sync();
2850 	emit flagAlignCrosshairChanged(b);
2851 }
2852 
getFlagAlignCrosshair(void) const2853 bool Oculars::getFlagAlignCrosshair(void) const
2854 {
2855 	return flagAlignCrosshair;
2856 }
2857 
setArrowButtonScale(const int val)2858 void Oculars::setArrowButtonScale(const int val)
2859 {
2860 	arrowButtonScale = val;
2861 	settings->setValue("arrow_scale", val);
2862 	settings->sync();
2863 	emit arrowButtonScaleChanged(val);
2864 }
2865 
getArrowButtonScale() const2866 int Oculars::getArrowButtonScale() const
2867 {
2868 	return arrowButtonScale;
2869 }
2870 
setFlagHideGridsLines(const bool b)2871 void Oculars::setFlagHideGridsLines(const bool b)
2872 {
2873 	if (b != flagHideGridsLines)
2874 	{
2875 		flagHideGridsLines = b;
2876 		settings->setValue("hide_grids_and_lines", b);
2877 		settings->sync();
2878 		emit flagHideGridsLinesChanged(b);
2879 
2880 		if (b && flagShowOculars)
2881 		{
2882 			// Store current state for later resetting
2883 			StelPropertyMgr* propMgr=StelApp::getInstance().getStelPropertyManager();
2884 			flagGridLinesDisplayedMain	= propMgr->getStelPropertyValue("GridLinesMgr.gridlinesDisplayed").toBool();
2885 			flagCardinalPointsMain		= propMgr->getStelPropertyValue("LandscapeMgr.cardinalsPointsDisplayed").toBool();
2886 			flagConstellationLinesMain	= propMgr->getStelPropertyValue("ConstellationMgr.linesDisplayed").toBool();
2887 			flagConstellationBoundariesMain	= propMgr->getStelPropertyValue("ConstellationMgr.boundariesDisplayed").toBool();
2888 			flagAsterismLinesMain		= propMgr->getStelPropertyValue("AsterismMgr.linesDisplayed").toBool();
2889 			flagRayHelpersLinesMain		= propMgr->getStelPropertyValue("AsterismMgr.rayHelpersDisplayed").toBool();
2890 			toggleLines(false);
2891 		}
2892 		else if (!b && flagShowOculars)
2893 		{
2894 			// Restore main program state
2895 			toggleLines(true);
2896 		}
2897 	}
2898 }
2899 
getFlagHideGridsLines() const2900 bool Oculars::getFlagHideGridsLines() const
2901 {
2902 	return flagHideGridsLines;
2903 }
2904 
getDimensionsString(double fovX,double fovY) const2905 QString Oculars::getDimensionsString(double fovX, double fovY) const
2906 {
2907 	QString stringFovX, stringFovY;
2908 	if (getFlagDMSDegrees())
2909 	{
2910 		if (fovX >= 1.0)
2911 		{
2912 			int degrees = static_cast<int>(fovX);
2913 			double minutes = (fovX - degrees) * 60.;
2914 			stringFovX = QString::number(degrees) + QChar(0x00B0) + QString::number(minutes, 'f', 2) + QChar(0x2032);
2915 		}
2916 		else
2917 		{
2918 			double minutes = fovX * 60.;
2919 			stringFovX = QString::number(minutes, 'f', 2) + QChar(0x2032);
2920 		}
2921 
2922 		if (fovY >= 1.0)
2923 		{
2924 			int degrees = static_cast<int>(fovY);
2925 			double minutes = (fovY - degrees) * 60.;
2926 			stringFovY = QString::number(degrees) + QChar(0x00B0) + QString::number(minutes, 'f', 2) + QChar(0x2032);
2927 		}
2928 		else
2929 		{
2930 			double minutes = fovY * 60;
2931 			stringFovY = QString::number(minutes, 'f', 2) + QChar(0x2032);
2932 		}
2933 	}
2934 	else
2935 	{
2936 		stringFovX = QString::number(fovX, 'f', 5) + QChar(0x00B0);
2937 		stringFovY = QString::number(fovY, 'f', 5) + QChar(0x00B0);
2938 	}
2939 
2940 	return stringFovX + QChar(0x00D7) + stringFovY;
2941 }
2942 
2943 // Define whether the button toggling eyepieces should be visible
setFlagShowOcularsButton(bool b)2944 void Oculars::setFlagShowOcularsButton(bool b)
2945 {
2946 	StelGui* gui = dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
2947 	if (gui)
2948 	{
2949 		if (b==true) {
2950 			if (toolbarButton==Q_NULLPTR) {
2951 				// Create the oculars button
2952 				toolbarButton = new StelButton(Q_NULLPTR, *pxmapOnIcon, *pxmapOffIcon, *pxmapGlow, "actionShow_Oculars", false, "actionShow_Oculars_dialog");
2953 			}
2954 			gui->getButtonBar()->addButton(toolbarButton, "065-pluginsGroup");
2955 		} else {
2956 			gui->getButtonBar()->hideButton("actionShow_Oculars");
2957 		}
2958 	}
2959 	flagShowOcularsButton = b;
2960 	settings->setValue("show_toolbar_button", b);
2961 	settings->sync();
2962 
2963 	emit flagShowOcularsButtonChanged(b);
2964 }
2965 
2966 
setGuiPanelFontSize(int size)2967 void Oculars::setGuiPanelFontSize(int size)
2968 {
2969 	// This forces a redraw of the panel.
2970 	if (size!=guiPanelFontSize)
2971 	{
2972 		bool guiPanelVisible=guiPanel;
2973 		if (guiPanelVisible)
2974 			enableGuiPanel(false);
2975 		guiPanelFontSize=size;
2976 		if (guiPanelVisible)
2977 			enableGuiPanel(true);
2978 
2979 		settings->setValue("gui_panel_fontsize", size);
2980 		settings->sync();
2981 		emit guiPanelFontSizeChanged(size);
2982 	}
2983 }
2984 
toggleCropOverlay()2985 void Oculars::toggleCropOverlay()
2986 {
2987 	setFlagShowCcdCropOverlay(!getFlagShowCcdCropOverlay());
2988 }
2989 
togglePixelGrid()2990 void Oculars::togglePixelGrid()
2991 {
2992 	setFlagShowCcdCropOverlayPixelGrid(!getFlagShowCcdCropOverlayPixelGrid());
2993 }
2994 
toggleFocuserOverlay()2995 void Oculars::toggleFocuserOverlay()
2996 {
2997 	setFlagShowFocuserOverlay(!getFlagShowFocuserOverlay());
2998 }
2999 
computeLimitMagnitude(Ocular * ocular,Telescope * telescope)3000 double Oculars::computeLimitMagnitude(Ocular *ocular, Telescope *telescope)
3001 {
3002 	// Simplified calculation of the penetrating power of the telescope
3003 	double diameter = 0.;
3004 	if (ocular->isBinoculars())
3005 		diameter = ocular->fieldStop();
3006 	else
3007 		diameter = telescope!=Q_NULLPTR ? telescope->diameter() : 0.1; // Avoid a potential call of null pointer, and a log(0) error.
3008 
3009 	// A better formula for telescopic limiting magnitudes?
3010 	// North, G.; Journal of the British Astronomical Association, vol.107, no.2, p.82
3011 	// http://adsabs.harvard.edu/abs/1997JBAA..107...82N
3012 	return 4.5 + 4.4*std::log10(diameter);
3013 }
3014 
handleAutoLimitToggle(bool on)3015 void Oculars::handleAutoLimitToggle(bool on)
3016 {
3017 	if (!flagShowOculars)
3018 		return;
3019 
3020 	// When we are in Oculars mode, we must toggle between the auto limit and manual limit. Logic taken from zoomOcular()/unzoomOcular()
3021 	StelCore *core = StelApp::getInstance().getCore();
3022 	StelSkyDrawer *skyDrawer = core->getSkyDrawer();
3023 	if (on)
3024 	{
3025 		Ocular * ocular = oculars[selectedOcularIndex];
3026 		Telescope * telescope = Q_NULLPTR;
3027 		if (!ocular->isBinoculars())
3028 		{
3029 			telescope = telescopes[selectedTelescopeIndex];
3030 		}
3031 		disconnect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double))); // keep the old manual value in config.
3032 		double limitMag = computeLimitMagnitude(ocular, telescope);
3033 		// TODO: Is it really good to apply the star formula to DSO?
3034 		skyDrawer->setFlagNebulaMagnitudeLimit(true);
3035 		skyDrawer->setCustomNebulaMagnitudeLimit(limitMag);
3036 		skyDrawer->setFlagStarMagnitudeLimit(true);
3037 		skyDrawer->setCustomStarMagnitudeLimit(limitMag);
3038 	}
3039 	else
3040 	{
3041 		connect(skyDrawer, SIGNAL(customStarMagLimitChanged(double)), this, SLOT(setMagLimitStarsOcularsManual(double)));
3042 		skyDrawer->setCustomStarMagnitudeLimit(magLimitStarsOculars);
3043 		skyDrawer->setFlagStarMagnitudeLimit(flagLimitStarsOculars);
3044 	}
3045 }
3046 
3047 // Handle switching the main program's star limitation flag
handleStarMagLimitToggle(bool on)3048 void Oculars::handleStarMagLimitToggle(bool on)
3049 {
3050 	if (!flagShowOculars)
3051 		return;
3052 
3053 	flagLimitStarsOculars=on;
3054 	// It only makes sense to switch off the auto-limit when we switch off the limit.
3055 	if (!on)
3056 	{
3057 		setFlagAutoLimitMagnitude(false);
3058 	}
3059 }
3060