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

KDE's Doxygen guidelines are available online.