Kstars

kstarsdata.cpp
1 /*
2  SPDX-FileCopyrightText: 2001 Heiko Evermann <[email protected]>
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 
41 namespace
42 {
43 // Report fatal error during data loading to user
44 // Calls QApplication::exit
45 void fatalErrorMessage(QString fname)
46 {
47  qCCritical(KSTARS) << i18n("Critical File not Found: %1", fname);
48  KSNotification::sorry(i18n("The file %1 could not be found. "
49  "KStars cannot run properly without this file. "
50  "KStars searches for this file in following locations:\n\n\t"
51  "%2\n\n"
52  "It appears that your setup is broken.",
54  i18n("Critical File Not Found: %1", fname)); // FIXME: Must list locations depending on file type
55 
56  qApp->exit(1);
57 }
58 
59 // Report non-fatal error during data loading to user and ask
60 // whether he wants to continue.
61 //
62 // No remaining calls so commented out to suppress unused warning
63 //
64 // Calls QApplication::exit if he don't
65 //bool nonFatalErrorMessage(QString fname)
66 //{
67 // qCWarning(KSTARS) << i18n( "Non-Critical File Not Found: %1", fname );
68 //#ifdef KSTARS_LITE
69 // Q_UNUSED(fname);
70 // return true;
71 //#else
72 // int res = KMessageBox::warningContinueCancel(nullptr,
73 // i18n("The file %1 could not be found. "
74 // "KStars can still run without this file. "
75 // "KStars search for this file in following locations:\n\n\t"
76 // "%2\n\n"
77 // "It appears that you setup is broken. Press Continue to run KStars without this file ",
78 // fname, QStandardPaths::standardLocations( QStandardPaths::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 
87 KStarsData *KStarsData::pinstance = nullptr;
88 
89 KStarsData *KStarsData::Create()
90 {
91  // This method should never be called twice within a run, since a
92  // lot of the code assumes that KStarsData, once created, is never
93  // destroyed. They maintain local copies of KStarsData::Instance()
94  // for efficiency (maybe this should change, but it is not
95  // required to delete and reinstantiate KStarsData). Thus, when we
96  // call this method, pinstance MUST be zero, i.e. this must be the
97  // first (and last) time we are calling it. -- asimha
98  Q_ASSERT(!pinstance);
99 
100  delete pinstance;
101  pinstance = new KStarsData();
102  return pinstance;
103 }
104 
106  : m_Geo(dms(0), dms(0)), m_ksuserdb(),
107  temporaryTrail(false),
108  //locale( new KLocale( "kstars" ) ),
109  m_preUpdateID(0), m_updateID(0), m_preUpdateNumID(0), m_updateNumID(0), m_preUpdateNum(J2000), m_updateNum(J2000)
110 {
111 #ifndef KSTARS_LITE
112  m_LogObject.reset(new OAL::Log);
113 #endif
114  // at startup times run forward
115  setTimeDirection(0.0);
116 }
117 
119 {
120  Q_ASSERT(pinstance);
121 
122  //delete locale;
123  qDeleteAll(geoList);
124  geoList.clear();
125  qDeleteAll(ADVtreeList);
126  ADVtreeList.clear();
127 
128  pinstance = nullptr;
129 }
130 
132 {
133  //Load Time Zone Rules//
134  emit progressText(i18n("Reading time zone rules"));
135  if (!readTimeZoneRulebook())
136  {
137  fatalErrorMessage("TZrules.dat");
138  return false;
139  }
140 
141  emit progressText(
142  i18n("Upgrade existing user city db to support geographic elevation."));
143 
144  QString dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mycitydb.sqlite");
145 
146  /// This code to add Height column to table city in mycitydb.sqlite is a transitional measure to support a meaningful
147  /// geographic elevation.
148  if (QFile::exists(dbfile))
149  {
150  QSqlDatabase fixcitydb = QSqlDatabase::addDatabase("QSQLITE", "fixcitydb");
151 
152  fixcitydb.setDatabaseName(dbfile);
153  fixcitydb.open();
154 
155  if (fixcitydb.tables().contains("city", Qt::CaseInsensitive))
156  {
157  QSqlRecord r = fixcitydb.record("city");
158  if (!r.contains("Elevation"))
159  {
160  emit progressText(i18n("Adding \"Elevation\" column to city table."));
161 
162  QSqlQuery query(fixcitydb);
163  if (query.exec(
164  "alter table city add column Elevation real default -10;") ==
165  false)
166  {
167  emit progressText(QString("failed to add Elevation column to city "
168  "table in mycitydb.sqlite: &1")
169  .arg(query.lastError().text()));
170  }
171  }
172  else
173  {
174  emit progressText(i18n("City table already contains \"Elevation\"."));
175  }
176  }
177  else
178  {
179  emit progressText(i18n("City table missing from database."));
180  }
181  fixcitydb.close();
182  }
183 
184  //Load Cities//
185  emit progressText(i18n("Loading city data"));
186  if (!readCityData())
187  {
188  fatalErrorMessage("citydb.sqlite");
189  return false;
190  }
191 
192  //Initialize User Database//
193  emit progressText(i18n("Loading User Information"));
194  m_ksuserdb.Initialize();
195 
196  //Initialize SkyMapComposite//
197  emit progressText(i18n("Loading sky objects"));
198  m_SkyComposite.reset(new SkyMapComposite());
199  //Load Image URLs//
200  //#ifndef Q_OS_ANDROID
201  //On Android these 2 calls produce segfault. WARNING
202  emit progressText(i18n("Loading Image URLs"));
203 
204  // if (!readURLData("image_url.dat", SkyObjectUserdata::Type::image) &&
205  // !nonFatalErrorMessage("image_url.dat"))
206  // return false;
207  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 
234 void KStarsData::updateTime(GeoLocation *geo, const bool automaticDSTchange)
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 
296 void 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 
305 unsigned 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 
348 void 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 
364 GeoLocation *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 
377 GeoLocation *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;
389  nearest = oneCity;
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 
426  emit geoChanged();
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 
436 bool 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 
447  QSqlQuery get_query(citydb);
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 
479  if (QFile::exists(dbfile))
480  {
481  mycitydb.setDatabaseName(dbfile);
482  if (mycitydb.open())
483  {
484  QSqlQuery get_query(mycitydb);
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 
512 bool 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 
543 bool KStarsData::openUrlFile(const QString &urlfile, QFile &file)
544 {
545  //QFile file;
546  QString localFile;
547  bool fileFound = false;
548  QFile localeFile;
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);
568  QStringList urlData;
569  QTextStream lStream(&file);
570  while (!lStream.atEnd())
571  urlData.append(lStream.readLine());
572 
573  //Find global file(s) in findAllResources() list.
574  QFileInfo fi_local(file.fileName());
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  {
582  QFileInfo fi_global(flist[i]);
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 ":"
590  QFile globalFile(flist[i]);
591  if (globalFile.open(QIODevice::ReadOnly))
592  {
593  QTextStream gStream(&globalFile);
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));
660  if (localeFile.open(QIODevice::WriteOnly))
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 
686 bool 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.
760 bool KStarsData::readUserLog()
761 {
762  QFile file;
763  QString fullContents;
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. ",
797  file.fileName(), QString::number(currentEntryIndex)),
798  i18n( "Malformed file %1", file.fileName() )
799  );
800  if( res != KMessageBox::Continue )
801  qApp->exit(1); // FIXME: Why does this not work?
802  };
803 
804  if (endIndex < 0)
805  {
806  malformatError();
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] == ']')
818  --openBracketCount;
819  else if (buffer[nameEndIndex] == '[')
820  ++openBracketCount;
821  ++nameEndIndex;
822  }
823  if (openBracketCount > 0)
824  {
825  malformatError();
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 
845 bool 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.
912 bool KStarsData::executeScript(const QString &scriptname, SkyMap *map)
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 
924  QTextStream istream(&f);
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)
1481  Q_UNUSED(scriptname)
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
1503  QSet<QString> all;
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
1512 Execute *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
1521 ImageExporter *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 
1530 std::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 
1567 std::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;
1573  QTemporaryFile TempFile;
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
1605  QUrl::fromLocalFile(TempFileName), -1,
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;
1615  QTextStream temp_stream(&TempFile);
1616  QTextStream out_stream(&URLFile);
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 
1646 std::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 
1668 std::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 
1691 std::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 
1762 SkyObjectUserdata::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 };
void append(const T &value)
T & first()
Definition: fov.h:27
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
QString readAll()
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QFuture< T > run(Function function,...)
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
Definition: kstarsdata.cpp:403
long double djd() const
QString number(int n, int base)
void updateTime(GeoLocation *geo, const bool automaticDSTchange=true)
Update the Simulation Clock.
Definition: kstarsdata.cpp:234
CaseInsensitive
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void setLocationFromOptions()
Set the GeoLocation according to the values stored in the configuration file.
Definition: kstarsdata.cpp:396
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.
int removeAll(const T &value)
Type type(const QSqlDatabase &db)
virtual bool open(QIODevice::OpenMode mode) override
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void setTimeDirection(float scale)
Sets the direction of time and stores it in bool TimeRunForwards.
Definition: kstarsdata.cpp:359
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
double TZ0() const
Definition: geolocation.h:136
QString trimmed() const const
void clear()
void setAutoRemove(bool b)
Backends for exporting a sky image, either raster or vector, with a legend.
Definition: imageexporter.h:23
const CachingDms * lng() const
Definition: geolocation.h:64
SkyObject * objectNamed(const QString &name)
Find object by name.
Definition: kstarsdata.cpp:429
QSet< T > fromList(const QList< T > &list)
TimeZoneRule * tzrule()
Definition: geolocation.h:150
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.
QString toString() const
void setFullTimeUpdate()
The Sky is updated more frequently than the moon, which is updated more frequently than the planets.
Definition: kstarsdata.cpp:313
const SkyObjectUserdata::Data & getUserData(const QString &name)
Get a reference to the user data of an object with the name name.
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void replace(int i, const T &value)
bool exists() const const
void progressText(const QString &text)
Signal that specifies the text that should be drawn in the KStarsSplash window.
Stores Users' Logs, Pictures and Websites regarding an object in the sky.
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
virtual QString fileName() const const override
bool Initialize()
Initialize KStarsDB while running splash screen.
Definition: ksuserdb.cpp:43
void removeAt(int i)
virtual QString fileName() const const override
bool load(const QString &filename)
Load a color scheme from a *.colors file filename the filename of the color scheme to be loaded.
QString country() const
Definition: geolocation.h:124
static KStars * Instance()
Definition: kstars.h:121
void updateMoons(KSNumbers *num) override
Delegate moon position updates to the SolarSystemComposite.
QString languageToString(QLocale::Language language)
int size() const const
QSqlRecord record(const QString &tablename) const const
QStringList standardLocations(QStandardPaths::StandardLocation type)
KStarsData()
Constructor.
Definition: kstarsdata.cpp:105
bool initialize()
Initialize KStarsData while running splash screen.
Definition: kstarsdata.cpp:131
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
QString toString(QUrl::FormattingOptions options) const const
void geoChanged()
Emitted when geo location changed.
SkyObject * findByName(const QString &name, bool exact=true) override
Search the children of this SkyMapComposite for a SkyObject whose name matches the argument.
void changeDateTime(const KStarsDateTime &newDate)
Change the current simulation date/time to the KStarsDateTime argument.
Definition: kstarsdata.cpp:327
QString i18n(const char *text, const TYPE &arg...)
Store several time-dependent astronomical quantities.
Definition: ksnumbers.h:42
const CachingDms * lat() const
Definition: geolocation.h:70
bool atEnd() const const
double elevation() const
Definition: geolocation.h:76
void setDatabaseName(const QString &name)
SkipEmptyParts
ColorScheme * colorScheme()
Definition: kstarsdata.h:171
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
GeoLocation * geo()
Definition: kstarsdata.h:229
int length() const const
QString translatedName() const
Definition: geolocation.cpp:76
QString readLine(qint64 maxlen)
Q_INVOKABLE SimClock * clock()
Definition: kstarsdata.h:217
QString translatedCountry() const
Definition: geolocation.cpp:97
void setFileName(const QString &name)
QString translatedProvince() const
Definition: geolocation.cpp:90
void updateSolarSystemBodies(KSNumbers *num) override
Delegate planet position updates to the SolarSystemComposite.
KLocalizedString KI18N_EXPORT ki18n(const char *text)
void update(KSNumbers *num=nullptr) override
Delegate update-position requests to all sub components.
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).
bool executeScript(const QString &name, SkyMap *map)
Execute a script.
Definition: kstarsdata.cpp:912
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
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
QString join(const QString &separator) const const
virtual void close() override
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
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.
QString name() const
Definition: geolocation.h:106
int indexOf(QStringView str, int from) const const
QString & replace(int position, int n, QChar after)
QString & remove(int position, int n)
QString province() const
Definition: geolocation.h:115
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
SkyMapComposite * skyComposite()
Definition: kstarsdata.h:165
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
void setSnapNextFocus(bool b=true)
Disable or re-enable the slewing animation for the next Focus change.
Definition: kstarsdata.h:282
const KStarsDateTime & ut() const
Definition: kstarsdata.h:156
QString toLower() const const
static bool Closing
Set to true when the application is being closed.
Definition: kstars.h:826
QString text() const const
QList< Key > keys() const const
QString toString() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void skyUpdate(bool)
Should be used to refresh skymap.
Q_SCRIPTABLE Q_NOREPLY void setUTC(const KStarsDateTime &newtime)
DBUS function to set the time of the SimClock.
Definition: simclock.cpp:143
QString left(int n) const const
const double & Degrees() const
Definition: dms.h:141
Canvas widget for displaying the sky bitmap; also handles user interaction events.
Definition: skymap.h:52
QString name(StandardShortcut id)
bool isEmptyRule() const
Definition: timezonerule.h:71
bool equals(TimeZoneRule *r)
QString filePath(const QString &fileName) const const
bool isTimeRunningForward() const
Returns true if time is running forward else false.
Definition: kstarsdata.h:118
const QChar at(int position) const const
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QSet::iterator insert(const T &value)
QList::iterator begin()
HideProgressInfo
~KStarsData() override
Destructor.
Definition: kstarsdata.cpp:118
bool contains(const QString &name) const const
void syncFOV()
Synchronize list of visible FOVs and list of selected FOVs in Options.
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 ...
void setNextDSTChange(const KStarsDateTime &dt)
Set the NextDSTChange member.
Definition: kstarsdata.h:109
QList::iterator end()
QSet< T > & intersect(const QSet< T > &other)
QSqlError lastError() const const
QString mid(int position, int n) const const
GeoLocation * nearestLocation(double longitude, double latitude)
nearestLocation Return nearest location to the given longitude and latitude coordinates
Definition: kstarsdata.cpp:377
QStringList tables(QSql::TableType type) const const
Information about an object in the sky.
Definition: skyobject.h:41
QStringRef mid(int position, int n) const const
QString & append(QChar ch)
void setLat(const dms &l)
Set latitude according to dms argument.
Definition: geolocation.h:180
void syncLST()
Sync the LST with the simulation clock.
Definition: kstarsdata.cpp:322
Stores the tite and URL of a webpage.
Relevant data about an observing location on Earth.
Definition: geolocation.h:27
virtual bool reset()
KStarsDateTime nextDSTChange() const
Definition: timezonerule.h:95
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:57:32 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.