1 /*
2  * Stellarium
3  * Copyright (C) 2008 Guillaume Chereau
4  * Copyright (C) 2011 Alexander Wolf
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19 */
20 
21 #include "Dialog.hpp"
22 #include "LocationDialog.hpp"
23 #include "StelLocationMgr.hpp"
24 #include "ui_locationDialogGui.h"
25 #include "StelApp.hpp"
26 #include "StelCore.hpp"
27 
28 #include "StelModuleMgr.hpp"
29 #include "SolarSystem.hpp"
30 #include "StelFileMgr.hpp"
31 #include "StelLocaleMgr.hpp"
32 #include "StelGui.hpp"
33 #include "StelGuiItems.hpp"
34 #include "StelSkyCultureMgr.hpp"
35 
36 #include <QSettings>
37 #include <QDebug>
38 #include <QFrame>
39 #include <QSortFilterProxyModel>
40 #include <QTimer>
41 #include <QStringListModel>
42 #include <QTimeZone>
43 
LocationDialog(QObject * parent)44 LocationDialog::LocationDialog(QObject* parent)
45 	: StelDialog("Location", parent)
46 	, isEditingNew(false)
47 	, allModel(Q_NULLPTR)
48 	, pickedModel(Q_NULLPTR)
49 	, proxyModel(Q_NULLPTR)
50 #ifdef ENABLE_GPS
51 	, gpsCount(0)
52 #endif
53 {
54 	ui = new Ui_locationDialogForm;
55 }
56 
~LocationDialog()57 LocationDialog::~LocationDialog()
58 {
59 	delete ui;
60 }
61 
retranslate()62 void LocationDialog::retranslate()
63 {
64 	if (dialog)
65 	{
66 		ui->retranslateUi(dialog);
67 		populatePlanetList();
68 		populateRegionList(StelApp::getInstance().getCore()->getCurrentLocation().planetName);
69 		populateTimeZonesList();
70 		populateTooltips();
71 	}
72 }
73 
styleChanged()74 void LocationDialog::styleChanged()
75 {
76 	// Make the map red if needed
77 	if (dialog)
78 		setMapForLocation(StelApp::getInstance().getCore()->getCurrentLocation());
79 }
80 
81 // Initialize the dialog widgets and connect the signals/slots
createDialogContent()82 void LocationDialog::createDialogContent()
83 {
84 	// We try to directly connect to the observer slots as much as we can
85 	ui->setupUi(dialog);
86 
87 	//enable resizability
88 	ui->mapLabel->setMinimumSize(0,0);
89 	ui->mapLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
90 	ui->mapLabel->setScaledContents(false);
91 
92 	StelApp *app = &StelApp::getInstance();
93 	connect(app, SIGNAL(languageChanged()), this, SLOT(retranslate()));
94 	connect(app, SIGNAL(flagShowDecimalDegreesChanged(bool)), this, SLOT(setDisplayFormatForSpins(bool)));
95 	connect(&app->getSkyCultureMgr(), SIGNAL(currentSkyCultureChanged(QString)), this, SLOT(populatePlanetList(QString)));
96 	// Init the SpinBox entries
97 	ui->longitudeSpinBox->setPrefixType(AngleSpinBox::Longitude);
98 	ui->longitudeSpinBox->setMinimum(-180.0, true);
99 	ui->longitudeSpinBox->setMaximum( 180.0, true);
100 	ui->longitudeSpinBox->setWrapping(true);
101 	ui->latitudeSpinBox->setPrefixType(AngleSpinBox::Latitude);
102 	ui->latitudeSpinBox->setMinimum(-90.0, true);
103 	ui->latitudeSpinBox->setMaximum( 90.0, true);
104 	ui->latitudeSpinBox->setWrapping(false);
105 	setDisplayFormatForSpins(app->getFlagShowDecimalDegrees());
106 
107 	//initialize list model
108 	allModel = new QStringListModel(this);
109 	pickedModel = new QStringListModel(this);
110 	StelLocationMgr *locMgr=&(StelApp::getInstance().getLocationMgr());
111 	connect(locMgr, SIGNAL(locationListChanged()), this, SLOT(reloadLocations()));
112 	reloadLocations();
113 	proxyModel = new QSortFilterProxyModel(ui->citiesListView);
114 	proxyModel->setSourceModel(allModel);
115 	proxyModel->sort(0, Qt::AscendingOrder);
116 	proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
117 	ui->citiesListView->setModel(proxyModel);
118 
119 	// Kinetic scrolling
120 	kineticScrollingList << ui->citiesListView;
121 	StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
122 	if (gui)
123 	{
124 		enableKineticScrolling(gui->getFlagUseKineticScrolling());
125 		connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
126 	}
127 
128 	populatePlanetList();
129 	populateTimeZonesList();
130 
131 	connect(ui->citySearchLineEdit, SIGNAL(textChanged(const QString&)), proxyModel, SLOT(setFilterWildcard(const QString&)));
132 	connect(ui->citiesListView, SIGNAL(clicked(const QModelIndex&)),
133 		this, SLOT(setLocationFromList(const QModelIndex&)));
134 
135 	// Connect all the QT signals
136 	connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
137 	connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
138 	connect(ui->mapLabel, SIGNAL(positionChanged(double, double)), this, SLOT(setLocationFromMap(double, double)));
139 
140 	connect(ui->addLocationToListPushButton, SIGNAL(clicked()), this, SLOT(addCurrentLocationToList()));
141 	connect(ui->deleteLocationFromListPushButton, SIGNAL(clicked()), this, SLOT(deleteCurrentLocationFromList()));
142 	connect(ui->resetListPushButton, SIGNAL(clicked()), this, SLOT(resetLocationList()));
143 	connect(ui->regionNameComboBox, SIGNAL(activated(const QString &)), this, SLOT(filterSitesByRegion()));
144 
145 	StelCore* core = StelApp::getInstance().getCore();
146 	const StelLocation& currentLocation = core->getCurrentLocation();
147 	populateRegionList(currentLocation.planetName);
148 	bool b = (currentLocation.getID() == core->getDefaultLocationID());
149 	QSettings* conf = StelApp::getInstance().getSettings();
150 	if (conf->value("init_location/location", "auto").toString() == "auto")
151 	{
152 		ui->useIpQueryCheckBox->setChecked(true);
153 		b = false;
154 	}
155 	updateDefaultLocationControls(b);
156 
157 	setFieldsFromLocation(currentLocation);
158 	if (currentLocation.ianaTimeZone != core->getCurrentTimeZone())
159 	{
160 		setTimezone(core->getCurrentTimeZone()); // also sets string customTimeZone and GUI checkbox
161 	}
162 	else
163 	{
164 		//ui->timeZoneNameComboBox->setEnabled(false);
165 		// TODO Maybe also:
166 		// StelApp::getInstance().getSettings()->remove("localization/time_zone");
167 	}
168 
169 #ifdef ENABLE_GPS
170 	connect(ui->gpsToolButton, SIGNAL(toggled(bool)), this, SLOT(gpsEnableQueryLocation(bool)));
171 	ui->gpsToolButton->setStyleSheet(QString("QToolButton{ background: gray; }")); // ? Missing default style?
172 	connect(locMgr, SIGNAL(gpsQueryFinished(bool)), this, SLOT(gpsReturn(bool)));
173 #else
174 	ui->gpsToolButton->setEnabled(false);
175 	ui->gpsToolButton->hide();
176 #endif
177 	connect(ui->useIpQueryCheckBox, SIGNAL(clicked(bool)), this, SLOT(ipQueryLocation(bool)));
178 	connect(ui->useAsDefaultLocationCheckBox, SIGNAL(clicked(bool)), this, SLOT(setDefaultLocation(bool)));
179 	connect(ui->pushButtonReturnToDefault, SIGNAL(clicked()), core, SLOT(returnToDefaultLocation()));
180 	connect(ui->pushButtonReturnToDefault, SIGNAL(clicked()), this, SLOT(resetLocationList()));
181 	connectBoolProperty(ui->dstCheckBox, "StelCore.flagUseDST");
182 	connectBoolProperty(ui->useCustomTimeZoneCheckBox, "StelCore.flagUseCTZ");
183 	connect(ui->useCustomTimeZoneCheckBox, SIGNAL(toggled(bool)), ui->timeZoneNameComboBox, SLOT(setEnabled(bool)));
184 	ui->timeZoneNameComboBox->setEnabled(core->getUseCustomTimeZone());
185 	connect(ui->useCustomTimeZoneCheckBox, SIGNAL(clicked(bool)), this, SLOT(updateTimeZoneControls(bool)));
186 	connect(core, SIGNAL(currentTimeZoneChanged(QString)), this, SLOT(setTimezone(QString)));
187 
188 	connectEditSignals();
189 
190 	populateTooltips();
191 
192 	connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(updateFromProgram(StelLocation)));
193 
194 	ui->citySearchLineEdit->setFocus();
195 }
196 
setDisplayFormatForSpins(bool flagDecimalDegrees)197 void LocationDialog::setDisplayFormatForSpins(bool flagDecimalDegrees)
198 {
199 	int places = 2;
200 	AngleSpinBox::DisplayFormat format = AngleSpinBox::DMSSymbols;
201 	if (flagDecimalDegrees)
202 	{
203 		places = 6;
204 		format = AngleSpinBox::DecimalDeg;
205 	}
206 	ui->longitudeSpinBox->setDecimals(places);
207 	ui->longitudeSpinBox->setDisplayFormat(format);
208 	ui->latitudeSpinBox->setDecimals(places);
209 	ui->latitudeSpinBox->setDisplayFormat(format);
210 }
211 
handleDialogSizeChanged(QSizeF size)212 void LocationDialog::handleDialogSizeChanged(QSizeF size)
213 {
214 	StelDialog::handleDialogSizeChanged(size);
215 	StelLocation loc = locationFromFields();
216 	//resizePixmap();
217 	//ui->mapLabel->setCursorPos(loc.longitude, loc.latitude);
218 }
219 
reloadLocations()220 void LocationDialog::reloadLocations()
221 {
222 	allModel->setStringList(StelApp::getInstance().getLocationMgr().getAllMap().keys());
223 }
224 
populateTooltips()225 void LocationDialog::populateTooltips()
226 {
227 	ui->resetListPushButton->setToolTip(q_("Reset location list to show all known locations"));
228 	ui->gpsToolButton->setToolTip(QString("<p>%1</p>").arg(q_("Toggle fetching GPS location. (Does not change time zone!) When satisfied, toggle off to let other programs access the GPS device.")));
229 }
230 
231 // Update the widget to make sure it is synchrone if the location is changed programmatically
updateFromProgram(const StelLocation & currentLocation)232 void LocationDialog::updateFromProgram(const StelLocation& currentLocation)
233 {
234 	if (!dialog)
235 		return;
236 
237 	StelCore* stelCore = StelApp::getInstance().getCore();
238 
239 	isEditingNew = false;
240 
241 	// Check that the use as default check box needs to be updated
242 	// Move to setFieldsFromLocation()? --BM?
243 	const bool b = currentLocation.getID() == stelCore->getDefaultLocationID();
244 	QSettings* conf = StelApp::getInstance().getSettings();
245 	if (conf->value("init_location/location", "auto").toString() != ("auto"))
246 	{
247 		updateDefaultLocationControls(b);
248 		ui->pushButtonReturnToDefault->setEnabled(!b);
249 	}
250 
251 	const QString& key1 = currentLocation.getID();
252 	const QString& key2 = locationFromFields().getID();
253 	if (key1!=key2)
254 	{
255 		setFieldsFromLocation(currentLocation);
256 	}
257 }
258 
disconnectEditSignals()259 void LocationDialog::disconnectEditSignals()
260 {
261 	disconnect(ui->longitudeSpinBox, SIGNAL(valueChanged()), this, SLOT(setLocationFromCoords()));
262 	disconnect(ui->latitudeSpinBox, SIGNAL(valueChanged()), this, SLOT(setLocationFromCoords()));
263 	disconnect(ui->altitudeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setLocationFromCoords(int)));
264 	disconnect(ui->planetNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(moveToAnotherPlanet(const QString&)));
265 	disconnect(ui->regionNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(reportEdit()));
266 	disconnect(ui->timeZoneNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(saveTimeZone()));
267 	disconnect(ui->cityNameLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(reportEdit()));
268 }
269 
connectEditSignals()270 void LocationDialog::connectEditSignals()
271 {
272 	connect(ui->longitudeSpinBox, SIGNAL(valueChanged()), this, SLOT(setLocationFromCoords()));
273 	connect(ui->latitudeSpinBox, SIGNAL(valueChanged()), this, SLOT(setLocationFromCoords()));
274 	connect(ui->altitudeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setLocationFromCoords(int)));
275 	connect(ui->planetNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(moveToAnotherPlanet(const QString&)));
276 	connect(ui->regionNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(reportEdit()));
277 	connect(ui->timeZoneNameComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(saveTimeZone()));
278 	connect(ui->cityNameLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(reportEdit()));
279 }
280 
setFieldsFromLocation(const StelLocation & loc)281 void LocationDialog::setFieldsFromLocation(const StelLocation& loc)
282 {
283 	StelCore *core = StelApp::getInstance().getCore();
284 
285 	// Deactivate edit signals
286 	disconnectEditSignals();
287 
288 	// When user has activated a custom timezone, we should not change this!
289 	if (core->getUseCustomTimeZone())
290 		customTimeZone=core->getCurrentTimeZone();
291 
292 	ui->cityNameLineEdit->setText(loc.name);
293 	ui->longitudeSpinBox->setDegrees(loc.longitude);
294 	ui->latitudeSpinBox->setDegrees(loc.latitude);
295 	ui->altitudeSpinBox->setValue(loc.altitude);
296 
297 	int idx = ui->planetNameComboBox->findData(loc.planetName, Qt::UserRole, Qt::MatchCaseSensitive);
298 	if (idx==-1)
299 	{
300 		// Use Earth as default
301 		idx = ui->planetNameComboBox->findData(QVariant("Earth"), Qt::UserRole, Qt::MatchCaseSensitive);
302 	}
303 	ui->planetNameComboBox->setCurrentIndex(idx);
304 
305 	idx = ui->regionNameComboBox->findData(loc.region, Qt::UserRole, Qt::MatchCaseSensitive);
306 	if (idx==-1)
307 	{
308 		if (ui->planetNameComboBox->currentData(Qt::UserRole).toString()=="Earth")
309 		{
310 			// Use Western Europe as default on Earth
311 			idx = ui->regionNameComboBox->findData(QVariant("Western Europe"), Qt::UserRole, Qt::MatchCaseSensitive);
312 		}
313 		else
314 			idx = ui->regionNameComboBox->findData(QVariant(""), Qt::UserRole, Qt::MatchCaseSensitive);
315 	}
316 	ui->regionNameComboBox->setCurrentIndex(idx);
317 
318 	QString tz = loc.ianaTimeZone;
319 	if (loc.planetName=="Earth" && tz.isEmpty())
320 	{
321 		qDebug() << "setFieldsFromLocation(): Empty loc.ianaTimeZone!";
322 		tz = "system_default";
323 	}
324 	if (loc.planetName!="Earth") // Check for non-terrestial location...
325 		tz = "LMST";
326 
327 	if (core->getUseCustomTimeZone())
328 		tz=customTimeZone;
329 
330 	idx = ui->timeZoneNameComboBox->findData(tz, Qt::UserRole, Qt::MatchCaseSensitive);
331 	if (idx==-1)
332 	{
333 		if (loc.planetName=="Earth")
334 			tz = "system_default";
335 		else
336 			tz = "LMST";
337 		// Use LMST/system_default as default
338 		idx = ui->timeZoneNameComboBox->findData(tz, Qt::UserRole, Qt::MatchCaseSensitive);
339 	}
340 	ui->timeZoneNameComboBox->setCurrentIndex(idx);
341 	core->setCurrentTimeZone(tz);
342 
343 	setMapForLocation(loc);
344 	ui->mapLabel->setCursorPos(loc.longitude, loc.latitude);
345 
346 	ui->deleteLocationFromListPushButton->setEnabled(StelApp::getInstance().getLocationMgr().canDeleteUserLocation(loc.getID()));
347 
348 	SolarSystem* ssm = GETSTELMODULE(SolarSystem);
349 	PlanetP p = ssm->searchByEnglishName(loc.planetName);
350 	StelModule* ls = StelApp::getInstance().getModule("LandscapeMgr");
351 	if (ls->property("flagEnvironmentAutoEnable").toBool())
352 	{
353 		if (loc.planetName != StelApp::getInstance().getCore()->getCurrentLocation().planetName)
354 		{
355 			QSettings* conf = StelApp::getInstance().getSettings();
356 			ls->setProperty("atmosphereDisplayed", p->hasAtmosphere() && conf->value("landscape/flag_atmosphere", true).toBool());
357 			ls->setProperty("fogDisplayed", p->hasAtmosphere() && conf->value("landscape/flag_fog", true).toBool());
358 		}
359 	}
360 
361 	// Reactivate edit signals
362 	connectEditSignals();
363 }
364 
365 // Update the map for the given location.
setMapForLocation(const StelLocation & loc)366 void LocationDialog::setMapForLocation(const StelLocation& loc)
367 {
368 	// Avoids useless processing
369 	if (lastPlanet==loc.planetName)
370 		return;
371 
372 	QPixmap pixmap;
373 	// Try to set the proper planet map image
374 	if (loc.planetName=="Earth")
375 	{
376 		// Special case for earth, we don't want to see the clouds
377 		pixmap = QPixmap(":/graphicGui/miscWorldMap.png");
378 	}
379 	else
380 	{
381 		SolarSystem* ssm = GETSTELMODULE(SolarSystem);
382 		PlanetP p = ssm->searchByEnglishName(loc.planetName);
383 		if (p)
384 		{
385 			QString path = StelFileMgr::findFile("textures/"+p->getTextMapName());
386 			if (path.isEmpty())
387 			{
388 				qWarning() << "ERROR - could not find planet map for " << loc.planetName;
389 				return;
390 			}
391 			pixmap = QPixmap(path);
392 		}
393 	}
394 	StelCore * core = StelApp::getInstance().getCore();
395 	pixmap.setDevicePixelRatio(core->getCurrentStelProjectorParams().devicePixelsPerPixel);
396 	ui->mapLabel->setPixmap(pixmap);
397 	ui->mapLabel->resizePixmap();
398 	ui->mapLabel->setCursorPos(loc.longitude, loc.latitude);
399 	// For caching
400 	lastPlanet = loc.planetName;
401 }
402 
populatePlanetList()403 void LocationDialog::populatePlanetList()
404 {
405 	Q_ASSERT(ui->planetNameComboBox);
406 	QComboBox* planetsCombo = ui->planetNameComboBox;
407 	SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
408 	QList<PlanetP> ssList = ssystem->getAllPlanets();
409 
410 	//Save the current selection to be restored later
411 	planetsCombo->blockSignals(true);
412 	int index = planetsCombo->currentIndex();
413 	QVariant selectedPlanetId = planetsCombo->itemData(index);
414 	planetsCombo->clear();
415 	//For each planet, display the localized name and store the original as user
416 	//data. Unfortunately, there's no other way to do this than with a loop.
417 	for (const auto& p : ssList)
418 	{
419 		planetsCombo->addItem(p->getNameI18n(), p->getEnglishName());
420 	}
421 	//Restore the selection
422 	index = planetsCombo->findData(selectedPlanetId, Qt::UserRole, Qt::MatchCaseSensitive);
423 	planetsCombo->setCurrentIndex(index);
424 	planetsCombo->model()->sort(0);
425 	planetsCombo->blockSignals(false);
426 }
427 
populateRegionList(const QString & planet)428 void LocationDialog::populateRegionList(const QString& planet)
429 {
430 	Q_ASSERT(ui->regionNameComboBox);
431 
432 	QComboBox* regionCombo = ui->regionNameComboBox;
433 	QStringList regionNames(StelApp::getInstance().getLocationMgr().getRegionNames(planet));
434 
435 	//Save the current selection to be restored later
436 	regionCombo->blockSignals(true);
437 	int index = regionCombo->currentIndex();
438 	QVariant selectedRegionId = regionCombo->itemData(index);
439 	regionCombo->clear();
440 	//For each region, display the localized name and store the original as user
441 	//data. Unfortunately, there's no other way to do this than with a loop.
442 	for (const auto& name : qAsConst(regionNames))
443 		regionCombo->addItem(q_(name), name);
444 
445 	regionCombo->addItem(QChar(0x2014), "");
446 	//Restore the selection
447 	index = regionCombo->findData(selectedRegionId, Qt::UserRole, Qt::MatchCaseSensitive);
448 	regionCombo->setCurrentIndex(index);
449 	regionCombo->model()->sort(0);
450 	regionCombo->blockSignals(false);
451 }
452 
populateTimeZonesList()453 void LocationDialog::populateTimeZonesList()
454 {
455 	Q_ASSERT(ui->timeZoneNameComboBox);
456 
457 	QComboBox* tzCombo = ui->timeZoneNameComboBox;
458 	// Return a list of all the known time zone names (from Qt)
459 	QStringList tzNames;
460 	auto tzList = QTimeZone::availableTimeZoneIds(); // System dependent set of IANA timezone names.
461 	for (const auto& tz : qAsConst(tzList))
462 	{
463 		tzNames.append(tz);
464 		// Activate this to get a list of known TZ names...
465 		//qDebug() << "Qt/IANA TZ entry from QTimeZone::available: " << tz;
466 	}
467 	tzNames.sort();
468 
469 	//Save the current selection to be restored later
470 	tzCombo->blockSignals(true);
471 	int index = tzCombo->currentIndex();
472 	QVariant selectedTzId = tzCombo->itemData(index);
473 	tzCombo->clear();
474 	//For each time zone, display the localized name and store the original as user
475 	//data. Unfortunately, there's no other way to do this than with a loop.
476 	for (const auto& name : tzNames)
477 	{
478 		tzCombo->addItem(name, name);
479 	}
480 	tzCombo->addItem(q_("Local Mean Solar Time"), "LMST");
481 	tzCombo->addItem(q_("Local True Solar Time"), "LTST");
482 	tzCombo->addItem(q_("System default"), "system_default");
483 	//Restore the selection
484 	index = tzCombo->findData(selectedTzId, Qt::UserRole, Qt::MatchCaseSensitive);
485 	// TODO: Handle notfound!?
486 	if (index==-1)
487 	{
488 		index=tzCombo->count()-1; // should point to system_default.
489 	}
490 	Q_ASSERT(index!=-1);
491 	tzCombo->setCurrentIndex(index);
492 	tzCombo->blockSignals(false);
493 }
494 
495 // Create a StelLocation instance from the fields
locationFromFields() const496 StelLocation LocationDialog::locationFromFields() const
497 {
498 	StelLocation loc;
499 	int index = ui->planetNameComboBox->currentIndex();
500 	if (index < 0)
501 	{
502 		qWarning() << "LocationDialog::locationFromFields(): no valid planet name from combo?";
503 		loc.planetName = QString("Earth"); //As returned by QComboBox::currentText()
504 	}
505 	else
506 		loc.planetName = ui->planetNameComboBox->itemData(index).toString();
507 	loc.name = ui->cityNameLineEdit->text().trimmed(); // avoid locations with leading whitespace
508 	loc.latitude = qBound(-90.0, ui->latitudeSpinBox->valueDegrees(), 90.0);
509 	loc.longitude = ui->longitudeSpinBox->valueDegrees();
510 	loc.altitude = ui->altitudeSpinBox->value();
511 	index = ui->regionNameComboBox->currentIndex();
512 	if (index < 0)
513 	{
514 		qWarning() << "LocationDialog::locationFromFields(): no valid region name from combo?";
515 		loc.region = QString();//As returned by QComboBox::currentText()
516 	}
517 	else
518 		loc.region = ui->regionNameComboBox->itemData(index).toString();
519 
520 	index = ui->timeZoneNameComboBox->currentIndex();
521 	if (index < 0)
522 	{
523 		qWarning() << "LocationDialog::locationFromFields(): no valid timezone name from combo?";
524 		loc.ianaTimeZone = QString("UTC"); //Give at least some useful default
525 	}
526 	else
527 	{
528 		QString tz=ui->timeZoneNameComboBox->itemData(index).toString();
529 		loc.ianaTimeZone = tz;
530 	}
531 	return loc;
532 }
533 
setLocationFromList(const QModelIndex & index)534 void LocationDialog::setLocationFromList(const QModelIndex& index)
535 {
536 	isEditingNew=false;
537 	ui->addLocationToListPushButton->setEnabled(false);
538 	StelLocation loc = StelApp::getInstance().getLocationMgr().locationForString(index.data().toString());
539 	setFieldsFromLocation(loc);
540 	StelApp::getInstance().getCore()->moveObserverTo(loc, 0.);
541 	// This calls indirectly updateFromProgram()
542 }
543 
setLocationFromMap(double longitude,double latitude)544 void LocationDialog::setLocationFromMap(double longitude, double latitude)
545 {
546 	StelCore *core = StelApp::getInstance().getCore();
547 	if (core->getUseCustomTimeZone())
548 		customTimeZone=core->getCurrentTimeZone();
549 	reportEdit();
550 	StelLocation loc = locationFromFields();
551 	loc.latitude = latitude;
552 	loc.longitude = longitude;
553 	setFieldsFromLocation(loc);
554 	core->moveObserverTo(loc, 0.);
555 	// Only for locations on Earth: set zone to LMST.
556 	// TODO: Find a way to lookup (lon,lat)->country->timezone.
557 
558 	if (core->getUseCustomTimeZone())
559 		ui->timeZoneNameComboBox->setCurrentIndex(ui->timeZoneNameComboBox->findData(customTimeZone, Qt::UserRole, Qt::MatchCaseSensitive));
560 	else
561 		ui->timeZoneNameComboBox->setCurrentIndex(ui->timeZoneNameComboBox->findData("LMST", Qt::UserRole, Qt::MatchCaseSensitive));
562 
563 	// Filter location list for nearby sites. I assume Earth locations are better known. With only few locations on other planets in the list, 30 degrees seem OK.
564 	LocationMap results = StelApp::getInstance().getLocationMgr().pickLocationsNearby(loc.planetName, longitude, latitude, loc.planetName=="Earth" ? 5.0f: 30.0f);
565 	pickedModel->setStringList(results.keys());
566 	proxyModel->setSourceModel(pickedModel);
567 	proxyModel->sort(0, Qt::AscendingOrder);
568 	ui->citySearchLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
569 }
570 
571 // Called when the planet name is changed by hand
moveToAnotherPlanet(const QString &)572 void LocationDialog::moveToAnotherPlanet(const QString&)
573 {
574 	reportEdit();
575 	StelLocation loc = locationFromFields();
576 	StelCore* stelCore = StelApp::getInstance().getCore();
577 	StelModule* lMgr = StelApp::getInstance().getModule("LandscapeMgr");
578 	populateRegionList(loc.planetName);
579 	if (loc.planetName != stelCore->getCurrentLocation().planetName)
580 	{
581 		setFieldsFromLocation(loc);
582 		if (lMgr->property("flagLandscapeAutoSelection").toBool())
583 		{
584 			// If we have a landscape for selected planet then set it, otherwise use default landscape
585 			// Details: https://bugs.launchpad.net/stellarium/+bug/1173254
586 			if (lMgr->property("allLandscapeNames").toStringList().indexOf(loc.planetName)>0)
587 				lMgr->setProperty("currentLandscapeName", loc.planetName);
588 			else
589 				lMgr->setProperty("currentLandscapeID", lMgr->property("defaultLandscapeID"));
590 		}
591 
592 		// GZ populate site list with sites only from that planet, or full list for Earth (faster than removing the ~50 non-Earth positions...).
593 		StelLocationMgr &locMgr=StelApp::getInstance().getLocationMgr();
594 		if (loc.planetName == "Earth")
595 		{
596 			proxyModel->setSourceModel(allModel);
597 		}
598 		else
599 		{
600 			LocationMap results = locMgr.pickLocationsNearby(loc.planetName, 0.0f, 0.0f, 180.0f);
601 			pickedModel->setStringList(results.keys());
602 			proxyModel->setSourceModel(pickedModel);
603 			ui->regionNameComboBox->setCurrentIndex(ui->regionNameComboBox->findData("", Qt::UserRole, Qt::MatchCaseSensitive));
604 			// For 0.19, time zone should not change. When we can work out LMST for other planets, we can accept LMST.
605 			//if (customTimeZone.isEmpty())  // This is always true!
606 			//	ui->timeZoneNameComboBox->setCurrentIndex(ui->timeZoneNameComboBox->findData("LMST", Qt::UserRole, Qt::MatchCaseSensitive));
607 		}
608 		proxyModel->sort(0, Qt::AscendingOrder);
609 		ui->citySearchLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
610 		ui->citySearchLineEdit->setFocus();
611 		stelCore->moveObserverTo(loc, 0., 0.);
612 	}
613 	// Planet transition time also set to null to prevent uglyness when
614 	// "use landscape location" is enabled for that planet's landscape. --BM
615 	// NOTE: I think it also makes sense in the other cases. --BM
616 }
617 
setLocationFromCoords(int i)618 void LocationDialog::setLocationFromCoords(int i)
619 {
620 	Q_UNUSED(i)
621 	reportEdit();
622 	StelLocation loc = locationFromFields();
623 	StelApp::getInstance().getCore()->moveObserverTo(loc, 0.);
624 	//Update the position of the map pointer
625 	ui->mapLabel->setCursorPos(loc.longitude, loc.latitude);
626 }
627 
saveTimeZone()628 void LocationDialog::saveTimeZone()
629 {
630 	QString tz = ui->timeZoneNameComboBox->itemData(ui->timeZoneNameComboBox->currentIndex()).toString();
631 	StelCore* core = StelApp::getInstance().getCore();
632 	core->setCurrentTimeZone(tz);
633 	if (core->getUseCustomTimeZone())
634 	{
635 		StelApp::getInstance().getSettings()->setValue("localization/time_zone", tz);
636 	}
637 }
638 
setTimezone(QString tz)639 void LocationDialog::setTimezone(QString tz)
640 {
641 	disconnectEditSignals();
642 
643 	int idx=ui->timeZoneNameComboBox->findData(tz, Qt::UserRole, Qt::MatchCaseSensitive);
644 	if (idx>=0)
645 	{
646 		ui->timeZoneNameComboBox->setCurrentIndex(idx);
647 	}
648 	else
649 	{
650 		qWarning() << "LocationDialog::setTimezone(): invalid name:" << tz;
651 	}
652 
653 	connectEditSignals();
654 }
655 
reportEdit()656 void LocationDialog::reportEdit()
657 {
658 	if (isEditingNew==false)
659 	{
660 		// The user starts editing manually a field, this creates automatically a new location
661 		// and allows to save it to the user locations list
662 		isEditingNew=true;
663 	}
664 
665 	StelLocation loc = locationFromFields();
666 	StelLocationMgr& locationMgr = StelApp::getInstance().getLocationMgr();
667 	bool canSave = locationMgr.canSaveUserLocation(loc);
668 	if (!canSave)
669 	{
670 		if (ui->cityNameLineEdit->hasFocus())
671 		{
672 			// The user is editing the location name: don't change it!
673 			ui->addLocationToListPushButton->setEnabled(false);
674 			ui->deleteLocationFromListPushButton->setEnabled(false);
675 			return;
676 		}
677 		else
678 		{
679 			ui->cityNameLineEdit->setText("");
680 			ui->cityNameLineEdit->selectAll();
681 			loc = locationFromFields();
682 		}
683 	}
684 	ui->addLocationToListPushButton->setEnabled(isEditingNew && canSave);
685 	ui->deleteLocationFromListPushButton->setEnabled(locationMgr.canDeleteUserLocation(loc.getID()));
686 }
687 
688 // Called when the user clicks on the save button
addCurrentLocationToList()689 void LocationDialog::addCurrentLocationToList()
690 {
691 	const StelLocation& loc = locationFromFields();
692 	ui->citySearchLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
693 	StelApp::getInstance().getLocationMgr().saveUserLocation(loc);
694 	isEditingNew=false;
695 	ui->addLocationToListPushButton->setEnabled(false);
696 
697 	const QAbstractItemModel* model = ui->citiesListView->model();
698 	const QString id = loc.getID();
699 	for (int i=0;i<model->rowCount();++i)
700 	{
701 		if (model->index(i,0).data()==id)
702 		{
703 			ui->citiesListView->selectionModel()->select(model->index(i,0), QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows);
704 			ui->citiesListView->scrollTo(model->index(i,0));
705 			setLocationFromList(model->index(i,0));
706 			disconnectEditSignals();
707 			ui->citySearchLineEdit->setFocus();
708 			connectEditSignals();
709 			break;
710 		}
711 	}
712 }
713 
714 // Called when the user wants to use the current location as default
setDefaultLocation(bool state)715 void LocationDialog::setDefaultLocation(bool state)
716 {
717 	if (state)
718 	{
719 		StelCore* core = StelApp::getInstance().getCore();
720 		QString currentLocationId = core->getCurrentLocation().getID();
721 		core->setDefaultLocationID(currentLocationId);
722 
723 		// Why this code even exists? After the previous code, this should always
724 		// be true, except if setting the default location somehow fails. --BM
725 		bool isDefault = (currentLocationId == core->getDefaultLocationID());
726 		disconnectEditSignals();
727 		updateDefaultLocationControls(isDefault);
728 		ui->pushButtonReturnToDefault->setEnabled(!isDefault);
729 		ui->useIpQueryCheckBox->setChecked(!state);
730 		ui->citySearchLineEdit->setFocus();
731 		connectEditSignals();
732 	}
733 }
734 
735 // Called when the user clicks on the delete button
deleteCurrentLocationFromList()736 void LocationDialog::deleteCurrentLocationFromList()
737 {
738 	const StelLocation& loc = locationFromFields();
739 	StelApp::getInstance().getLocationMgr().deleteUserLocation(loc.getID());
740 }
741 
updateDefaultLocationControls(bool currentIsDefault)742 void LocationDialog::updateDefaultLocationControls(bool currentIsDefault)
743 {
744 	ui->useAsDefaultLocationCheckBox->setChecked(currentIsDefault);
745 	ui->useAsDefaultLocationCheckBox->setEnabled(!currentIsDefault);
746 }
747 
updateTimeZoneControls(bool useCustomTimeZone)748 void LocationDialog::updateTimeZoneControls(bool useCustomTimeZone)
749 {
750 	StelCore* core = StelApp::getInstance().getCore();
751 	core->setUseCustomTimeZone(useCustomTimeZone);
752 
753 	if (useCustomTimeZone)
754 	{
755 		saveTimeZone();
756 	}
757 	else
758 	{
759 		StelLocation loc = core->getCurrentLocation();
760 		QString tz = loc.ianaTimeZone;
761 		if (loc.planetName=="Earth" && tz.isEmpty())
762 			tz = "system_default";
763 		int idx = ui->timeZoneNameComboBox->findData(tz, Qt::UserRole, Qt::MatchCaseSensitive);
764 		if (idx==-1)
765 		{
766 			QString defTZ = "LMST";
767 			if (loc.planetName=="Earth")
768 				defTZ = "system_default";
769 			// Use LMST/system_default as default
770 			idx = ui->timeZoneNameComboBox->findData(defTZ, Qt::UserRole, Qt::MatchCaseSensitive);
771 		}
772 		ui->timeZoneNameComboBox->setCurrentIndex(idx);
773 		StelApp::getInstance().getSettings()->remove("localization/time_zone");
774 		core->setUseCustomTimeZone(false);
775 	}
776 }
777 
778 // called when the user clicks on the IP Query button
ipQueryLocation(bool state)779 void LocationDialog::ipQueryLocation(bool state)
780 {
781 	disconnectEditSignals();
782 	resetLocationList(); // in case we are on Moon/Mars, we must get list back to show all (earth) locations...
783 	StelLocationMgr &locMgr=StelApp::getInstance().getLocationMgr();
784 	locMgr.locationFromIP(); // This just triggers asynchronous lookup.
785 	// NOTE: These steps seem to assume IP lookup is successful!
786 	ui->useAsDefaultLocationCheckBox->setChecked(!state);
787 	ui->pushButtonReturnToDefault->setEnabled(!state);
788 	updateTimeZoneControls(!state);
789 	connectEditSignals();
790 	ui->citySearchLineEdit->setFocus();
791 	QSettings* conf = StelApp::getInstance().getSettings();
792 	if (state)
793 		conf->setValue("init_location/location", "auto");
794 	else
795 		conf->setValue("init_location/location", StelApp::getInstance().getCore()->getCurrentLocation().getID());
796 }
797 
798 #ifdef ENABLE_GPS
799 // called when the user toggles the GPS Query button. Use gpsd or Qt's NMEA reader.
gpsEnableQueryLocation(bool running)800 void LocationDialog::gpsEnableQueryLocation(bool running)
801 {
802 	if (running)
803 	{
804 		disconnectEditSignals();
805 		gpsCount=0;
806 		ui->gpsToolButton->setText(q_("GPS listening..."));
807 		StelApp::getInstance().getLocationMgr().locationFromGPS(3000);
808 	}
809 	else
810 	{
811 		// (edit signals restored by gpsReturn())
812 		StelApp::getInstance().getLocationMgr().locationFromGPS(0);
813 		ui->gpsToolButton->setText(q_("GPS disconnecting..."));
814 		QTimer::singleShot(1500, this, SLOT(resetGPSbuttonLabel()));
815 	}
816 }
817 
gpsReturn(bool success)818 void LocationDialog::gpsReturn(bool success)
819 {
820 	if (success)
821 	{
822 		StelCore* core = StelApp::getInstance().getCore();
823 
824 		gpsCount++;
825 		ui->gpsToolButton->setText(QString("%1 %2").arg(q_("GPS location fix")).arg(gpsCount));
826 		ui->useAsDefaultLocationCheckBox->setChecked(false);
827 		ui->pushButtonReturnToDefault->setEnabled(true);
828 		//ui->useCustomTimeZoneCheckBox->setChecked(true); // done by updateTimeZoneControls(true) below.
829 		ui->useIpQueryCheckBox->setChecked(false); // Disable IP query option when GPS is used!
830 		resetLocationList(); // in case we come back from Moon/Mars, we must get list back to show all (earth) locations...
831 		updateTimeZoneControls(true);
832 		StelLocation loc=core->getCurrentLocation();
833 		setFieldsFromLocation(loc);
834 		if (loc.altitude==0) // give feedback of fix quality.
835 		{
836 			ui->gpsToolButton->setStyleSheet(QString("QToolButton{ background: yellow; }"));
837 		}
838 		else
839 		{
840 			ui->gpsToolButton->setStyleSheet(QString("QToolButton{ background: green; }"));
841 		}
842 		QSettings* conf = StelApp::getInstance().getSettings();
843 		conf->setValue("init_location/location", loc.getID());
844 		conf->setValue("init_location/last_location", QString("%1, %2, %3").arg(loc.latitude).arg(loc.longitude).arg(loc.altitude));
845 	}
846 	else
847 	{
848 		ui->gpsToolButton->setText(q_("GPS:FAILED"));
849 		ui->gpsToolButton->setStyleSheet(QString("QToolButton{ background: red; }"));
850 		// Use QTimer to reset the labels after 2 seconds.
851 		QTimer::singleShot(2000, this, SLOT(resetGPSbuttonLabel()));
852 	}
853 	connectEditSignals();
854 	ui->citySearchLineEdit->setFocus();
855 }
856 
resetGPSbuttonLabel()857 void LocationDialog::resetGPSbuttonLabel()
858 {
859 	ui->gpsToolButton->setChecked(false);
860 	ui->gpsToolButton->setText(q_("Get location from GPS"));
861 	ui->gpsToolButton->setStyleSheet(QString("QToolButton{ background: gray; }"));
862 }
863 #endif
864 
865 // called when user clicks "reset list"
resetLocationList()866 void LocationDialog::resetLocationList()
867 {
868 	//reset search before setting model, prevents unnecessary search in full list
869 	ui->citySearchLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
870 	ui->citySearchLineEdit->setFocus();
871 	proxyModel->setSourceModel(allModel);
872 	proxyModel->sort(0, Qt::AscendingOrder);
873 }
874 
875 // called when user clicks in the country combobox and selects a country. The locations in the list are updated to select only sites in that country.
filterSitesByRegion()876 void LocationDialog::filterSitesByRegion()
877 {
878 	QString region=ui->regionNameComboBox->currentData(Qt::UserRole).toString();
879 	StelLocationMgr &locMgr=StelApp::getInstance().getLocationMgr();
880 
881 	LocationMap results = locMgr.pickLocationsInRegion(region);
882 	pickedModel->setStringList(results.keys());
883 	proxyModel->setSourceModel(pickedModel);
884 	proxyModel->sort(0, Qt::AscendingOrder);
885 	ui->citySearchLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
886 	ui->citySearchLineEdit->setFocus();
887 }
888