File indexing completed on 2024-04-21 03:45:03
0001 /* 0002 SPDX-FileCopyrightText: 2001 Heiko Evermann <heiko@evermann.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kstarsdata.h" 0008 0009 #include "ksutils.h" 0010 #include "Options.h" 0011 #include "auxiliary/kspaths.h" 0012 #include "skycomponents/supernovaecomponent.h" 0013 #include "skycomponents/skymapcomposite.h" 0014 #include "ksnotification.h" 0015 #include "skyobjectuserdata.h" 0016 #include <kio/job_base.h> 0017 #include <kio/filecopyjob.h> 0018 #ifndef KSTARS_LITE 0019 #include "fov.h" 0020 #include "imageexporter.h" 0021 #include "kstars.h" 0022 #include "observinglist.h" 0023 #include "skymap.h" 0024 #include "dialogs/detaildialog.h" 0025 #include "oal/execute.h" 0026 #endif 0027 0028 #ifndef KSTARS_LITE 0029 #include <KMessageBox> 0030 #endif 0031 0032 #include <QSqlQuery> 0033 #include <QSqlRecord> 0034 #include <QtConcurrent> 0035 0036 #include "kstars_debug.h" 0037 0038 // Qt version calming 0039 #include <qtskipemptyparts.h> 0040 0041 namespace 0042 { 0043 // Report fatal error during data loading to user 0044 // Calls QApplication::exit 0045 void fatalErrorMessage(QString fname) 0046 { 0047 qCCritical(KSTARS) << i18n("Critical File not Found: %1", fname); 0048 KSNotification::sorry(i18n("The file %1 could not be found. " 0049 "KStars cannot run properly without this file. " 0050 "KStars searches for this file in following locations:\n\n\t" 0051 "%2\n\n" 0052 "It appears that your setup is broken.", 0053 fname, QStandardPaths::standardLocations(QStandardPaths::DataLocation).join("\n\t")), 0054 i18n("Critical File Not Found: %1", fname)); // FIXME: Must list locations depending on file type 0055 0056 qApp->exit(1); 0057 } 0058 0059 // Report non-fatal error during data loading to user and ask 0060 // whether he wants to continue. 0061 // 0062 // No remaining calls so commented out to suppress unused warning 0063 // 0064 // Calls QApplication::exit if he don't 0065 //bool nonFatalErrorMessage(QString fname) 0066 //{ 0067 // qCWarning(KSTARS) << i18n( "Non-Critical File Not Found: %1", fname ); 0068 //#ifdef KSTARS_LITE 0069 // Q_UNUSED(fname); 0070 // return true; 0071 //#else 0072 // int res = KMessageBox::warningContinueCancel(nullptr, 0073 // i18n("The file %1 could not be found. " 0074 // "KStars can still run without this file. " 0075 // "KStars search for this file in following locations:\n\n\t" 0076 // "%2\n\n" 0077 // "It appears that you setup is broken. Press Continue to run KStars without this file ", 0078 // fname, QStandardPaths::standardLocations( QStandardPaths::DataLocation ).join("\n\t") ), 0079 // i18n( "Non-Critical File Not Found: %1", fname )); // FIXME: Must list locations depending on file type 0080 // if( res != KMessageBox::Continue ) 0081 // qApp->exit(1); 0082 // return res == KMessageBox::Continue; 0083 //#endif 0084 //} 0085 } 0086 0087 KStarsData *KStarsData::pinstance = nullptr; 0088 0089 KStarsData *KStarsData::Create() 0090 { 0091 // This method should never be called twice within a run, since a 0092 // lot of the code assumes that KStarsData, once created, is never 0093 // destroyed. They maintain local copies of KStarsData::Instance() 0094 // for efficiency (maybe this should change, but it is not 0095 // required to delete and reinstantiate KStarsData). Thus, when we 0096 // call this method, pinstance MUST be zero, i.e. this must be the 0097 // first (and last) time we are calling it. -- asimha 0098 Q_ASSERT(!pinstance); 0099 0100 delete pinstance; 0101 pinstance = new KStarsData(); 0102 return pinstance; 0103 } 0104 0105 KStarsData::KStarsData() 0106 : m_Geo(dms(0), dms(0)), m_ksuserdb(), 0107 temporaryTrail(false), 0108 //locale( new KLocale( "kstars" ) ), 0109 m_preUpdateID(0), m_updateID(0), m_preUpdateNumID(0), m_updateNumID(0), m_preUpdateNum(J2000), m_updateNum(J2000) 0110 { 0111 #ifndef KSTARS_LITE 0112 m_LogObject.reset(new OAL::Log); 0113 #endif 0114 // at startup times run forward 0115 setTimeDirection(0.0); 0116 } 0117 0118 KStarsData::~KStarsData() 0119 { 0120 Q_ASSERT(pinstance); 0121 0122 //delete locale; 0123 qDeleteAll(geoList); 0124 geoList.clear(); 0125 qDeleteAll(ADVtreeList); 0126 ADVtreeList.clear(); 0127 0128 pinstance = nullptr; 0129 } 0130 0131 bool KStarsData::initialize() 0132 { 0133 //Load Time Zone Rules// 0134 emit progressText(i18n("Reading time zone rules")); 0135 if (!readTimeZoneRulebook()) 0136 { 0137 fatalErrorMessage("TZrules.dat"); 0138 return false; 0139 } 0140 0141 emit progressText( 0142 i18n("Upgrade existing user city db to support geographic elevation.")); 0143 0144 QString dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mycitydb.sqlite"); 0145 0146 /// This code to add Height column to table city in mycitydb.sqlite is a transitional measure to support a meaningful 0147 /// geographic elevation. 0148 if (QFile::exists(dbfile)) 0149 { 0150 QSqlDatabase fixcitydb = QSqlDatabase::addDatabase("QSQLITE", "fixcitydb"); 0151 0152 fixcitydb.setDatabaseName(dbfile); 0153 fixcitydb.open(); 0154 0155 if (fixcitydb.tables().contains("city", Qt::CaseInsensitive)) 0156 { 0157 QSqlRecord r = fixcitydb.record("city"); 0158 if (!r.contains("Elevation")) 0159 { 0160 emit progressText(i18n("Adding \"Elevation\" column to city table.")); 0161 0162 QSqlQuery query(fixcitydb); 0163 if (query.exec( 0164 "alter table city add column Elevation real default -10;") == 0165 false) 0166 { 0167 emit progressText(QString("failed to add Elevation column to city " 0168 "table in mycitydb.sqlite: &1") 0169 .arg(query.lastError().text())); 0170 } 0171 } 0172 else 0173 { 0174 emit progressText(i18n("City table already contains \"Elevation\".")); 0175 } 0176 } 0177 else 0178 { 0179 emit progressText(i18n("City table missing from database.")); 0180 } 0181 fixcitydb.close(); 0182 } 0183 0184 //Load Cities// 0185 emit progressText(i18n("Loading city data")); 0186 if (!readCityData()) 0187 { 0188 fatalErrorMessage("citydb.sqlite"); 0189 return false; 0190 } 0191 0192 //Initialize User Database// 0193 emit progressText(i18n("Loading User Information")); 0194 m_ksuserdb.Initialize(); 0195 0196 //Initialize SkyMapComposite// 0197 emit progressText(i18n("Loading sky objects")); 0198 m_SkyComposite.reset(new SkyMapComposite()); 0199 //Load Image URLs// 0200 //#ifndef Q_OS_ANDROID 0201 //On Android these 2 calls produce segfault. WARNING 0202 emit progressText(i18n("Loading Image URLs")); 0203 0204 // if (!readURLData("image_url.dat", SkyObjectUserdata::Type::image) && 0205 // !nonFatalErrorMessage("image_url.dat")) 0206 // return false; 0207 QtConcurrent::run(this, &KStarsData::readURLData, QString("image_url.dat"), 0208 SkyObjectUserdata::Type::image); 0209 0210 //Load Information URLs// 0211 //emit progressText(i18n("Loading Information URLs")); 0212 // if (!readURLData("info_url.dat", SkyObjectUserdata::Type::website) && 0213 // !nonFatalErrorMessage("info_url.dat")) 0214 // return false; 0215 QtConcurrent::run(this, &KStarsData::readURLData, QString("info_url.dat"), 0216 SkyObjectUserdata::Type::website); 0217 0218 //#endif 0219 //emit progressText( i18n("Loading Variable Stars" ) ); 0220 0221 #ifndef KSTARS_LITE 0222 //Initialize Observing List 0223 m_ObservingList = new ObservingList(); 0224 #endif 0225 0226 readUserLog(); 0227 0228 #ifndef KSTARS_LITE 0229 readADVTreeData(); 0230 #endif 0231 return true; 0232 } 0233 0234 void KStarsData::updateTime(GeoLocation *geo, const bool automaticDSTchange) 0235 { 0236 // sync LTime with the simulation clock 0237 LTime = geo->UTtoLT(ut()); 0238 syncLST(); 0239 0240 //Only check DST if (1) TZrule is not the empty rule, and (2) if we have crossed 0241 //the DST change date/time. 0242 if (!geo->tzrule()->isEmptyRule()) 0243 { 0244 if (TimeRunsForward) 0245 { 0246 // timedirection is forward 0247 // DST change happens if current date is bigger than next calculated dst change 0248 if (ut() > NextDSTChange) 0249 resetToNewDST(geo, automaticDSTchange); 0250 } 0251 else 0252 { 0253 // timedirection is backward 0254 // DST change happens if current date is smaller than next calculated dst change 0255 if (ut() < NextDSTChange) 0256 resetToNewDST(geo, automaticDSTchange); 0257 } 0258 } 0259 0260 KSNumbers num(ut().djd()); 0261 0262 if (std::abs(ut().djd() - LastNumUpdate.djd()) > 1.0) 0263 { 0264 LastNumUpdate = KStarsDateTime(ut().djd()); 0265 m_preUpdateNumID++; 0266 m_preUpdateNum = KSNumbers(num); 0267 skyComposite()->update(&num); 0268 } 0269 0270 if (std::abs(ut().djd() - LastPlanetUpdate.djd()) > 0.01) 0271 { 0272 LastPlanetUpdate = KStarsDateTime(ut().djd()); 0273 skyComposite()->updateSolarSystemBodies(&num); 0274 } 0275 0276 // Moon moves ~30 arcmin/hr, so update its position every minute. 0277 if (std::abs(ut().djd() - LastMoonUpdate.djd()) > 0.00069444) 0278 { 0279 LastMoonUpdate = ut(); 0280 skyComposite()->updateMoons(&num); 0281 } 0282 0283 //Update Alt/Az coordinates. Timescale varies with zoom level 0284 //If Clock is in Manual Mode, always update. (?) 0285 if (std::abs(ut().djd() - LastSkyUpdate.djd()) > 0.1 / Options::zoomFactor() || clock()->isManualMode()) 0286 { 0287 LastSkyUpdate = ut(); 0288 m_preUpdateID++; 0289 //omit KSNumbers arg == just update Alt/Az coords // <-- Eh? -- asimha. Looks like this behavior / ideology has changed drastically. 0290 skyComposite()->update(&num); 0291 0292 emit skyUpdate(clock()->isManualMode()); 0293 } 0294 } 0295 0296 void KStarsData::syncUpdateIDs() 0297 { 0298 m_updateID = m_preUpdateID; 0299 if (m_updateNumID == m_preUpdateNumID) 0300 return; 0301 m_updateNumID = m_preUpdateNumID; 0302 m_updateNum = KSNumbers(m_preUpdateNum); 0303 } 0304 0305 unsigned int KStarsData::incUpdateID() 0306 { 0307 m_preUpdateID++; 0308 m_preUpdateNumID++; 0309 syncUpdateIDs(); 0310 return m_updateID; 0311 } 0312 0313 void KStarsData::setFullTimeUpdate() 0314 { 0315 //Set the update markers to invalid dates to trigger updates in each category 0316 LastSkyUpdate = KStarsDateTime(QDateTime()); 0317 LastPlanetUpdate = KStarsDateTime(QDateTime()); 0318 LastMoonUpdate = KStarsDateTime(QDateTime()); 0319 LastNumUpdate = KStarsDateTime(QDateTime()); 0320 } 0321 0322 void KStarsData::syncLST() 0323 { 0324 LST = geo()->GSTtoLST(ut().gst()); 0325 } 0326 0327 void KStarsData::changeDateTime(const KStarsDateTime &newDate) 0328 { 0329 //Turn off animated slews for the next time step. 0330 setSnapNextFocus(); 0331 0332 clock()->setUTC(newDate); 0333 0334 LTime = geo()->UTtoLT(ut()); 0335 //set local sideral time 0336 syncLST(); 0337 0338 //Make sure Numbers, Moon, planets, and sky objects are updated immediately 0339 setFullTimeUpdate(); 0340 0341 // reset tzrules data with new local time and time direction (forward or backward) 0342 geo()->tzrule()->reset_with_ltime(LTime, geo()->TZ0(), isTimeRunningForward()); 0343 0344 // reset next dst change time 0345 setNextDSTChange(geo()->tzrule()->nextDSTChange()); 0346 } 0347 0348 void KStarsData::resetToNewDST(GeoLocation *geo, const bool automaticDSTchange) 0349 { 0350 // reset tzrules data with local time, timezone offset and time direction (forward or backward) 0351 // force a DST change with option true for 3. parameter 0352 geo->tzrule()->reset_with_ltime(LTime, geo->TZ0(), TimeRunsForward, automaticDSTchange); 0353 // reset next DST change time 0354 setNextDSTChange(geo->tzrule()->nextDSTChange()); 0355 //reset LTime, because TZoffset has changed 0356 LTime = geo->UTtoLT(ut()); 0357 } 0358 0359 void KStarsData::setTimeDirection(float scale) 0360 { 0361 TimeRunsForward = scale >= 0; 0362 } 0363 0364 GeoLocation *KStarsData::locationNamed(const QString &city, const QString &province, const QString &country) 0365 { 0366 foreach (GeoLocation *loc, geoList) 0367 { 0368 if (loc->translatedName() == city && (province.isEmpty() || loc->translatedProvince() == province) && 0369 (country.isEmpty() || loc->translatedCountry() == country)) 0370 { 0371 return loc; 0372 } 0373 } 0374 return nullptr; 0375 } 0376 0377 GeoLocation *KStarsData::nearestLocation(double longitude, double latitude) 0378 { 0379 GeoLocation *nearest = nullptr; 0380 double distance = 1e6; 0381 0382 dms lng(longitude), lat(latitude); 0383 for (auto oneCity : geoList) 0384 { 0385 double newDistance = oneCity->distanceTo(lng, lat); 0386 if (newDistance < distance) 0387 { 0388 distance = newDistance; 0389 nearest = oneCity; 0390 } 0391 } 0392 0393 return nearest; 0394 } 0395 0396 void KStarsData::setLocationFromOptions() 0397 { 0398 setLocation(GeoLocation(dms(Options::longitude()), dms(Options::latitude()), Options::cityName(), 0399 Options::provinceName(), Options::countryName(), Options::timeZone(), 0400 &(Rulebook[Options::dST()]), Options::elevation(), false, 4)); 0401 } 0402 0403 void KStarsData::setLocation(const GeoLocation &l) 0404 { 0405 m_Geo = GeoLocation(l); 0406 if (m_Geo.lat()->Degrees() >= 90.0) 0407 m_Geo.setLat(dms(89.99)); 0408 if (m_Geo.lat()->Degrees() <= -90.0) 0409 m_Geo.setLat(dms(-89.99)); 0410 0411 //store data in the Options objects 0412 Options::setCityName(m_Geo.name()); 0413 Options::setProvinceName(m_Geo.province()); 0414 Options::setCountryName(m_Geo.country()); 0415 Options::setTimeZone(m_Geo.TZ0()); 0416 Options::setElevation(m_Geo.elevation()); 0417 Options::setLongitude(m_Geo.lng()->Degrees()); 0418 Options::setLatitude(m_Geo.lat()->Degrees()); 0419 // set the rule from rulebook 0420 foreach (const QString &key, Rulebook.keys()) 0421 { 0422 if (!key.isEmpty() && m_Geo.tzrule()->equals(&Rulebook[key])) 0423 Options::setDST(key); 0424 } 0425 0426 emit geoChanged(); 0427 } 0428 0429 SkyObject *KStarsData::objectNamed(const QString &name) 0430 { 0431 if ((name == "star") || (name == "nothing") || name.isEmpty()) 0432 return nullptr; 0433 return skyComposite()->findByName(name, true); // objectNamed has to do an exact match 0434 } 0435 0436 bool KStarsData::readCityData() 0437 { 0438 QSqlDatabase citydb = QSqlDatabase::addDatabase("QSQLITE", "citydb"); 0439 QString dbfile = KSPaths::locate(QStandardPaths::AppLocalDataLocation, "citydb.sqlite"); 0440 citydb.setDatabaseName(dbfile); 0441 if (citydb.open() == false) 0442 { 0443 qCCritical(KSTARS) << "Unable to open city database file " << dbfile << citydb.lastError().text(); 0444 return false; 0445 } 0446 0447 QSqlQuery get_query(citydb); 0448 0449 //get_query.prepare("SELECT * FROM city"); 0450 if (!get_query.exec("SELECT * FROM city")) 0451 { 0452 qCCritical(KSTARS) << get_query.lastError(); 0453 return false; 0454 } 0455 0456 bool citiesFound = false; 0457 // get_query.size() always returns -1 so we set citiesFound if at least one city is found 0458 while (get_query.next()) 0459 { 0460 citiesFound = true; 0461 QString name = get_query.value(1).toString(); 0462 QString province = get_query.value(2).toString(); 0463 QString country = get_query.value(3).toString(); 0464 dms lat = dms(get_query.value(4).toString()); 0465 dms lng = dms(get_query.value(5).toString()); 0466 double TZ = get_query.value(6).toDouble(); 0467 TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]); 0468 double elevation = get_query.value(8).toDouble(); 0469 0470 // appends city names to list 0471 geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, elevation, true, 4)); 0472 } 0473 citydb.close(); 0474 0475 // Reading local database 0476 QSqlDatabase mycitydb = QSqlDatabase::addDatabase("QSQLITE", "mycitydb"); 0477 dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mycitydb.sqlite"); 0478 0479 if (QFile::exists(dbfile)) 0480 { 0481 mycitydb.setDatabaseName(dbfile); 0482 if (mycitydb.open()) 0483 { 0484 QSqlQuery get_query(mycitydb); 0485 0486 if (!get_query.exec("SELECT * FROM city")) 0487 { 0488 qDebug() << Q_FUNC_INFO << get_query.lastError(); 0489 return false; 0490 } 0491 while (get_query.next()) 0492 { 0493 QString name = get_query.value(1).toString(); 0494 QString province = get_query.value(2).toString(); 0495 QString country = get_query.value(3).toString(); 0496 dms lat = dms(get_query.value(4).toString()); 0497 dms lng = dms(get_query.value(5).toString()); 0498 double TZ = get_query.value(6).toDouble(); 0499 TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]); 0500 double elevation = get_query.value(8).toDouble(); 0501 0502 // appends city names to list 0503 geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, elevation, false, 4)); 0504 } 0505 mycitydb.close(); 0506 } 0507 } 0508 0509 return citiesFound; 0510 } 0511 0512 bool KStarsData::readTimeZoneRulebook() 0513 { 0514 QFile file; 0515 0516 if (KSUtils::openDataFile(file, "TZrules.dat")) 0517 { 0518 QTextStream stream(&file); 0519 0520 while (!stream.atEnd()) 0521 { 0522 QString line = stream.readLine().trimmed(); 0523 if (line.length() && !line.startsWith('#')) //ignore commented and blank lines 0524 { 0525 QStringList fields = line.split(' ', Qt::SkipEmptyParts); 0526 QString id = fields[0]; 0527 QTime stime = QTime(fields[3].leftRef(fields[3].indexOf(':')).toInt(), 0528 fields[3].midRef(fields[3].indexOf(':') + 1, fields[3].length()).toInt()); 0529 QTime rtime = QTime(fields[6].leftRef(fields[6].indexOf(':')).toInt(), 0530 fields[6].midRef(fields[6].indexOf(':') + 1, fields[6].length()).toInt()); 0531 0532 Rulebook[id] = TimeZoneRule(fields[1], fields[2], stime, fields[4], fields[5], rtime); 0533 } 0534 } 0535 return true; 0536 } 0537 else 0538 { 0539 return false; 0540 } 0541 } 0542 0543 bool KStarsData::openUrlFile(const QString &urlfile, QFile &file) 0544 { 0545 //QFile file; 0546 QString localFile; 0547 bool fileFound = false; 0548 QFile localeFile; 0549 0550 //if ( locale->language() != "en_US" ) 0551 if (QLocale().language() != QLocale::English) 0552 //localFile = locale->language() + '/' + urlfile; 0553 localFile = QLocale().languageToString(QLocale().language()) + '/' + urlfile; 0554 0555 if (!localFile.isEmpty() && KSUtils::openDataFile(file, localFile)) 0556 { 0557 fileFound = true; 0558 } 0559 else 0560 { 0561 // Try to load locale file, if not successful, load regular urlfile and then copy it to locale. 0562 file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(urlfile)); 0563 if (file.open(QIODevice::ReadOnly)) 0564 { 0565 //local file found. Now, if global file has newer timestamp, then merge the two files. 0566 //First load local file into QStringList 0567 bool newDataFound(false); 0568 QStringList urlData; 0569 QTextStream lStream(&file); 0570 while (!lStream.atEnd()) 0571 urlData.append(lStream.readLine()); 0572 0573 //Find global file(s) in findAllResources() list. 0574 QFileInfo fi_local(file.fileName()); 0575 0576 QStringList flist = KSPaths::locateAll(QStandardPaths::DataLocation, urlfile); 0577 0578 for (int i = 0; i < flist.size(); i++) 0579 { 0580 if (flist[i] != file.fileName()) 0581 { 0582 QFileInfo fi_global(flist[i]); 0583 0584 //Is this global file newer than the local file? 0585 if (fi_global.lastModified() > fi_local.lastModified()) 0586 { 0587 //Global file has newer timestamp than local. Add lines in global file that don't already exist in local file. 0588 //be smart about this; in some cases the URL is updated but the image is probably the same if its 0589 //label string is the same. So only check strings up to last ":" 0590 QFile globalFile(flist[i]); 0591 if (globalFile.open(QIODevice::ReadOnly)) 0592 { 0593 QTextStream gStream(&globalFile); 0594 while (!gStream.atEnd()) 0595 { 0596 QString line = gStream.readLine(); 0597 0598 //If global-file line begins with "XXX:" then this line should be removed from the local file. 0599 if (line.startsWith(QLatin1String("XXX:")) && urlData.contains(line.mid(4))) 0600 { 0601 urlData.removeAt(urlData.indexOf(line.mid(4))); 0602 } 0603 else 0604 { 0605 //does local file contain the current global file line, up to second ':' ? 0606 0607 bool linefound(false); 0608 for (int j = 0; j < urlData.size(); ++j) 0609 { 0610 if (urlData[j].contains(line.left(line.indexOf(':', line.indexOf(':') + 1)))) 0611 { 0612 //replace line in urlData with its equivalent in the newer global file. 0613 urlData.replace(j, line); 0614 if (!newDataFound) 0615 newDataFound = true; 0616 linefound = true; 0617 break; 0618 } 0619 } 0620 if (!linefound) 0621 { 0622 urlData.append(line); 0623 if (!newDataFound) 0624 newDataFound = true; 0625 } 0626 } 0627 } 0628 } 0629 } 0630 } 0631 } 0632 0633 file.close(); 0634 0635 //(possibly) write appended local file 0636 if (newDataFound) 0637 { 0638 if (file.open(QIODevice::WriteOnly)) 0639 { 0640 QTextStream outStream(&file); 0641 for (int i = 0; i < urlData.size(); i++) 0642 { 0643 outStream << urlData[i] << '\n'; 0644 } 0645 file.close(); 0646 } 0647 } 0648 0649 if (file.open(QIODevice::ReadOnly)) 0650 fileFound = true; 0651 } 0652 else 0653 { 0654 if (KSUtils::openDataFile(file, urlfile)) 0655 { 0656 if (QLocale().language() != QLocale::English) 0657 qDebug() << Q_FUNC_INFO << "No localized URL file; using default English file."; 0658 // we found urlfile, we need to copy it to locale 0659 localeFile.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(urlfile)); 0660 if (localeFile.open(QIODevice::WriteOnly)) 0661 { 0662 QTextStream readStream(&file); 0663 QTextStream writeStream(&localeFile); 0664 while (!readStream.atEnd()) 0665 { 0666 QString line = readStream.readLine(); 0667 if (!line.startsWith(QLatin1String("XXX:"))) //do not write "deleted" lines 0668 writeStream << line << '\n'; 0669 } 0670 0671 localeFile.close(); 0672 file.reset(); 0673 } 0674 else 0675 { 0676 qDebug() << Q_FUNC_INFO << "Failed to copy default URL file to locale folder, modifying default object links is " 0677 "not possible"; 0678 } 0679 fileFound = true; 0680 } 0681 } 0682 } 0683 return fileFound; 0684 } 0685 0686 bool KStarsData::readURLData(const QString &urlfile, SkyObjectUserdata::Type type) 0687 { 0688 #ifndef KSTARS_LITE 0689 if (KStars::Closing) 0690 return true; 0691 #endif 0692 0693 QFile file; 0694 if (!openUrlFile(urlfile, file)) 0695 return false; 0696 0697 QTextStream stream(&file); 0698 QMutexLocker _{ &m_user_data_mutex }; 0699 0700 while (!stream.atEnd()) 0701 { 0702 QString line = stream.readLine(); 0703 0704 //ignore comment lines 0705 if (!line.startsWith('#')) 0706 { 0707 #ifndef KSTARS_LITE 0708 if (KStars::Closing) 0709 { 0710 file.close(); 0711 return true; 0712 } 0713 #endif 0714 0715 int idx = line.indexOf(':'); 0716 QString name = line.left(idx); 0717 if (name == "XXX") 0718 continue; 0719 QString sub = line.mid(idx + 1); 0720 idx = sub.indexOf(':'); 0721 QString title = sub.left(idx); 0722 QString url = sub.mid(idx + 1); 0723 // Dirty hack to fix things up for planets 0724 0725 // if (name == "Mercury" || name == "Venus" || name == "Mars" || name == "Jupiter" || name == "Saturn" || 0726 // name == "Uranus" || name == "Neptune" /* || name == "Pluto" */) 0727 // o = skyComposite()->findByName(i18n(name.toLocal8Bit().data())); 0728 // else 0729 0730 auto &data_element = m_user_data[name]; 0731 data_element.addLink(title, QUrl{ url }, type); 0732 } 0733 } 0734 file.close(); 0735 return true; 0736 } 0737 0738 // FIXME: Improve the user log system 0739 0740 // Note: It might be very nice to keep the log in plaintext files, for 0741 // portability, human-readability, and greppability. However, it takes 0742 // a lot of time to parse and look up, is very messy from the 0743 // reliability and programming point of view, needs to be parsed at 0744 // start, can become corrupt easily because of a missing bracket... 0745 0746 // An SQLite database is a good compromise. A user can easily view it 0747 // using an SQLite browser. There is no need to read at start-up, one 0748 // can read the log when required. Easy to edit logs / update logs 0749 // etc. Will not become corrupt. Needn't be parsed. 0750 0751 // However, IMHO, it is best to put these kinds of things in separate 0752 // databases, instead of unifying them as a table under the user 0753 // database. This ensures portability and a certain robustness that if 0754 // a user opens it, they cannot incorrectly edit a part of the DB they 0755 // did not intend to edit. 0756 0757 // --asimha 2016 Aug 17 0758 0759 // FIXME: This is a significant contributor to KStars startup time. 0760 bool KStarsData::readUserLog() 0761 { 0762 QFile file; 0763 QString fullContents; 0764 0765 if (!KSUtils::openDataFile(file, "userlog.dat")) 0766 return false; 0767 0768 QTextStream stream(&file); 0769 0770 if (!stream.atEnd()) 0771 fullContents = stream.readAll(); 0772 0773 QMutexLocker _{ &m_user_data_mutex }; 0774 0775 QStringRef buffer(&fullContents); 0776 const QLatin1String logStart("[KSLABEL:"), logEnd("[KSLogEnd]"); 0777 std::size_t currentEntryIndex = 0; 0778 while (!buffer.isEmpty()) 0779 { 0780 int startIndex, endIndex; 0781 QStringRef sub, name, data; 0782 0783 startIndex = buffer.indexOf(logStart) + logStart.size(); 0784 if (startIndex < 0) 0785 break; 0786 currentEntryIndex += startIndex; 0787 endIndex = buffer.indexOf(logEnd, startIndex); 0788 0789 auto malformatError = [&]() 0790 { 0791 int res = KMessageBox::warningContinueCancel( 0792 nullptr, 0793 i18n("The user notes log file %1 is malformatted in the opening of the entry starting at %2. " 0794 "KStars can still run without fully reading this file. " 0795 "Press Continue to run KStars with whatever partial reading was successful. " 0796 "The file may get truncated if KStars writes to the file later. Press Cancel to instead abort now and manually fix the problem. ", 0797 file.fileName(), QString::number(currentEntryIndex)), 0798 i18n( "Malformed file %1", file.fileName() ) 0799 ); 0800 if( res != KMessageBox::Continue ) 0801 qApp->exit(1); // FIXME: Why does this not work? 0802 }; 0803 0804 if (endIndex < 0) 0805 { 0806 malformatError(); 0807 break; 0808 } 0809 0810 // Read name after KSLABEL identifier 0811 // Because some object names have [] within them, we have to be careful 0812 // [...] names are used by SIMBAD and NED to specify paper authors 0813 // Unbalanced [,] are not allowed in the object name, but are allowed in the notes 0814 int nameEndIndex = startIndex, openBracketCount = 1; 0815 while (openBracketCount > 0 && nameEndIndex < endIndex) 0816 { 0817 if (buffer[nameEndIndex] == ']') 0818 --openBracketCount; 0819 else if (buffer[nameEndIndex] == '[') 0820 ++openBracketCount; 0821 ++nameEndIndex; 0822 } 0823 if (openBracketCount > 0) 0824 { 0825 malformatError(); 0826 break; 0827 } 0828 name = buffer.mid(startIndex, (nameEndIndex - 1) - startIndex); 0829 0830 // Read data and skip new line 0831 if (buffer[nameEndIndex] == '\n') 0832 ++nameEndIndex; 0833 data = buffer.mid(nameEndIndex, endIndex - nameEndIndex); 0834 buffer = buffer.mid(endIndex + logEnd.size() + 1); 0835 currentEntryIndex += (endIndex + logEnd.size() + 1 - startIndex); 0836 0837 auto &data_element = m_user_data[name.toString()]; 0838 data_element.userLog = data.toString(); 0839 0840 } // end while 0841 file.close(); 0842 return true; 0843 } 0844 0845 bool KStarsData::readADVTreeData() 0846 { 0847 QFile file; 0848 QString Interface; 0849 QString Name, Link, subName; 0850 0851 if (!KSUtils::openDataFile(file, "advinterface.dat")) 0852 return false; 0853 0854 QTextStream stream(&file); 0855 QString Line; 0856 0857 while (!stream.atEnd()) 0858 { 0859 int Type, interfaceIndex; 0860 0861 Line = stream.readLine(); 0862 0863 if (Line.startsWith(QLatin1String("[KSLABEL]"))) 0864 { 0865 Name = Line.mid(9); 0866 Type = 0; 0867 } 0868 else if (Line.startsWith(QLatin1String("[END]"))) 0869 Type = 1; 0870 else if (Line.startsWith(QLatin1String("[KSINTERFACE]"))) 0871 { 0872 Interface = Line.mid(13); 0873 continue; 0874 } 0875 0876 else 0877 { 0878 int idx = Line.indexOf(':'); 0879 Name = Line.left(idx); 0880 Link = Line.mid(idx + 1); 0881 0882 // Link is empty, using Interface instead 0883 if (Link.isEmpty()) 0884 { 0885 Link = Interface; 0886 subName = Name; 0887 interfaceIndex = Link.indexOf(QLatin1String("KSINTERFACE")); 0888 Link.remove(interfaceIndex, 11); 0889 Link = Link.insert(interfaceIndex, subName.replace(' ', '+')); 0890 } 0891 0892 Type = 2; 0893 } 0894 0895 ADVTreeData *ADVData = new ADVTreeData; 0896 0897 ADVData->Name = Name; 0898 ADVData->Link = Link; 0899 ADVData->Type = Type; 0900 0901 ADVtreeList.append(ADVData); 0902 } 0903 0904 return true; 0905 } 0906 0907 //There's a lot of code duplication here, but it's not avoidable because 0908 //this function is only called from main.cpp when the user is using 0909 //"dump" mode to produce an image from the command line. In this mode, 0910 //there is no KStars object, so none of the DBus functions can be called 0911 //directly. 0912 bool KStarsData::executeScript(const QString &scriptname, SkyMap *map) 0913 { 0914 #ifndef KSTARS_LITE 0915 int cmdCount(0); 0916 0917 QFile f(scriptname); 0918 if (!f.open(QIODevice::ReadOnly)) 0919 { 0920 qDebug() << Q_FUNC_INFO << "Could not open file " << f.fileName(); 0921 return false; 0922 } 0923 0924 QTextStream istream(&f); 0925 while (!istream.atEnd()) 0926 { 0927 QString line = istream.readLine(); 0928 line.remove("string:"); 0929 line.remove("int32:"); 0930 line.remove("double:"); 0931 line.remove("bool:"); 0932 0933 //find a dbus line and extract the function name and its arguments 0934 //The function name starts after the last occurrence of "org.kde.kstars." 0935 //or perhaps "org.kde.kstars.SimClock.". 0936 if (line.startsWith(QString("dbus-send"))) 0937 { 0938 QString funcprefix = "org.kde.kstars.SimClock."; 0939 int i = line.lastIndexOf(funcprefix); 0940 if (i >= 0) 0941 { 0942 i += funcprefix.length(); 0943 } 0944 else 0945 { 0946 funcprefix = "org.kde.kstars."; 0947 i = line.lastIndexOf(funcprefix); 0948 if (i >= 0) 0949 { 0950 i += funcprefix.length(); 0951 } 0952 } 0953 if (i < 0) 0954 { 0955 qWarning() << "Could not parse line: " << line; 0956 return false; 0957 } 0958 0959 QStringList fn = line.mid(i).split(' '); 0960 0961 //DEBUG 0962 //qDebug() << Q_FUNC_INFO << fn; 0963 0964 if (fn[0] == "lookTowards" && fn.size() >= 2) 0965 { 0966 double az(-1.0); 0967 QString arg = fn[1].toLower(); 0968 if (arg == "n" || arg == "north") 0969 az = 0.0; 0970 if (arg == "ne" || arg == "northeast") 0971 az = 45.0; 0972 if (arg == "e" || arg == "east") 0973 az = 90.0; 0974 if (arg == "se" || arg == "southeast") 0975 az = 135.0; 0976 if (arg == "s" || arg == "south") 0977 az = 180.0; 0978 if (arg == "sw" || arg == "southwest") 0979 az = 225.0; 0980 if (arg == "w" || arg == "west") 0981 az = 270.0; 0982 if (arg == "nw" || arg == "northwest") 0983 az = 335.0; 0984 if (az >= 0.0) 0985 { 0986 // N.B. unrefract() doesn't matter at 90 degrees 0987 map->setFocusAltAz(dms(90.0), map->focus()->az()); 0988 map->focus()->HorizontalToEquatorial(&LST, geo()->lat()); 0989 map->setDestination(*map->focus()); 0990 cmdCount++; 0991 } 0992 0993 if (arg == "z" || arg == "zenith") 0994 { 0995 // N.B. unrefract() doesn't matter at 90 degrees 0996 map->setFocusAltAz(dms(90.0), map->focus()->az()); 0997 map->focus()->HorizontalToEquatorial(&LST, geo()->lat()); 0998 map->setDestination(*map->focus()); 0999 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 1487 void KStarsData::syncFOV() 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> 1531 KStarsData::addToUserData(const QString &name, const SkyObjectUserdata::LinkData &data) 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 1549 if (!file.open(QIODevice::ReadWrite | QIODevice::Append)) 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 1604 KIO::file_copy(QUrl::fromLocalFile(URLFile.fileName()), 1605 QUrl::fromLocalFile(TempFileName), -1, 1606 KIO::Overwrite | KIO::HideProgressInfo); 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 1755 const SkyObjectUserdata::Data &KStarsData::getUserData(const QString &name) 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 };