Kstars

kstarsdata.cpp
1/*
2 SPDX-FileCopyrightText: 2001 Heiko Evermann <heiko@evermann.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "kstarsdata.h"
8
9#include "ksutils.h"
10#include "Options.h"
11#include "auxiliary/kspaths.h"
12#include "skycomponents/supernovaecomponent.h"
13#include "skycomponents/skymapcomposite.h"
14#include "ksnotification.h"
15#include "skyobjectuserdata.h"
16#include <kio/job_base.h>
17#include <kio/filecopyjob.h>
18#ifndef KSTARS_LITE
19#include "fov.h"
20#include "imageexporter.h"
21#include "kstars.h"
22#include "observinglist.h"
23#include "skymap.h"
24#include "dialogs/detaildialog.h"
25#include "oal/execute.h"
26#endif
27
28#ifndef KSTARS_LITE
29#include <KMessageBox>
30#endif
31
32#include <QSqlQuery>
33#include <QSqlRecord>
34#include <QtConcurrent>
35
36#include "kstars_debug.h"
37
38// Qt version calming
39#include <qtskipemptyparts.h>
40
41namespace
42{
43// Report fatal error during data loading to user
44// Calls QApplication::exit
45void fatalErrorMessage(QString fname)
46{
47 qCCritical(KSTARS) << i18n("Critical File not Found: %1", fname);
48 KSNotification::sorry(i18n("The file %1 could not be found. "
49 "KStars cannot run properly without this file. "
50 "KStars searches for this file in following locations:\n\n\t"
51 "%2\n\n"
52 "It appears that your setup is broken.",
54 i18n("Critical File Not Found: %1", fname)); // FIXME: Must list locations depending on file type
55
56 qApp->exit(1);
57}
58
59// Report non-fatal error during data loading to user and ask
60// whether he wants to continue.
61//
62// No remaining calls so commented out to suppress unused warning
63//
64// Calls QApplication::exit if he don't
65//bool nonFatalErrorMessage(QString fname)
66//{
67// qCWarning(KSTARS) << i18n( "Non-Critical File Not Found: %1", fname );
68//#ifdef KSTARS_LITE
69// Q_UNUSED(fname);
70// return true;
71//#else
72// int res = KMessageBox::warningContinueCancel(nullptr,
73// i18n("The file %1 could not be found. "
74// "KStars can still run without this file. "
75// "KStars search for this file in following locations:\n\n\t"
76// "%2\n\n"
77// "It appears that you setup is broken. Press Continue to run KStars without this file ",
78// fname, QStandardPaths::standardLocations( QStandardPaths::AppLocalDataLocation ).join("\n\t") ),
79// i18n( "Non-Critical File Not Found: %1", fname )); // FIXME: Must list locations depending on file type
80// if( res != KMessageBox::Continue )
81// qApp->exit(1);
82// return res == KMessageBox::Continue;
83//#endif
84//}
85}
86
87KStarsData *KStarsData::pinstance = nullptr;
88
89KStarsData *KStarsData::Create()
90{
91 // This method should never be called twice within a run, since a
92 // lot of the code assumes that KStarsData, once created, is never
93 // destroyed. They maintain local copies of KStarsData::Instance()
94 // for efficiency (maybe this should change, but it is not
95 // required to delete and reinstantiate KStarsData). Thus, when we
96 // call this method, pinstance MUST be zero, i.e. this must be the
97 // first (and last) time we are calling it. -- asimha
98 Q_ASSERT(!pinstance);
99
100 delete pinstance;
101 pinstance = new KStarsData();
102 return pinstance;
103}
104
106 : m_Geo(dms(0), dms(0)), m_ksuserdb(),
107 temporaryTrail(false),
108 //locale( new KLocale( "kstars" ) ),
109 m_preUpdateID(0), m_updateID(0), m_preUpdateNumID(0), m_updateNumID(0), m_preUpdateNum(J2000), m_updateNum(J2000)
110{
111#ifndef KSTARS_LITE
112 m_LogObject.reset(new OAL::Log);
113#endif
114 // at startup times run forward
115 setTimeDirection(0.0);
116}
117
119{
120 Q_ASSERT(pinstance);
121
122 //delete locale;
123 qDeleteAll(geoList);
124 geoList.clear();
125 qDeleteAll(ADVtreeList);
126 ADVtreeList.clear();
127
128 pinstance = nullptr;
129}
130
132{
133 //Load Time Zone Rules//
134 emit progressText(i18n("Reading time zone rules"));
135 if (!readTimeZoneRulebook())
136 {
137 fatalErrorMessage("TZrules.dat");
138 return false;
139 }
140
141 emit progressText(
142 i18n("Upgrade existing user city db to support geographic elevation."));
143
144 QString dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mycitydb.sqlite");
145
146 /// This code to add Height column to table city in mycitydb.sqlite is a transitional measure to support a meaningful
147 /// geographic elevation.
148 if (QFile::exists(dbfile))
149 {
150 QSqlDatabase fixcitydb = QSqlDatabase::addDatabase("QSQLITE", "fixcitydb");
151
152 fixcitydb.setDatabaseName(dbfile);
153 fixcitydb.open();
154
155 if (fixcitydb.tables().contains("city", Qt::CaseInsensitive))
156 {
157 QSqlRecord r = fixcitydb.record("city");
158 if (!r.contains("Elevation"))
159 {
160 emit progressText(i18n("Adding \"Elevation\" column to city table."));
161
162 QSqlQuery query(fixcitydb);
163 if (query.exec(
164 "alter table city add column Elevation real default -10;") ==
165 false)
166 {
167 emit progressText(QString("failed to add Elevation column to city "
168 "table in mycitydb.sqlite: &1")
169 .arg(query.lastError().text()));
170 }
171 }
172 else
173 {
174 emit progressText(i18n("City table already contains \"Elevation\"."));
175 }
176 }
177 else
178 {
179 emit progressText(i18n("City table missing from database."));
180 }
181 fixcitydb.close();
182 }
183
184 //Load Cities//
185 emit progressText(i18n("Loading city data"));
186 if (!readCityData())
187 {
188 fatalErrorMessage("citydb.sqlite");
189 return false;
190 }
191
192 //Initialize User Database//
193 emit progressText(i18n("Loading User Information"));
194 m_ksuserdb.Initialize();
195
196 //Initialize SkyMapComposite//
197 emit progressText(i18n("Loading sky objects"));
198 m_SkyComposite.reset(new SkyMapComposite());
199 //Load Image URLs//
200 //#ifndef Q_OS_ANDROID
201 //On Android these 2 calls produce segfault. WARNING
202 emit progressText(i18n("Loading Image URLs"));
203
204 // if (!readURLData("image_url.dat", SkyObjectUserdata::Type::image) &&
205 // !nonFatalErrorMessage("image_url.dat"))
206 // return false;
207#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
208 QFuture<bool> future = QtConcurrent::run(&KStarsData::readURLData, this, QString("image_url.dat"),
209 SkyObjectUserdata::Type::image);
210#else
211 QtConcurrent::run(this, &KStarsData::readURLData, QString("image_url.dat"),
212 SkyObjectUserdata::Type::image);
213#endif
214
215 //Load Information URLs//
216 //emit progressText(i18n("Loading Information URLs"));
217 // if (!readURLData("info_url.dat", SkyObjectUserdata::Type::website) &&
218 // !nonFatalErrorMessage("info_url.dat"))
219 // return false;
220#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
221 future = QtConcurrent::run(&KStarsData::readURLData, this, QString("info_url.dat"),
222 SkyObjectUserdata::Type::website);
223#else
224 QtConcurrent::run(this, &KStarsData::readURLData, QString("info_url.dat"),
225 SkyObjectUserdata::Type::website);
226#endif
227
228 //#endif
229 //emit progressText( i18n("Loading Variable Stars" ) );
230
231#ifndef KSTARS_LITE
232 //Initialize Observing List
233 m_ObservingList = new ObservingList();
234#endif
235
236 readUserLog();
237
238#ifndef KSTARS_LITE
239 readADVTreeData();
240#endif
241 return true;
242}
243
244void KStarsData::updateTime(GeoLocation *geo, const bool automaticDSTchange)
245{
246 // sync LTime with the simulation clock
247 LTime = geo->UTtoLT(ut());
248 syncLST();
249
250 //Only check DST if (1) TZrule is not the empty rule, and (2) if we have crossed
251 //the DST change date/time.
252 if (!geo->tzrule()->isEmptyRule())
253 {
254 if (TimeRunsForward)
255 {
256 // timedirection is forward
257 // DST change happens if current date is bigger than next calculated dst change
258 if (ut() > NextDSTChange)
259 resetToNewDST(geo, automaticDSTchange);
260 }
261 else
262 {
263 // timedirection is backward
264 // DST change happens if current date is smaller than next calculated dst change
265 if (ut() < NextDSTChange)
266 resetToNewDST(geo, automaticDSTchange);
267 }
268 }
269
270 KSNumbers num(ut().djd());
271
272 if (std::abs(ut().djd() - LastNumUpdate.djd()) > 1.0)
273 {
274 LastNumUpdate = KStarsDateTime(ut().djd());
275 m_preUpdateNumID++;
276 m_preUpdateNum = KSNumbers(num);
277 skyComposite()->update(&num);
278 }
279
280 if (std::abs(ut().djd() - LastPlanetUpdate.djd()) > 0.01)
281 {
282 LastPlanetUpdate = KStarsDateTime(ut().djd());
284 }
285
286 // Moon moves ~30 arcmin/hr, so update its position every minute.
287 if (std::abs(ut().djd() - LastMoonUpdate.djd()) > 0.00069444)
288 {
289 LastMoonUpdate = ut();
290 skyComposite()->updateMoons(&num);
291 }
292
293 //Update Alt/Az coordinates. Timescale varies with zoom level
294 //If Clock is in Manual Mode, always update. (?)
295 if (std::abs(ut().djd() - LastSkyUpdate.djd()) > 0.1 / Options::zoomFactor() || clock()->isManualMode())
296 {
297 LastSkyUpdate = ut();
298 m_preUpdateID++;
299 //omit KSNumbers arg == just update Alt/Az coords // <-- Eh? -- asimha. Looks like this behavior / ideology has changed drastically.
300 skyComposite()->update(&num);
301
302 emit skyUpdate(clock()->isManualMode());
303 }
304}
305
306void KStarsData::syncUpdateIDs()
307{
308 m_updateID = m_preUpdateID;
309 if (m_updateNumID == m_preUpdateNumID)
310 return;
311 m_updateNumID = m_preUpdateNumID;
312 m_updateNum = KSNumbers(m_preUpdateNum);
313}
314
315unsigned int KStarsData::incUpdateID()
316{
317 m_preUpdateID++;
318 m_preUpdateNumID++;
319 syncUpdateIDs();
320 return m_updateID;
321}
322
324{
325 //Set the update markers to invalid dates to trigger updates in each category
326 LastSkyUpdate = KStarsDateTime(QDateTime());
327 LastPlanetUpdate = KStarsDateTime(QDateTime());
328 LastMoonUpdate = KStarsDateTime(QDateTime());
329 LastNumUpdate = KStarsDateTime(QDateTime());
330}
331
333{
334 LST = geo()->GSTtoLST(ut().gst());
335}
336
338{
339 //Turn off animated slews for the next time step.
341
342 clock()->setUTC(newDate);
343
344 LTime = geo()->UTtoLT(ut());
345 //set local sideral time
346 syncLST();
347
348 //Make sure Numbers, Moon, planets, and sky objects are updated immediately
350
351 // reset tzrules data with new local time and time direction (forward or backward)
352 geo()->tzrule()->reset_with_ltime(LTime, geo()->TZ0(), isTimeRunningForward());
353
354 // reset next dst change time
355 setNextDSTChange(geo()->tzrule()->nextDSTChange());
356}
357
358void KStarsData::resetToNewDST(GeoLocation *geo, const bool automaticDSTchange)
359{
360 // reset tzrules data with local time, timezone offset and time direction (forward or backward)
361 // force a DST change with option true for 3. parameter
362 geo->tzrule()->reset_with_ltime(LTime, geo->TZ0(), TimeRunsForward, automaticDSTchange);
363 // reset next DST change time
365 //reset LTime, because TZoffset has changed
366 LTime = geo->UTtoLT(ut());
367}
368
370{
371 TimeRunsForward = scale >= 0;
372}
373
374GeoLocation *KStarsData::locationNamed(const QString &city, const QString &province, const QString &country)
375{
376 foreach (GeoLocation *loc, geoList)
377 {
378 if (loc->translatedName() == city && (province.isEmpty() || loc->translatedProvince() == province) &&
379 (country.isEmpty() || loc->translatedCountry() == country))
380 {
381 return loc;
382 }
383 }
384 return nullptr;
385}
386
387GeoLocation *KStarsData::nearestLocation(double longitude, double latitude)
388{
389 GeoLocation *nearest = nullptr;
390 double distance = 1e6;
391
392 dms lng(longitude), lat(latitude);
393 for (auto oneCity : geoList)
394 {
395 double newDistance = oneCity->distanceTo(lng, lat);
396 if (newDistance < distance)
397 {
398 distance = newDistance;
399 nearest = oneCity;
400 }
401 }
402
403 return nearest;
404}
405
407{
408 setLocation(GeoLocation(dms(Options::longitude()), dms(Options::latitude()), Options::cityName(),
409 Options::provinceName(), Options::countryName(), Options::timeZone(),
410 &(Rulebook[Options::dST()]), Options::elevation(), false, 4));
411}
412
414{
415 m_Geo = GeoLocation(l);
416 if (m_Geo.lat()->Degrees() >= 90.0)
417 m_Geo.setLat(dms(89.99));
418 if (m_Geo.lat()->Degrees() <= -90.0)
419 m_Geo.setLat(dms(-89.99));
420
421 //store data in the Options objects
422 Options::setCityName(m_Geo.name());
423 Options::setProvinceName(m_Geo.province());
424 Options::setCountryName(m_Geo.country());
425 Options::setTimeZone(m_Geo.TZ0());
426 Options::setElevation(m_Geo.elevation());
427 Options::setLongitude(m_Geo.lng()->Degrees());
428 Options::setLatitude(m_Geo.lat()->Degrees());
429 // set the rule from rulebook
430 foreach (const QString &key, Rulebook.keys())
431 {
432 if (!key.isEmpty() && m_Geo.tzrule()->equals(&Rulebook[key]))
433 Options::setDST(key);
434 }
435
436 emit geoChanged();
437}
438
440{
441 if ((name == "star") || (name == "nothing") || name.isEmpty())
442 return nullptr;
443 return skyComposite()->findByName(name, true); // objectNamed has to do an exact match
444}
445
446bool KStarsData::readCityData()
447{
448 QSqlDatabase citydb = QSqlDatabase::addDatabase("QSQLITE", "citydb");
449 QString dbfile = KSPaths::locate(QStandardPaths::AppLocalDataLocation, "citydb.sqlite");
450 citydb.setDatabaseName(dbfile);
451 if (citydb.open() == false)
452 {
453 qCCritical(KSTARS) << "Unable to open city database file " << dbfile << citydb.lastError().text();
454 return false;
455 }
456
457 QSqlQuery get_query(citydb);
458
459 //get_query.prepare("SELECT * FROM city");
460 if (!get_query.exec("SELECT * FROM city"))
461 {
462 qCCritical(KSTARS) << get_query.lastError();
463 return false;
464 }
465
466 bool citiesFound = false;
467 // get_query.size() always returns -1 so we set citiesFound if at least one city is found
468 while (get_query.next())
469 {
470 citiesFound = true;
471 QString name = get_query.value(1).toString();
472 QString province = get_query.value(2).toString();
473 QString country = get_query.value(3).toString();
474 dms lat = dms(get_query.value(4).toString());
475 dms lng = dms(get_query.value(5).toString());
476 double TZ = get_query.value(6).toDouble();
477 TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]);
478 double elevation = get_query.value(8).toDouble();
479
480 // appends city names to list
481 geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, elevation, true, 4));
482 }
483 citydb.close();
484
485 // Reading local database
486 QSqlDatabase mycitydb = QSqlDatabase::addDatabase("QSQLITE", "mycitydb");
487 dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mycitydb.sqlite");
488
489 if (QFile::exists(dbfile))
490 {
491 mycitydb.setDatabaseName(dbfile);
492 if (mycitydb.open())
493 {
494 QSqlQuery get_query(mycitydb);
495
496 if (!get_query.exec("SELECT * FROM city"))
497 {
498 qDebug() << Q_FUNC_INFO << get_query.lastError();
499 return false;
500 }
501 while (get_query.next())
502 {
503 QString name = get_query.value(1).toString();
504 QString province = get_query.value(2).toString();
505 QString country = get_query.value(3).toString();
506 dms lat = dms(get_query.value(4).toString());
507 dms lng = dms(get_query.value(5).toString());
508 double TZ = get_query.value(6).toDouble();
509 TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]);
510 double elevation = get_query.value(8).toDouble();
511
512 // appends city names to list
513 geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, elevation, false, 4));
514 }
515 mycitydb.close();
516 }
517 }
518
519 return citiesFound;
520}
521
522bool KStarsData::readTimeZoneRulebook()
523{
524 QFile file;
525
526 if (KSUtils::openDataFile(file, "TZrules.dat"))
527 {
528 QTextStream stream(&file);
529
530 while (!stream.atEnd())
531 {
532 QString line = stream.readLine().trimmed();
533 if (line.length() && !line.startsWith('#')) //ignore commented and blank lines
534 {
535 QStringList fields = line.split(' ', Qt::SkipEmptyParts);
536 QString id = fields[0];
537 QTime stime = QTime(fields[3].left(fields[3].indexOf(':')).toInt(),
538 fields[3].mid(fields[3].indexOf(':') + 1, fields[3].length()).toInt());
539 QTime rtime = QTime(fields[6].left(fields[6].indexOf(':')).toInt(),
540 fields[6].mid(fields[6].indexOf(':') + 1, fields[6].length()).toInt());
541
542 Rulebook[id] = TimeZoneRule(fields[1], fields[2], stime, fields[4], fields[5], rtime);
543 }
544 }
545 return true;
546 }
547 else
548 {
549 return false;
550 }
551}
552
553bool KStarsData::openUrlFile(const QString &urlfile, QFile &file)
554{
555 //QFile file;
556 QString localFile;
557 bool fileFound = false;
558 QFile localeFile;
559
560 //if ( locale->language() != "en_US" )
561 if (QLocale().language() != QLocale::English)
562 //localFile = locale->language() + '/' + urlfile;
563 localFile = QLocale().languageToString(QLocale().language()) + '/' + urlfile;
564
565 if (!localFile.isEmpty() && KSUtils::openDataFile(file, localFile))
566 {
567 fileFound = true;
568 }
569 else
570 {
571 // Try to load locale file, if not successful, load regular urlfile and then copy it to locale.
572 file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(urlfile));
573 if (file.open(QIODevice::ReadOnly))
574 {
575 //local file found. Now, if global file has newer timestamp, then merge the two files.
576 //First load local file into QStringList
577 bool newDataFound(false);
578 QStringList urlData;
579 QTextStream lStream(&file);
580 while (!lStream.atEnd())
581 urlData.append(lStream.readLine());
582
583 //Find global file(s) in findAllResources() list.
584 QFileInfo fi_local(file.fileName());
585
586 QStringList flist = KSPaths::locateAll(QStandardPaths::AppDataLocation, urlfile);
587
588 for (int i = 0; i < flist.size(); i++)
589 {
590 if (flist[i] != file.fileName())
591 {
592 QFileInfo fi_global(flist[i]);
593
594 //Is this global file newer than the local file?
595 if (fi_global.lastModified() > fi_local.lastModified())
596 {
597 //Global file has newer timestamp than local. Add lines in global file that don't already exist in local file.
598 //be smart about this; in some cases the URL is updated but the image is probably the same if its
599 //label string is the same. So only check strings up to last ":"
600 QFile globalFile(flist[i]);
601 if (globalFile.open(QIODevice::ReadOnly))
602 {
603 QTextStream gStream(&globalFile);
604 while (!gStream.atEnd())
605 {
606 QString line = gStream.readLine();
607
608 //If global-file line begins with "XXX:" then this line should be removed from the local file.
609 if (line.startsWith(QLatin1String("XXX:")) && urlData.contains(line.mid(4)))
610 {
611 urlData.removeAt(urlData.indexOf(line.mid(4)));
612 }
613 else
614 {
615 //does local file contain the current global file line, up to second ':' ?
616
617 bool linefound(false);
618 for (int j = 0; j < urlData.size(); ++j)
619 {
620 if (urlData[j].contains(line.left(line.indexOf(':', line.indexOf(':') + 1))))
621 {
622 //replace line in urlData with its equivalent in the newer global file.
623 urlData.replace(j, line);
624 if (!newDataFound)
625 newDataFound = true;
626 linefound = true;
627 break;
628 }
629 }
630 if (!linefound)
631 {
632 urlData.append(line);
633 if (!newDataFound)
634 newDataFound = true;
635 }
636 }
637 }
638 }
639 }
640 }
641 }
642
643 file.close();
644
645 //(possibly) write appended local file
646 if (newDataFound)
647 {
648 if (file.open(QIODevice::WriteOnly))
649 {
650 QTextStream outStream(&file);
651 for (int i = 0; i < urlData.size(); i++)
652 {
653 outStream << urlData[i] << '\n';
654 }
655 file.close();
656 }
657 }
658
659 if (file.open(QIODevice::ReadOnly))
660 fileFound = true;
661 }
662 else
663 {
664 if (KSUtils::openDataFile(file, urlfile))
665 {
666 if (QLocale().language() != QLocale::English)
667 qDebug() << Q_FUNC_INFO << "No localized URL file; using default English file.";
668 // we found urlfile, we need to copy it to locale
669 localeFile.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(urlfile));
670 if (localeFile.open(QIODevice::WriteOnly))
671 {
672 QTextStream readStream(&file);
673 QTextStream writeStream(&localeFile);
674 while (!readStream.atEnd())
675 {
676 QString line = readStream.readLine();
677 if (!line.startsWith(QLatin1String("XXX:"))) //do not write "deleted" lines
678 writeStream << line << '\n';
679 }
680
681 localeFile.close();
682 file.reset();
683 }
684 else
685 {
686 qDebug() << Q_FUNC_INFO << "Failed to copy default URL file to locale folder, modifying default object links is "
687 "not possible";
688 }
689 fileFound = true;
690 }
691 }
692 }
693 return fileFound;
694}
695
696bool KStarsData::readURLData(const QString &urlfile, SkyObjectUserdata::Type type)
697{
698#ifndef KSTARS_LITE
699 if (KStars::Closing)
700 return true;
701#endif
702
703 QFile file;
704 if (!openUrlFile(urlfile, file))
705 return false;
706
707 QTextStream stream(&file);
708 QMutexLocker _{ &m_user_data_mutex };
709
710 while (!stream.atEnd())
711 {
712 QString line = stream.readLine();
713
714 //ignore comment lines
715 if (!line.startsWith('#'))
716 {
717#ifndef KSTARS_LITE
718 if (KStars::Closing)
719 {
720 file.close();
721 return true;
722 }
723#endif
724
725 int idx = line.indexOf(':');
726 QString name = line.left(idx);
727 if (name == "XXX")
728 continue;
729 QString sub = line.mid(idx + 1);
730 idx = sub.indexOf(':');
731 QString title = sub.left(idx);
732 QString url = sub.mid(idx + 1);
733 // Dirty hack to fix things up for planets
734
735 // if (name == "Mercury" || name == "Venus" || name == "Mars" || name == "Jupiter" || name == "Saturn" ||
736 // name == "Uranus" || name == "Neptune" /* || name == "Pluto" */)
737 // o = skyComposite()->findByName(i18n(name.toLocal8Bit().data()));
738 // else
739
740 auto &data_element = m_user_data[name];
741 data_element.addLink(title, QUrl{ url }, type);
742 }
743 }
744 file.close();
745 return true;
746}
747
748// FIXME: Improve the user log system
749
750// Note: It might be very nice to keep the log in plaintext files, for
751// portability, human-readability, and greppability. However, it takes
752// a lot of time to parse and look up, is very messy from the
753// reliability and programming point of view, needs to be parsed at
754// start, can become corrupt easily because of a missing bracket...
755
756// An SQLite database is a good compromise. A user can easily view it
757// using an SQLite browser. There is no need to read at start-up, one
758// can read the log when required. Easy to edit logs / update logs
759// etc. Will not become corrupt. Needn't be parsed.
760
761// However, IMHO, it is best to put these kinds of things in separate
762// databases, instead of unifying them as a table under the user
763// database. This ensures portability and a certain robustness that if
764// a user opens it, they cannot incorrectly edit a part of the DB they
765// did not intend to edit.
766
767// --asimha 2016 Aug 17
768
769// FIXME: This is a significant contributor to KStars startup time.
770bool KStarsData::readUserLog()
771{
772 QFile file;
773 QString fullContents;
774
775 if (!KSUtils::openDataFile(file, "userlog.dat"))
776 return false;
777
778 QTextStream stream(&file);
779
780 if (!stream.atEnd())
781 fullContents = stream.readAll();
782
783 QMutexLocker _{ &m_user_data_mutex };
784
785 QString buffer(fullContents);
786 const QLatin1String logStart("[KSLABEL:"), logEnd("[KSLogEnd]");
787 std::size_t currentEntryIndex = 0;
788 while (!buffer.isEmpty())
789 {
790 int startIndex, endIndex;
791 QString sub, name, data;
792
793 startIndex = buffer.indexOf(logStart) + logStart.size();
794 if (startIndex < 0)
795 break;
796 currentEntryIndex += startIndex;
797 endIndex = buffer.indexOf(logEnd, startIndex);
798
799 auto malformatError = [&]()
800 {
802 nullptr,
803 i18n("The user notes log file %1 is malformatted in the opening of the entry starting at %2. "
804 "KStars can still run without fully reading this file. "
805 "Press Continue to run KStars with whatever partial reading was successful. "
806 "The file may get truncated if KStars writes to the file later. Press Cancel to instead abort now and manually fix the problem. ",
807 file.fileName(), QString::number(currentEntryIndex)),
808 i18n( "Malformed file %1", file.fileName() )
809 );
810 if( res != KMessageBox::Continue )
811 qApp->exit(1); // FIXME: Why does this not work?
812 };
813
814 if (endIndex < 0)
815 {
816 malformatError();
817 break;
818 }
819
820 // Read name after KSLABEL identifier
821 // Because some object names have [] within them, we have to be careful
822 // [...] names are used by SIMBAD and NED to specify paper authors
823 // Unbalanced [,] are not allowed in the object name, but are allowed in the notes
824 int nameEndIndex = startIndex, openBracketCount = 1;
825 while (openBracketCount > 0 && nameEndIndex < endIndex)
826 {
827 if (buffer[nameEndIndex] == ']')
828 --openBracketCount;
829 else if (buffer[nameEndIndex] == '[')
830 ++openBracketCount;
831 ++nameEndIndex;
832 }
833 if (openBracketCount > 0)
834 {
835 malformatError();
836 break;
837 }
838 name = buffer.mid(startIndex, (nameEndIndex - 1) - startIndex);
839
840 // Read data and skip new line
841 if (buffer[nameEndIndex] == '\n')
842 ++nameEndIndex;
843 data = buffer.mid(nameEndIndex, endIndex - nameEndIndex);
844 buffer = buffer.mid(endIndex + logEnd.size() + 1);
845 currentEntryIndex += (endIndex + logEnd.size() + 1 - startIndex);
846
847 auto &data_element = m_user_data[name];
848 data_element.userLog = data;
849
850 } // end while
851 file.close();
852 return true;
853}
854
855bool KStarsData::readADVTreeData()
856{
857 QFile file;
858 QString Interface;
859 QString Name, Link, subName;
860
861 if (!KSUtils::openDataFile(file, "advinterface.dat"))
862 return false;
863
864 QTextStream stream(&file);
865 QString Line;
866
867 while (!stream.atEnd())
868 {
869 int Type, interfaceIndex;
870
871 Line = stream.readLine();
872
873 if (Line.startsWith(QLatin1String("[KSLABEL]")))
874 {
875 Name = Line.mid(9);
876 Type = 0;
877 }
878 else if (Line.startsWith(QLatin1String("[END]")))
879 Type = 1;
880 else if (Line.startsWith(QLatin1String("[KSINTERFACE]")))
881 {
882 Interface = Line.mid(13);
883 continue;
884 }
885
886 else
887 {
888 int idx = Line.indexOf(':');
889 Name = Line.left(idx);
890 Link = Line.mid(idx + 1);
891
892 // Link is empty, using Interface instead
893 if (Link.isEmpty())
894 {
895 Link = Interface;
896 subName = Name;
897 interfaceIndex = Link.indexOf(QLatin1String("KSINTERFACE"));
898 Link.remove(interfaceIndex, 11);
899 Link = Link.insert(interfaceIndex, subName.replace(' ', '+'));
900 }
901
902 Type = 2;
903 }
904
905 ADVTreeData *ADVData = new ADVTreeData;
906
907 ADVData->Name = Name;
908 ADVData->Link = Link;
909 ADVData->Type = Type;
910
911 ADVtreeList.append(ADVData);
912 }
913
914 return true;
915}
916
917//There's a lot of code duplication here, but it's not avoidable because
918//this function is only called from main.cpp when the user is using
919//"dump" mode to produce an image from the command line. In this mode,
920//there is no KStars object, so none of the DBus functions can be called
921//directly.
922bool KStarsData::executeScript(const QString &scriptname, SkyMap *map)
923{
924#ifndef KSTARS_LITE
925 int cmdCount(0);
926
927 QFile f(scriptname);
928 if (!f.open(QIODevice::ReadOnly))
929 {
930 qDebug() << Q_FUNC_INFO << "Could not open file " << f.fileName();
931 return false;
932 }
933
934 QTextStream istream(&f);
935 while (!istream.atEnd())
936 {
937 QString line = istream.readLine();
938 line.remove("string:");
939 line.remove("int32:");
940 line.remove("double:");
941 line.remove("bool:");
942
943 //find a dbus line and extract the function name and its arguments
944 //The function name starts after the last occurrence of "org.kde.kstars."
945 //or perhaps "org.kde.kstars.SimClock.".
946 if (line.startsWith(QString("dbus-send")))
947 {
948 QString funcprefix = "org.kde.kstars.SimClock.";
949 int i = line.lastIndexOf(funcprefix);
950 if (i >= 0)
951 {
952 i += funcprefix.length();
953 }
954 else
955 {
956 funcprefix = "org.kde.kstars.";
957 i = line.lastIndexOf(funcprefix);
958 if (i >= 0)
959 {
960 i += funcprefix.length();
961 }
962 }
963 if (i < 0)
964 {
965 qWarning() << "Could not parse line: " << line;
966 return false;
967 }
968
969 QStringList fn = line.mid(i).split(' ');
970
971 //DEBUG
972 //qDebug() << Q_FUNC_INFO << fn;
973
974 if (fn[0] == "lookTowards" && fn.size() >= 2)
975 {
976 double az(-1.0);
977 QString arg = fn[1].toLower();
978 if (arg == "n" || arg == "north")
979 az = 0.0;
980 if (arg == "ne" || arg == "northeast")
981 az = 45.0;
982 if (arg == "e" || arg == "east")
983 az = 90.0;
984 if (arg == "se" || arg == "southeast")
985 az = 135.0;
986 if (arg == "s" || arg == "south")
987 az = 180.0;
988 if (arg == "sw" || arg == "southwest")
989 az = 225.0;
990 if (arg == "w" || arg == "west")
991 az = 270.0;
992 if (arg == "nw" || arg == "northwest")
993 az = 335.0;
994 if (az >= 0.0)
995 {
996 // N.B. unrefract() doesn't matter at 90 degrees
997 map->setFocusAltAz(dms(90.0), map->focus()->az());
998 map->focus()->HorizontalToEquatorial(&LST, geo()->lat());
999 map->setDestination(*map->focus());
1000 cmdCount++;
1001 }
1002
1003 if (arg == "z" || arg == "zenith")
1004 {
1005 // N.B. unrefract() doesn't matter at 90 degrees
1006 map->setFocusAltAz(dms(90.0), map->focus()->az());
1007 map->focus()->HorizontalToEquatorial(&LST, geo()->lat());
1008 map->setDestination(*map->focus());
1009 cmdCount++;
1010 }
1011
1012 //try a named object. The name is everything after fn[0],
1013 //concatenated with spaces.
1014 fn.removeAll(fn.first());
1015 QString objname = fn.join(" ");
1016 SkyObject *target = objectNamed(objname);
1017 if (target)
1018 {
1019 map->setFocus(target);
1020 map->focus()->EquatorialToHorizontal(&LST, geo()->lat());
1021 map->setDestination(*map->focus());
1022 cmdCount++;
1023 }
1024 }
1025 else if (fn[0] == "setRaDec" && fn.size() == 3)
1026 {
1027 bool ok(false);
1028 dms r(0.0), d(0.0);
1029
1030 ok = r.setFromString(fn[1], false); //assume angle in hours
1031 if (ok)
1032 ok = d.setFromString(fn[2], true); //assume angle in degrees
1033 if (ok)
1034 {
1035 map->setFocus(r, d);
1036 map->focus()->EquatorialToHorizontal(&LST, geo()->lat());
1037 cmdCount++;
1038 }
1039 }
1040 else if (fn[0] == "setAltAz" && fn.size() == 3)
1041 {
1042 bool ok(false);
1043 dms az(0.0), alt(0.0);
1044
1045 ok = alt.setFromString(fn[1]);
1046 if (ok)
1047 ok = az.setFromString(fn[2]);
1048 if (ok)
1049 {
1050 map->setFocusAltAz(alt, az);
1051 map->focus()->HorizontalToEquatorial(&LST, geo()->lat());
1052 cmdCount++;
1053 }
1054 }
1055 else if (fn[0] == "loadColorScheme")
1056 {
1057 fn.removeAll(fn.first());
1058 QString csName = fn.join(" ").remove('\"');
1059 qCDebug(KSTARS) << "Loading Color scheme: " << csName;
1060
1061 QString filename = csName.toLower().trimmed();
1062 bool ok(false);
1063
1064 //Parse default names which don't follow the regular file-naming scheme
1065 if (csName == i18nc("use default color scheme", "Default Colors"))
1066 filename = "classic.colors";
1067 if (csName == i18nc("use 'star chart' color scheme", "Star Chart"))
1068 filename = "chart.colors";
1069 if (csName == i18nc("use 'night vision' color scheme", "Night Vision"))
1070 filename = "night.colors";
1071
1072 //Try the filename if it ends with ".colors"
1073 if (filename.endsWith(QLatin1String(".colors")))
1074 ok = colorScheme()->load(filename);
1075
1076 //If that didn't work, try assuming that 'name' is the color scheme name
1077 //convert it to a filename exactly as ColorScheme::save() does
1078 if (!ok)
1079 {
1080 if (!filename.isEmpty())
1081 {
1082 for (int i = 0; i < filename.length(); ++i)
1083 if (filename.at(i) == ' ')
1084 filename.replace(i, 1, "-");
1085
1086 filename = filename.append(".colors");
1087 ok = colorScheme()->load(filename);
1088 }
1089
1090 if (!ok)
1091 qDebug() << Q_FUNC_INFO << QString("Unable to load color scheme named %1. Also tried %2.")
1092 .arg(csName, filename);
1093 }
1094 }
1095 else if (fn[0] == "zoom" && fn.size() == 2)
1096 {
1097 bool ok(false);
1098 double z = fn[1].toDouble(&ok);
1099 if (ok)
1100 {
1101 if (z > MAXZOOM)
1102 z = MAXZOOM;
1103 if (z < MINZOOM)
1104 z = MINZOOM;
1105 Options::setZoomFactor(z);
1106 cmdCount++;
1107 }
1108 }
1109 else if (fn[0] == "zoomIn")
1110 {
1111 if (Options::zoomFactor() < MAXZOOM)
1112 {
1113 Options::setZoomFactor(Options::zoomFactor() * DZOOM);
1114 cmdCount++;
1115 }
1116 }
1117 else if (fn[0] == "zoomOut")
1118 {
1119 if (Options::zoomFactor() > MINZOOM)
1120 {
1121 Options::setZoomFactor(Options::zoomFactor() / DZOOM);
1122 cmdCount++;
1123 }
1124 }
1125 else if (fn[0] == "defaultZoom")
1126 {
1127 Options::setZoomFactor(DEFAULTZOOM);
1128 cmdCount++;
1129 }
1130 else if (fn[0] == "setLocalTime" && fn.size() == 7)
1131 {
1132 bool ok(false);
1133 // min is a macro - use mnt
1134 int yr(0), mth(0), day(0), hr(0), mnt(0), sec(0);
1135 yr = fn[1].toInt(&ok);
1136 if (ok)
1137 mth = fn[2].toInt(&ok);
1138 if (ok)
1139 day = fn[3].toInt(&ok);
1140 if (ok)
1141 hr = fn[4].toInt(&ok);
1142 if (ok)
1143 mnt = fn[5].toInt(&ok);
1144 if (ok)
1145 sec = fn[6].toInt(&ok);
1146 if (ok)
1147 {
1148 changeDateTime(geo()->LTtoUT(KStarsDateTime(QDate(yr, mth, day), QTime(hr, mnt, sec))));
1149 cmdCount++;
1150 }
1151 else
1152 {
1153 qWarning() << ki18n("Could not set time: %1 / %2 / %3 ; %4:%5:%6")
1154 .subs(day)
1155 .subs(mth)
1156 .subs(yr)
1157 .subs(hr)
1158 .subs(mnt)
1159 .subs(sec)
1160 .toString();
1161 }
1162 }
1163 else if (fn[0] == "changeViewOption" && fn.size() == 3)
1164 {
1165 bool bOk(false), dOk(false);
1166
1167 //parse bool value
1168 bool bVal(false);
1169 if (fn[2].toLower() == "true")
1170 {
1171 bOk = true;
1172 bVal = true;
1173 }
1174 if (fn[2].toLower() == "false")
1175 {
1176 bOk = true;
1177 bVal = false;
1178 }
1179 if (fn[2] == "1")
1180 {
1181 bOk = true;
1182 bVal = true;
1183 }
1184 if (fn[2] == "0")
1185 {
1186 bOk = true;
1187 bVal = false;
1188 }
1189
1190 //parse double value
1191 double dVal = fn[2].toDouble(&dOk);
1192
1193 // FIXME: REGRESSION
1194 // if ( fn[1] == "FOVName" ) { Options::setFOVName( fn[2] ); cmdCount++; }
1195 // if ( fn[1] == "FOVSizeX" && dOk ) { Options::setFOVSizeX( (float)dVal ); cmdCount++; }
1196 // if ( fn[1] == "FOVSizeY" && dOk ) { Options::setFOVSizeY( (float)dVal ); cmdCount++; }
1197 // if ( fn[1] == "FOVShape" && nOk ) { Options::setFOVShape( nVal ); cmdCount++; }
1198 // if ( fn[1] == "FOVColor" ) { Options::setFOVColor( fn[2] ); cmdCount++; }
1199 if (fn[1] == "ShowStars" && bOk)
1200 {
1201 Options::setShowStars(bVal);
1202 cmdCount++;
1203 }
1204 if (fn[1] == "ShowCLines" && bOk)
1205 {
1206 Options::setShowCLines(bVal);
1207 cmdCount++;
1208 }
1209 if (fn[1] == "ShowCNames" && bOk)
1210 {
1211 Options::setShowCNames(bVal);
1212 cmdCount++;
1213 }
1214 if (fn[1] == "ShowMilkyWay" && bOk)
1215 {
1216 Options::setShowMilkyWay(bVal);
1217 cmdCount++;
1218 }
1219 if (fn[1] == "ShowEquatorialGrid" && bOk)
1220 {
1221 Options::setShowEquatorialGrid(bVal);
1222 cmdCount++;
1223 }
1224 if (fn[1] == "ShowHorizontalGrid" && bOk)
1225 {
1226 Options::setShowHorizontalGrid(bVal);
1227 cmdCount++;
1228 }
1229 if (fn[1] == "ShowEquator" && bOk)
1230 {
1231 Options::setShowEquator(bVal);
1232 cmdCount++;
1233 }
1234 if (fn[1] == "ShowEcliptic" && bOk)
1235 {
1236 Options::setShowEcliptic(bVal);
1237 cmdCount++;
1238 }
1239 if (fn[1] == "ShowHorizon" && bOk)
1240 {
1241 Options::setShowHorizon(bVal);
1242 cmdCount++;
1243 }
1244 if (fn[1] == "ShowGround" && bOk)
1245 {
1246 Options::setShowGround(bVal);
1247 cmdCount++;
1248 }
1249 if (fn[1] == "ShowSun" && bOk)
1250 {
1251 Options::setShowSun(bVal);
1252 cmdCount++;
1253 }
1254 if (fn[1] == "ShowMoon" && bOk)
1255 {
1256 Options::setShowMoon(bVal);
1257 cmdCount++;
1258 }
1259 if (fn[1] == "ShowMercury" && bOk)
1260 {
1261 Options::setShowMercury(bVal);
1262 cmdCount++;
1263 }
1264 if (fn[1] == "ShowVenus" && bOk)
1265 {
1266 Options::setShowVenus(bVal);
1267 cmdCount++;
1268 }
1269 if (fn[1] == "ShowMars" && bOk)
1270 {
1271 Options::setShowMars(bVal);
1272 cmdCount++;
1273 }
1274 if (fn[1] == "ShowJupiter" && bOk)
1275 {
1276 Options::setShowJupiter(bVal);
1277 cmdCount++;
1278 }
1279 if (fn[1] == "ShowSaturn" && bOk)
1280 {
1281 Options::setShowSaturn(bVal);
1282 cmdCount++;
1283 }
1284 if (fn[1] == "ShowUranus" && bOk)
1285 {
1286 Options::setShowUranus(bVal);
1287 cmdCount++;
1288 }
1289 if (fn[1] == "ShowNeptune" && bOk)
1290 {
1291 Options::setShowNeptune(bVal);
1292 cmdCount++;
1293 }
1294 //if ( fn[1] == "ShowPluto" && bOk ) { Options::setShowPluto( bVal ); cmdCount++; }
1295 if (fn[1] == "ShowAsteroids" && bOk)
1296 {
1297 Options::setShowAsteroids(bVal);
1298 cmdCount++;
1299 }
1300 if (fn[1] == "ShowComets" && bOk)
1301 {
1302 Options::setShowComets(bVal);
1303 cmdCount++;
1304 }
1305 if (fn[1] == "ShowSolarSystem" && bOk)
1306 {
1307 Options::setShowSolarSystem(bVal);
1308 cmdCount++;
1309 }
1310 if (fn[1] == "ShowDeepSky" && bOk)
1311 {
1312 Options::setShowDeepSky(bVal);
1313 cmdCount++;
1314 }
1315 if (fn[1] == "ShowSupernovae" && bOk)
1316 {
1317 Options::setShowSupernovae(bVal);
1318 cmdCount++;
1319 }
1320 if (fn[1] == "ShowStarNames" && bOk)
1321 {
1322 Options::setShowStarNames(bVal);
1323 cmdCount++;
1324 }
1325 if (fn[1] == "ShowStarMagnitudes" && bOk)
1326 {
1327 Options::setShowStarMagnitudes(bVal);
1328 cmdCount++;
1329 }
1330 if (fn[1] == "ShowAsteroidNames" && bOk)
1331 {
1332 Options::setShowAsteroidNames(bVal);
1333 cmdCount++;
1334 }
1335 if (fn[1] == "ShowCometNames" && bOk)
1336 {
1337 Options::setShowCometNames(bVal);
1338 cmdCount++;
1339 }
1340 if (fn[1] == "ShowPlanetNames" && bOk)
1341 {
1342 Options::setShowPlanetNames(bVal);
1343 cmdCount++;
1344 }
1345 if (fn[1] == "ShowPlanetImages" && bOk)
1346 {
1347 Options::setShowPlanetImages(bVal);
1348 cmdCount++;
1349 }
1350
1351 if (fn[1] == "UseAltAz" && bOk)
1352 {
1353 Options::setUseAltAz(bVal);
1354 cmdCount++;
1355 }
1356 if (fn[1] == "UseRefraction" && bOk)
1357 {
1358 Options::setUseRefraction(bVal);
1359 cmdCount++;
1360 }
1361 if (fn[1] == "UseAutoLabel" && bOk)
1362 {
1363 Options::setUseAutoLabel(bVal);
1364 cmdCount++;
1365 }
1366 if (fn[1] == "UseAutoTrail" && bOk)
1367 {
1368 Options::setUseAutoTrail(bVal);
1369 cmdCount++;
1370 }
1371 if (fn[1] == "UseAnimatedSlewing" && bOk)
1372 {
1373 Options::setUseAnimatedSlewing(bVal);
1374 cmdCount++;
1375 }
1376 if (fn[1] == "FadePlanetTrails" && bOk)
1377 {
1378 Options::setFadePlanetTrails(bVal);
1379 cmdCount++;
1380 }
1381 if (fn[1] == "SlewTimeScale" && dOk)
1382 {
1383 Options::setSlewTimeScale(dVal);
1384 cmdCount++;
1385 }
1386 if (fn[1] == "ZoomFactor" && dOk)
1387 {
1388 Options::setZoomFactor(dVal);
1389 cmdCount++;
1390 }
1391 // if ( fn[1] == "MagLimitDrawStar" && dOk ) { Options::setMagLimitDrawStar( dVal ); cmdCount++; }
1392 if (fn[1] == "StarDensity" && dOk)
1393 {
1394 Options::setStarDensity(dVal);
1395 cmdCount++;
1396 }
1397 // if ( fn[1] == "MagLimitDrawStarZoomOut" && dOk ) { Options::setMagLimitDrawStarZoomOut( dVal ); cmdCount++; }
1398 if (fn[1] == "MagLimitDrawDeepSky" && dOk)
1399 {
1400 Options::setMagLimitDrawDeepSky(dVal);
1401 cmdCount++;
1402 }
1403 if (fn[1] == "MagLimitDrawDeepSkyZoomOut" && dOk)
1404 {
1405 Options::setMagLimitDrawDeepSkyZoomOut(dVal);
1406 cmdCount++;
1407 }
1408 if (fn[1] == "StarLabelDensity" && dOk)
1409 {
1410 Options::setStarLabelDensity(dVal);
1411 cmdCount++;
1412 }
1413 if (fn[1] == "MagLimitHideStar" && dOk)
1414 {
1415 Options::setMagLimitHideStar(dVal);
1416 cmdCount++;
1417 }
1418 if (fn[1] == "MagLimitAsteroid" && dOk)
1419 {
1420 Options::setMagLimitAsteroid(dVal);
1421 cmdCount++;
1422 }
1423 if (fn[1] == "AsteroidLabelDensity" && dOk)
1424 {
1425 Options::setAsteroidLabelDensity(dVal);
1426 cmdCount++;
1427 }
1428 if (fn[1] == "MaxRadCometName" && dOk)
1429 {
1430 Options::setMaxRadCometName(dVal);
1431 cmdCount++;
1432 }
1433
1434 //these three are a "radio group"
1435 if (fn[1] == "UseLatinConstellationNames" && bOk)
1436 {
1437 Options::setUseLatinConstellNames(true);
1438 Options::setUseLocalConstellNames(false);
1439 Options::setUseAbbrevConstellNames(false);
1440 cmdCount++;
1441 }
1442 if (fn[1] == "UseLocalConstellationNames" && bOk)
1443 {
1444 Options::setUseLatinConstellNames(false);
1445 Options::setUseLocalConstellNames(true);
1446 Options::setUseAbbrevConstellNames(false);
1447 cmdCount++;
1448 }
1449 if (fn[1] == "UseAbbrevConstellationNames" && bOk)
1450 {
1451 Options::setUseLatinConstellNames(false);
1452 Options::setUseLocalConstellNames(false);
1453 Options::setUseAbbrevConstellNames(true);
1454 cmdCount++;
1455 }
1456 }
1457 else if (fn[0] == "setGeoLocation" && (fn.size() == 3 || fn.size() == 4))
1458 {
1459 QString city(fn[1]), province, country(fn[2]);
1460 province.clear();
1461 if (fn.size() == 4)
1462 {
1463 province = fn[2];
1464 country = fn[3];
1465 }
1466
1467 bool cityFound(false);
1468 foreach (GeoLocation *loc, geoList)
1469 {
1470 if (loc->translatedName() == city &&
1471 (province.isEmpty() || loc->translatedProvince() == province) &&
1472 loc->translatedCountry() == country)
1473 {
1474 cityFound = true;
1475 setLocation(*loc);
1476 cmdCount++;
1477 break;
1478 }
1479 }
1480
1481 if (!cityFound)
1482 qWarning() << i18n("Could not set location named %1, %2, %3", city, province, country);
1483 }
1484 }
1485 } //end while
1486
1487 if (cmdCount)
1488 return true;
1489#else
1490 Q_UNUSED(map)
1491 Q_UNUSED(scriptname)
1492#endif
1493 return false;
1494}
1495
1496#ifndef KSTARS_LITE
1498{
1499 visibleFOVs.clear();
1500 // Add visible FOVs
1501 foreach (FOV *fov, availFOVs)
1502 {
1503 if (Options::fOVNames().contains(fov->name()))
1504 visibleFOVs.append(fov);
1505 }
1506 // Remove unavailable FOVs
1507 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
1508 QSet<QString> names = QSet<QString>::fromList(Options::fOVNames());
1509 #else
1510 const QStringList m_fOVNames = Options::fOVNames();
1511 QSet<QString> names (m_fOVNames.begin(), m_fOVNames.end());
1512 #endif
1513 QSet<QString> all;
1514 foreach (FOV *fov, visibleFOVs)
1515 {
1516 all.insert(fov->name());
1517 }
1518 Options::setFOVNames(all.intersect(names).values());
1519}
1520
1521// FIXME: Why does KStarsData store the Execute instance??? -- asimha
1522Execute *KStarsData::executeSession()
1523{
1524 if (!m_Execute.get())
1525 m_Execute.reset(new Execute());
1526
1527 return m_Execute.get();
1528}
1529
1530// FIXME: Why does KStarsData store the ImageExporer instance??? KStarsData is supposed to work with no reference to KStars -- asimha
1531ImageExporter *KStarsData::imageExporter()
1532{
1533 if (!m_ImageExporter.get())
1534 m_ImageExporter.reset(new ImageExporter(KStars::Instance()));
1535
1536 return m_ImageExporter.get();
1537}
1538#endif
1539
1540std::pair<bool, QString>
1542{
1543 QMutexLocker _{ &m_user_data_mutex };
1544
1545 findUserData(name).links[data.type].push_back(data);
1546
1547 QString entry;
1548 QFile file;
1549 const auto isImage = data.type == SkyObjectUserdata::Type::image;
1550
1551 //Also, update the user's custom image links database
1552 //check for user's image-links database. If it doesn't exist, create it.
1553 file.setFileName(
1554 KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) +
1555 (isImage ?
1556 "image_url.dat" :
1557 "info_url.dat")); //determine filename in local user KDE directory tree.
1558
1560 return { false,
1561 isImage ?
1562 i18n("Custom image-links file could not be opened.\nLink cannot "
1563 "be recorded for future sessions.") :
1564 i18n("Custom information-links file could not be opened.\nLink "
1565 "cannot be recorded for future sessions.") };
1566 else
1567 {
1568 entry = name + ':' + data.title + ':' + data.url.toString();
1569 QTextStream stream(&file);
1570 stream << entry << '\n';
1571 file.close();
1572 }
1573
1574 return { true, {} };
1575}
1576
1577std::pair<bool, QString> updateLocalDatabase(SkyObjectUserdata::Type type,
1578 const QString &search_line,
1579 const QString &replace_line)
1580{
1581 QString TempFileName, file_line;
1582 QFile URLFile;
1583 QTemporaryFile TempFile;
1584 TempFile.setAutoRemove(false);
1585 TempFile.open();
1586
1587 bool replace = !replace_line.isEmpty();
1588
1589 if (search_line.isEmpty())
1590 return { false, "Invalid update request." };
1591
1592 TempFileName = TempFile.fileName();
1593
1594 switch (type)
1595 {
1596 // Info Links
1597 case SkyObjectUserdata::Type::website:
1598 // Get name for our local info_url file
1599 URLFile.setFileName(
1600 KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) +
1601 "info_url.dat");
1602 break;
1603
1604 // Image Links
1605 case SkyObjectUserdata::Type::image:
1606 // Get name for our local info_url file
1607 URLFile.setFileName(
1608 KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) +
1609 "image_url.dat");
1610 break;
1611 }
1612
1613 // Copy URL file to temp file
1615 QUrl::fromLocalFile(TempFileName), -1,
1617
1618 if (!URLFile.open(QIODevice::WriteOnly))
1619 {
1620 return { false, "Failed to open " + URLFile.fileName() +
1621 "KStars cannot save to user database" };
1622 }
1623
1624 // Get streams;
1625 QTextStream temp_stream(&TempFile);
1626 QTextStream out_stream(&URLFile);
1627
1628 bool found = false;
1629 while (!temp_stream.atEnd())
1630 {
1631 file_line = temp_stream.readLine();
1632 // If we find a match, either replace, or remove (by skipping).
1633 if (file_line == search_line)
1634 {
1635 found = true;
1636 if (replace)
1637 (out_stream) << replace_line << '\n';
1638 else
1639 continue;
1640 }
1641 else
1642 (out_stream) << file_line << '\n';
1643 }
1644
1645 // just append it if we haven't found it.
1646 if (!found && replace)
1647 {
1648 out_stream << replace_line << '\n';
1649 }
1650
1651 URLFile.close();
1652
1653 return { true, {} };
1654}
1655
1656std::pair<bool, QString> KStarsData::editUserData(const QString &name,
1657 const unsigned int index,
1658 const SkyObjectUserdata::LinkData &data)
1659{
1660 QMutexLocker _{ &m_user_data_mutex };
1661
1662 auto &entry = findUserData(name);
1663 if (index >= entry.links[data.type].size())
1664 return { false, i18n("Userdata at index %1 does not exist.", index) };
1665
1666 entry.links[data.type][index] = data;
1667
1668 QString search_line = name;
1669 search_line += ':';
1670 search_line += data.title;
1671 search_line += ':';
1672 search_line += data.url.toString();
1673
1674 QString replace_line = name + ':' + data.title + ':' + data.url.toString();
1675 return updateLocalDatabase(data.type, search_line, replace_line);
1676}
1677
1678std::pair<bool, QString> KStarsData::deleteUserData(const QString &name,
1679 const unsigned int index,
1680 SkyObjectUserdata::Type type)
1681{
1682 QMutexLocker _{ &m_user_data_mutex };
1683
1684 auto &linkList = findUserData(name).links[type];
1685 if (index >= linkList.size())
1686 return { false, i18n("Userdata at index %1 does not exist.", index) };
1687
1688 const auto data = linkList[index];
1689 linkList.erase(linkList.begin() + index);
1690
1691 QString search_line = name;
1692 search_line += ':';
1693 search_line += data.title;
1694 search_line += ':';
1695 search_line += data.url.toString();
1696
1697 QString replace_line = name + ':' + data.title + ':' + data.url.toString();
1698 return updateLocalDatabase(data.type, search_line, "");
1699}
1700
1701std::pair<bool, QString> KStarsData::updateUserLog(const QString &name,
1702 const QString &newLog)
1703{
1704 QMutexLocker _{ &m_user_data_mutex };
1705
1706 QFile file;
1707 QString logs; //existing logs
1708
1709 //Do nothing if:
1710 //+ new log is the "default" message
1711 //+ new log is empty
1712 if (newLog == (i18n("Record here observation logs and/or data on %1.", name)) ||
1713 newLog.isEmpty())
1714 return { true, {} };
1715
1716 // header label
1717 QString KSLabel = "[KSLABEL:" + name + ']';
1718
1719 file.setFileName(
1720 QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(
1721 "userlog.dat")); //determine filename in local user KDE directory tree.
1722
1723 if (file.open(QIODevice::ReadOnly))
1724 {
1725 QTextStream instream(&file);
1726 // read all data into memory
1727 logs = instream.readAll();
1728 file.close();
1729 }
1730
1731 const auto &userLog = m_user_data[name].userLog;
1732
1733 // Remove old log entry from the logs text
1734 if (!userLog.isEmpty())
1735 {
1736 int startIndex, endIndex;
1737 QString sub;
1738
1739 startIndex = logs.indexOf(KSLabel);
1740 sub = logs.mid(startIndex);
1741 endIndex = sub.indexOf("[KSLogEnd]");
1742
1743 logs.remove(startIndex, endIndex + 11);
1744 }
1745
1746 //append the new log entry to the end of the logs text
1747 logs.append(KSLabel + '\n' + newLog.trimmed() + "\n[KSLogEnd]\n");
1748
1749 //Open file for writing
1750 if (!file.open(QIODevice::WriteOnly))
1751 {
1752 return { false, "Cannot write to user log file" };
1753 }
1754
1755 //Write new logs text
1756 QTextStream outstream(&file);
1757 outstream << logs;
1758
1759 file.close();
1760
1761 findUserData(name).userLog = newLog;
1762 return { true, {} };
1763};
1764
1766{
1767 QMutexLocker _{ &m_user_data_mutex };
1768
1769 return findUserData(name); // we're consting it
1770}
1771
1772SkyObjectUserdata::Data &KStarsData::findUserData(const QString &name)
1773{
1774 auto element = m_user_data.find(name);
1775 if (element != m_user_data.end())
1776 {
1777 return element->second;
1778 }
1779
1780 // fallback: we did not find it directly, therefore we may try to
1781 // find a matching object
1782 const auto *object = m_SkyComposite->findByName(name);
1783 if (object != nullptr)
1784 {
1785 return m_user_data[object->name()];
1786 }
1787
1788 return m_user_data[name];
1789};
bool load(const QString &filename)
Load a color scheme from a *.colors file filename the filename of the color scheme to be loaded.
Executes an observation session.
Definition execute.h:25
A simple class encapsulating a Field-of-View symbol.
Definition fov.h:28
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
QString country() const
const CachingDms * lat() const
Definition geolocation.h:70
const CachingDms * lng() const
Definition geolocation.h:64
QString translatedCountry() const
double elevation() const
Definition geolocation.h:76
void setLat(const dms &l)
Set latitude according to dms argument.
double TZ0() const
QString province() const
QString translatedName() const
QString translatedProvince() const
TimeZoneRule * tzrule()
QString name() const
Backends for exporting a sky image, either raster or vector, with a legend.
QString toString() const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
There are several time-dependent values used in position calculations, that are not specific to an ob...
Definition ksnumbers.h:43
bool Initialize()
Initialize KStarsDB while running splash screen.
Definition ksuserdb.cpp:43
KStarsData is the backbone of KStars.
Definition kstarsdata.h:72
void setNextDSTChange(const KStarsDateTime &dt)
Set the NextDSTChange member.
Definition kstarsdata.h:110
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
std::pair< bool, QString > deleteUserData(const QString &name, const unsigned int index, SkyObjectUserdata::Type type)
Remove data of type from the user data at index for the object with name, both in memory and on disk.
void changeDateTime(const KStarsDateTime &newDate)
Change the current simulation date/time to the KStarsDateTime argument.
~KStarsData() override
Destructor.
std::pair< bool, QString > editUserData(const QString &name, const unsigned int index, const SkyObjectUserdata::LinkData &data)
Replace data in the user data at index for the object with name, both in memory and on disk.
void setFullTimeUpdate()
The Sky is updated more frequently than the moon, which is updated more frequently than the planets.
void updateTime(GeoLocation *geo, const bool automaticDSTchange=true)
Update the Simulation Clock.
bool initialize()
Initialize KStarsData while running splash screen.
SkyObject * objectNamed(const QString &name)
Find object by name.
GeoLocation * nearestLocation(double longitude, double latitude)
nearestLocation Return nearest location to the given longitude and latitude coordinates
void setTimeDirection(float scale)
Sets the direction of time and stores it in bool TimeRunForwards.
std::pair< bool, QString > updateUserLog(const QString &name, const QString &newLog)
Update the user log of the object with the name to contain newLog (find and replace).
ColorScheme * colorScheme()
Definition kstarsdata.h:172
const SkyObjectUserdata::Data & getUserData(const QString &name)
Get a reference to the user data of an object with the name name.
void skyUpdate(bool)
Should be used to refresh skymap.
void syncLST()
Sync the LST with the simulation clock.
const KStarsDateTime & ut() const
Definition kstarsdata.h:157
void syncFOV()
Synchronize list of visible FOVs and list of selected FOVs in Options.
bool executeScript(const QString &name, SkyMap *map)
Execute a script.
void progressText(const QString &text)
Signal that specifies the text that should be drawn in the KStarsSplash window.
bool isTimeRunningForward() const
Returns true if time is running forward else false.
Definition kstarsdata.h:119
Q_INVOKABLE SimClock * clock()
Definition kstarsdata.h:218
GeoLocation * geo()
Definition kstarsdata.h:230
KStarsData()
Constructor.
void geoChanged()
Emitted when geo location changed.
void setLocationFromOptions()
Set the GeoLocation according to the values stored in the configuration file.
SkyMapComposite * skyComposite()
Definition kstarsdata.h:166
void setSnapNextFocus(bool b=true)
Disable or re-enable the slewing animation for the next Focus change.
Definition kstarsdata.h:283
std::pair< bool, QString > addToUserData(const QString &name, const SkyObjectUserdata::LinkData &data)
Adds a link data to the user data for the object with name, both in memory and on disk.
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
long double djd() const
static KStars * Instance()
Definition kstars.h:121
static bool Closing
Set to true when the application is being closed.
Definition kstars.h:854
Tool window for managing a custom list of objects.
Q_SCRIPTABLE Q_NOREPLY void setUTC(const KStarsDateTime &newtime)
DBUS function to set the time of the SimClock.
Definition simclock.cpp:181
SkyMapComposite is the root object in the object hierarchy of the sky map.
SkyObject * findByName(const QString &name, bool exact=true) override
Search the children of this SkyMapComposite for a SkyObject whose name matches the argument.
void updateSolarSystemBodies(KSNumbers *num) override
Delegate planet position updates to the SolarSystemComposite.
void update(KSNumbers *num=nullptr) override
Delegate update-position requests to all sub components.
void updateMoons(KSNumbers *num) override
Delegate moon position updates to the SolarSystemComposite.
This is the canvas on which the sky is painted.
Definition skymap.h:54
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:42
This class provides the information needed to determine whether Daylight Savings Time (DST; a....
KStarsDateTime nextDSTChange() const
bool isEmptyRule() const
bool equals(TimeZoneRule *r)
void reset_with_ltime(KStarsDateTime &ltime, const double TZoffset, const bool time_runs_forward, const bool automaticDSTchange=false)
Recalculate next dst change and if DST is active by a given local time with timezone offset and time ...
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
virtual bool setFromString(const QString &s, bool isDeg=true)
Attempt to parse the string argument as a dms value, and set the dms object accordingly.
Definition dms.cpp:48
const double & Degrees() const
Definition dms.h:141
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
QString name(GameStandardAction id)
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
HideProgressInfo
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)
QStringView country(QStringView ifopt)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QString filePath(const QString &fileName) const const
bool exists() const const
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void setFileName(const QString &name)
virtual void close() override
virtual bool reset()
void append(QList< T > &&value)
iterator begin()
void clear()
iterator end()
T & first()
qsizetype removeAll(const AT &t)
void removeAt(qsizetype i)
void replace(qsizetype i, parameter_type value)
qsizetype size() const const
QString languageToString(Language language)
QList< Key > keys() const const
iterator insert(const T &value)
QSet< T > & intersect(const QSet< T > &other)
QSqlDatabase addDatabase(QSqlDriver *driver, const QString &connectionName)
QSqlError lastError() const const
QSqlRecord record(const QString &tablename) const const
void setDatabaseName(const QString &name)
QStringList tables(QSql::TableType type) const const
QString text() const const
bool contains(const QString &name) const const
QStringList standardLocations(StandardLocation type)
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
void clear()
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
iterator erase(const_iterator first, const_iterator last)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QString join(QChar separator) const const
bool isEmpty() const const
QString toString() const const
CaseInsensitive
SkipEmptyParts
QTextStream & left(QTextStream &stream)
QFuture< T > run(Function function,...)
virtual QString fileName() const const override
void setAutoRemove(bool b)
bool atEnd() const const
QString readAll()
QString readLine(qint64 maxlen)
QUrl fromLocalFile(const QString &localFile)
QString toString(FormattingOptions options) const const
Stores Users' Logs, Pictures and Websites regarding an object in the sky.
Stores the tite and URL of a webpage.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.