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

KDE's Doxygen guidelines are available online.