7#include "locationdialog.h"
10#include "kstarsdata.h"
12#include "ksnotification.h"
13#include "kstars_debug.h"
19#include <QGeoPositionInfoSource>
22#include <QJsonDocument>
25#include <QNetworkAccessManager>
26#include <QNetworkReply>
29#include <QPlainTextEdit>
32LocationDialogUI::LocationDialogUI(
QWidget *parent) :
QFrame(parent)
44 SelectedCity =
nullptr;
45 ld =
new LocationDialogUI(
this);
51 ld->MapView->setLocationDialog(
this);
60 for (
int i = 0; i < 25; ++i)
61 ld->TZBox->addItem(
QLocale().toString(
static_cast<double>(i - 12)));
67 ld->DSTRuleBox->addItem(key);
73 connect(ld->NewCityName, SIGNAL(textChanged(
QString)),
this, SLOT(nameChanged()));
74 connect(ld->NewProvinceName, SIGNAL(textChanged(
QString)),
this, SLOT(nameChanged()));
75 connect(ld->NewCountryName, SIGNAL(textChanged(
QString)),
this, SLOT(nameChanged()));
76 connect(ld->NewLong, SIGNAL(textChanged(
QString)),
this, SLOT(dataChanged()));
77 connect(ld->NewLat, SIGNAL(textChanged(
QString)),
this, SLOT(dataChanged()));
78 connect(ld->NewElev, SIGNAL(valueChanged(
double)),
this, SLOT(dataChanged()));
80 connect(ld->TZBox, SIGNAL(activated(
int)),
this, SLOT(dataChanged()));
81 connect(ld->DSTRuleBox, SIGNAL(activated(
int)),
this, SLOT(dataChanged()));
83 connect(ld->AddCityButton, SIGNAL(clicked()),
this, SLOT(
addCity()));
84 connect(ld->ClearFieldsButton, SIGNAL(clicked()),
this, SLOT(clearFields()));
92 qDebug() << Q_FUNC_INFO <<
"Last known position" << source->lastKnownPosition().coordinate();
96 connect(source, SIGNAL(updateTimeout()),
this, SLOT(positionUpdateTimeout()));
98 connect(ld->GetLocationButton, SIGNAL(clicked()),
this, SLOT(requestUpdate()));
101 ld->DSTLabel->setText(
"<a href=\"showrules\">" +
i18n(
"DST rule:") +
"</a>");
102 connect(ld->DSTLabel, SIGNAL(linkActivated(
QString)),
this, SLOT(showTZRules()));
104 dataModified =
false;
105 nameModified =
false;
106 ld->AddCityButton->setEnabled(
false);
108 ld->errorLabel->setText(
QString());
125 ld->GeoBox->addItem(loc->
fullName());
126 filteredCityList.append(loc);
129 if (loc->
TZ0() -
int(loc->
TZ0()) && ld->TZBox->findText(
QLocale().toString(loc->
TZ0())) != -1)
131 for (
int i = 0; i < ld->TZBox->count(); ++i)
133 if (ld->TZBox->itemText(i).toDouble() > loc->
TZ0())
135 ld->TZBox->addItem(
QLocale().toString(loc->
TZ0()), i - 1);
143 ld->GeoBox->sortItems();
145 ld->CountLabel->setText(
146 i18np(
"One city matches search criteria",
"%1 cities match search criteria", ld->GeoBox->count()));
149 ld->GeoBox->setCurrentItem(
nullptr);
150 for (
int i = 0; i < ld->GeoBox->count(); i++)
152 if (ld->GeoBox->item(i)->text() == data->
geo()->
fullName())
154 ld->GeoBox->setCurrentRow(i);
167 timer->setSingleShot(
true);
178 while (!filteredCityList.isEmpty())
179 filteredCityList.takeFirst();
181 nameModified =
false;
182 dataModified =
false;
183 ld->AddCityButton->setEnabled(
false);
184 ld->UpdateButton->setEnabled(
false);
198 ld->GeoBox->addItem(loc->
fullName());
199 filteredCityList.append(loc);
203 ld->GeoBox->sortItems();
205 ld->CountLabel->setText(
206 i18np(
"One city matches search criteria",
"%1 cities match search criteria", ld->GeoBox->count()));
208 if (ld->GeoBox->count() > 0)
209 ld->GeoBox->setCurrentItem(ld->GeoBox->item(0));
211 ld->MapView->repaint();
219 SelectedCity =
nullptr;
220 if (ld->GeoBox->currentItem())
222 for (
auto &loc : filteredCityList)
224 if (loc->fullName() == ld->GeoBox->currentItem()->text())
232 ld->MapView->repaint();
237 ld->NewCityName->setText(SelectedCity->translatedName());
238 if (SelectedCity->province().isEmpty())
239 ld->NewProvinceName->setText(
QString());
241 ld->NewProvinceName->setText(SelectedCity->translatedProvince());
243 ld->NewCountryName->setText(SelectedCity->translatedCountry());
244 ld->NewLong->show(SelectedCity->lng());
245 ld->NewLat->show(SelectedCity->lat());
246 ld->TZBox->setEditText(
QLocale().toString(SelectedCity->TZ0()));
247 ld->NewElev->setValue(SelectedCity->elevation());
250 for (
int i = 0; i < ld->DSTRuleBox->count(); ++i)
253 if (tzr.
equals(SelectedCity->tzrule()))
255 ld->DSTRuleBox->setCurrentIndex(i);
260 ld->RemoveButton->setEnabled(SelectedCity->isReadOnly() ==
false);
263 nameModified =
false;
264 dataModified =
false;
265 ld->AddCityButton->setEnabled(
false);
266 ld->UpdateButton->setEnabled(
false);
276 if (SelectedCity ==
nullptr)
284 if (SelectedCity ==
nullptr)
292 if (operation == CITY_REMOVE)
298 else if (!nameModified && !dataModified)
300 QString message =
i18n(
"This city already exists in the database.");
301 KSNotification::sorry(message,
i18n(
"Error: Duplicate Entry"));
305 bool latOk(
false), lngOk(
false), tzOk(
false);
306 dms lat = ld->NewLat->createDms(&latOk);
307 dms lng = ld->NewLong->createDms(&lngOk);
308 QString TimeZoneString = ld->TZBox->lineEdit()->text();
310 double TZ = TimeZoneString.
toDouble(&tzOk);
311 double height = ld->NewElev->value();
313 if (ld->NewCityName->text().isEmpty() || ld->NewCountryName->text().isEmpty())
315 QString message =
i18n(
"All fields (except province) must be filled to add this location.");
316 KSNotification::sorry(message,
i18n(
"Fields are Empty"));
319 else if (!latOk || !lngOk)
321 QString message =
i18n(
"Could not parse the Latitude/Longitude.");
322 KSNotification::sorry(message,
i18n(
"Bad Coordinates"));
327 QString message =
i18n(
"UTC Offset must be selected.");
328 KSNotification::sorry(message,
i18n(
"UTC Offset"));
333 if (operation == CITY_ADD && !nameModified)
334 operation = CITY_UPDATE;
352 QString query(
"CREATE TABLE city ( "
353 "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, "
354 "Name TEXT DEFAULT NULL, "
355 "Province TEXT DEFAULT NULL, "
356 "Country TEXT DEFAULT NULL, "
357 "Latitude TEXT DEFAULT NULL, "
358 "Longitude TEXT DEFAULT NULL, "
359 "TZ REAL DEFAULT NULL, "
360 "TZRule TEXT DEFAULT NULL,"
361 "Elevation REAL NOT NULL DEFAULT -10 )");
363 if (create_query.
exec(query) ==
false)
365 qCWarning(KSTARS) << create_query.
lastError();
369 else if (mycitydb.
open() ==
false)
371 qCWarning(KSTARS) << mycitydb.
lastError();
379 QString TZrule = ld->DSTRuleBox->currentText();
380 double Elevation = ld->NewElev->value();
388 add_query.
prepare(
"INSERT INTO city(Name, Province, Country, Latitude, Longitude, TZ, TZRule, Elevation) "
389 "VALUES(:Name, :Province, :Country, :Latitude, :Longitude, :TZ, :TZRule, :Elevation)");
391 add_query.
bindValue(
":Province", province);
392 add_query.
bindValue(
":Country", country);
397 add_query.
bindValue(
":Elevation", Elevation);
398 if (add_query.
exec() ==
false)
400 qCWarning(KSTARS) << add_query.
lastError();
405 g =
new GeoLocation(lng, lat, name, province, country, TZ, &KStarsData::Instance()->Rulebook[TZrule], Elevation);
406 KStarsData::Instance()->getGeoList().append(g);
415 update_query.
prepare(
"UPDATE city SET Name = :newName, Province = :newProvince, Country = :newCountry, "
416 "Latitude = :Latitude, Longitude = :Longitude, TZ = :TZ, TZRule = :TZRule, Elevation = :Elevation WHERE "
417 "Name = :Name AND Province = :Province AND Country = :Country");
418 update_query.
bindValue(
":newName", name);
419 update_query.
bindValue(
":newProvince", province);
420 update_query.
bindValue(
":newCountry", country);
421 update_query.
bindValue(
":Name", SelectedCity->name());
422 update_query.
bindValue(
":Province", SelectedCity->province());
423 update_query.
bindValue(
":Country", SelectedCity->country());
427 update_query.
bindValue(
":TZRule", TZrule);
428 update_query.
bindValue(
":Elevation", Elevation);
429 if (update_query.
exec() ==
false)
431 qCWarning(KSTARS) << update_query.
lastError();
441 g->
setTZRule(&KStarsData::Instance()->Rulebook[TZrule]);
451 delete_query.
prepare(
"DELETE FROM city WHERE Name = :Name AND Province = :Province AND Country = :Country");
453 delete_query.
bindValue(
":Province", province);
454 delete_query.
bindValue(
":Country", country);
455 if (delete_query.
exec() ==
false)
457 qCWarning(KSTARS) << delete_query.
lastError();
461 filteredCityList.removeOne(g);
462 KStarsData::Instance()->getGeoList().removeOne(g);
473 ld->GeoBox->setCurrentItem(
nullptr);
474 if (g && ld->GeoBox->count())
476 for (
int i = 0; i < ld->GeoBox->count(); i++)
478 if (ld->GeoBox->item(i)->text() == g->
fullName())
480 ld->GeoBox->setCurrentRow(i);
498 while (!filteredCityList.isEmpty())
499 filteredCityList.takeFirst();
505 ld->GeoBox->addItem(loc->
fullName());
506 filteredCityList.append(loc);
510 ld->GeoBox->sortItems();
511 ld->CountLabel->setText(
512 i18np(
"One city matches search criteria",
"%1 cities match search criteria", ld->GeoBox->count()));
514 if (ld->GeoBox->count() > 0)
515 ld->GeoBox->setCurrentItem(ld->GeoBox->item(0));
520bool LocationDialog::checkLongLat()
522 if (ld->NewLong->text().isEmpty() || ld->NewLat->text().isEmpty())
526 double lng = ld->NewLong->createDms(&ok).Degrees();
529 double lat = ld->NewLat->createDms(&ok).Degrees();
533 if (fabs(lng) > 180 || fabs(lat) > 90)
539void LocationDialog::clearFields()
541 ld->CityFilter->clear();
542 ld->ProvinceFilter->clear();
543 ld->CountryFilter->clear();
544 ld->NewCityName->clear();
545 ld->NewProvinceName->clear();
546 ld->NewCountryName->clear();
547 ld->NewLong->clearFields();
548 ld->NewLat->clearFields();
549 ld->NewElev->setValue(-10);
550 ld->TZBox->setCurrentIndex(-1);
553 ld->DSTRuleBox->setCurrentIndex(0);
555 dataModified =
false;
558 ld->UpdateButton->setEnabled(
false);
559 ld->NewCityName->setFocus();
562void LocationDialog::showTZRules()
566 if (KSUtils::openDataFile(file,
"TZrules.dat") ==
false)
569 QTextStream stream(&file);
571 QString message =
i18n(
"Daylight Saving Time Rules");
573 QPointer<QDialog> tzd =
new QDialog(
this);
574 tzd->setWindowTitle(message);
576 QPlainTextEdit *textEdit =
new QPlainTextEdit(tzd);
578 while (stream.atEnd() ==
false)
580 QString line = stream.readLine();
587 QVBoxLayout *mainLayout =
new QVBoxLayout;
594 tzd->setLayout(mainLayout);
601void LocationDialog::nameChanged()
608void LocationDialog::dataChanged()
611 ld->AddCityButton->setEnabled(nameModified && !ld->NewCityName->text().isEmpty() &&
612 !ld->NewCountryName->text().isEmpty() && checkLongLat() && ld->TZBox->currentIndex() != -1);
614 ld->UpdateButton->setEnabled(SelectedCity->isReadOnly() ==
false && !ld->NewCityName->text().isEmpty() &&
615 !ld->NewCountryName->text().isEmpty() && checkLongLat() && ld->TZBox->currentIndex() != -1);
617 if (ld->AddCityButton->isEnabled() ==
false && ld->UpdateButton->isEnabled() ==
false)
619 if (ld->NewCityName->text().isEmpty())
621 ld->errorLabel->setText(
i18n(
"Cannot add new location -- city name blank"));
623 else if (ld->NewCountryName->text().isEmpty())
625 ld->errorLabel->setText(
i18n(
"Cannot add new location -- country name blank"));
627 else if (!checkLongLat())
629 ld->errorLabel->setText(
i18n(
"Cannot add new location -- invalid latitude / longitude"));
631 else if (ld->TZBox->currentIndex() == -1)
633 ld->errorLabel->setText(
i18n(
"Cannot add new location -- missing UTC Offset"));
635 else if (SelectedCity && SelectedCity->isReadOnly())
637 ld->errorLabel->setText(
i18n(
"City is Read Only. Change name to add new city."));
642 ld->errorLabel->setText(QString());
646void LocationDialog::slotOk()
648 if (ld->AddCityButton->isEnabled())
659void LocationDialog::getNameFromCoordinates(
double latitude,
double longitude)
663 QString latlng(lat +
", " + lon);
665 QUrl url(
"http://maps.googleapis.com/maps/api/geocode/json");
667 query.addQueryItem(
"latlng", latlng);
669 qDebug() << Q_FUNC_INFO <<
"submitting request";
671 nam->get(QNetworkRequest(url));
672 connect(nam, SIGNAL(
finished(QNetworkReply*)),
this, SLOT(processLocationNameData(QNetworkReply*)));
675void LocationDialog::processLocationNameData(
QNetworkReply *networkReply)
680 if (!networkReply->
error())
686 QJsonObject obj = document.
object();
689 if (obj.
contains(QStringLiteral(
"results")))
691 val = obj[
"results"];
694 val.
toArray()[0].toObject()[
"address_components"].toArray()[2].toObject()[
"long_name"].toString();
696 val.
toArray()[0].toObject()[
"address_components"].toArray()[3].toObject()[
"long_name"].toString();
698 val.
toArray()[0].toObject()[
"address_components"].toArray()[4].toObject()[
"long_name"].toString();
710void LocationDialog::requestUpdate()
712 source->requestUpdate(15000);
717 qDebug() << Q_FUNC_INFO <<
"Position updated:" << info;
722 qDebug() << Q_FUNC_INFO <<
"Position update error: " <<
error;
725void LocationDialog::positionUpdateTimeout()
727 qDebug() << Q_FUNC_INFO <<
"Timed out!";
728 qDebug() << Q_FUNC_INFO << source->error();
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
void setName(const QString &n)
Set City name according to argument.
const CachingDms * lat() const
const CachingDms * lng() const
QString translatedCountry() const
void setLong(const dms &l)
Set longitude according to dms argument.
void setLat(const dms &l)
Set latitude according to dms argument.
void setTZ0(double value)
Set Time zone.
void setElevation(double hg)
Set elevation above sea level.
void setTZRule(TimeZoneRule *value)
Set Time zone rule.
QString translatedName() const
void setProvince(const QString &n)
Set Province name according to argument.
QString translatedProvince() const
void setCountry(const QString &n)
Set Country name according to argument.
KStarsData is the backbone of KStars.
QList< GeoLocation * > & getGeoList()
const QMap< QString, TimeZoneRule > & getRulebook() const
Return map for daylight saving rules.
void enqueueFilterCity()
Filter by city / province / country only after a few milliseconds.
bool updateCity()
When the "Update City" QPushButton is clicked, update the city information in the user's custom city ...
QString selectedCityName() const
void filterCity()
When text is entered in the City/Province/Country Filter KLineEdits, the List of cities is trimmed to...
void initCityList(void)
Initialize list of cities.
LocationDialog(QWidget *parent)
Constructor.
void findCitiesNear(int longitude, int latitude)
Show only cities within 3 degrees of point specified by arguments.
bool addCity()
When the "Add new city" QPushButton is clicked, add the manually-entered city information to the user...
bool removeCity()
When the "Remove City" QPushButton is clicked, remove the city information from the user's custom cit...
void changeCity()
When the selected city in the QListBox changes, repaint the MapCanvas so that the crosshairs icon app...
This class provides the information needed to determine whether Daylight Savings Time (DST; a....
bool equals(TimeZoneRule *r)
An angle, stored as degrees, but expressible in many ways.
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
const double & Degrees() const
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QStringView country(QStringView ifopt)
QDialog(QWidget *parent, Qt::WindowFlags f)
void finished(int result)
QString filePath(const QString &fileName) const const
bool exists(const QString &fileName)
SatellitePositioningMethods
QGeoPositionInfoSource * createDefaultSource(QObject *parent)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool isObject() const const
QJsonObject object() const const
bool contains(QLatin1StringView key) const const
QJsonArray toArray() const const
QList< Key > keys() const const
T value(const Key &key, const T &defaultValue) const const
NetworkError error() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
void appendPlainText(const QString &text)
void ensureCursorVisible()
void moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode)
void setReadOnly(bool ro)
QSqlDatabase database(const QString &connectionName, bool open)
QSqlError lastError() const const
void setDatabaseName(const QString &name)
void bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType paramType)
QSqlError lastError() const const
bool prepare(const QString &query)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
QString trimmed() const const
QStringView trimmed() const const