File indexing completed on 2024-03-24 15:15:00

0001 /*
0002     SPDX-FileCopyrightText: 2002 Mark Hollomon <mhh@mindspring.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ksutils.h"
0008 #include "config-kstars.h"
0009 #include "ksnotification.h"
0010 #include "kstars_debug.h"
0011 
0012 #include "catalogobject.h"
0013 #ifndef KSTARS_LITE
0014 #include "kswizard.h"
0015 #endif
0016 #include "Options.h"
0017 #include "starobject.h"
0018 #include "auxiliary/kspaths.h"
0019 
0020 #ifndef KSTARS_LITE
0021 #include <KMessageBox>
0022 #include <zlib.h>
0023 #endif
0024 
0025 #ifdef HAVE_LIBRAW
0026 #include <libraw/libraw.h>
0027 #endif
0028 
0029 #if defined(__APPLE__)
0030 #include <sys/sysctl.h>
0031 #elif defined(_WIN32)
0032 #include "windows.h"
0033 #else //Linux
0034 #include <QProcess>
0035 #endif
0036 
0037 #include <QPointer>
0038 #include <QProcessEnvironment>
0039 #include <QLoggingCategory>
0040 
0041 #ifdef HAVE_STELLARSOLVER
0042 #include <stellarsolver.h>
0043 #endif
0044 
0045 namespace KSUtils
0046 {
0047 bool isHardwareLimited()
0048 {
0049 #ifdef __arm__
0050     return true;
0051 #else
0052     return false;
0053 #endif
0054 }
0055 
0056 bool openDataFile(QFile &file, const QString &s)
0057 {
0058     QString FileName = KSPaths::locate(QStandardPaths::AppLocalDataLocation, s);
0059     if (!FileName.isNull())
0060     {
0061         file.setFileName(FileName);
0062         return file.open(QIODevice::ReadOnly);
0063     }
0064     return false;
0065 }
0066 
0067 QString getDSSURL(const SkyPoint *const p)
0068 {
0069     double height, width;
0070     double dss_default_size = Options::defaultDSSImageSize();
0071     double dss_padding      = Options::dSSPadding();
0072 
0073     Q_ASSERT(p);
0074     Q_ASSERT(dss_default_size > 0.0 && dss_padding >= 0.0);
0075 
0076     const auto *dso = dynamic_cast<const CatalogObject *>(p);
0077 
0078     // Decide what to do about the height and width
0079     if (dso)
0080     {
0081         // For deep-sky objects, use their height and width information
0082         double a, b, pa;
0083         a = dso->a();
0084         b = dso->a() *
0085             dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects
0086         pa = dso->pa() * dms::DegToRad;
0087 
0088         // We now want to convert a, b, and pa into an image
0089         // height and width -- i.e. a dRA and dDec.
0090         // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT)
0091         // From some trigonometry, assuming we have a rectangular object (worst case), we need:
0092         width  = a * sin(pa) + b * cos(pa);
0093         height = a * cos(pa) + b * sin(pa);
0094         // 'a' and 'b' are in arcminutes, so height and width are in arcminutes
0095 
0096         // Pad the RA and Dec, so that we show more of the sky than just the object.
0097         height += dss_padding;
0098         width += dss_padding;
0099     }
0100     else
0101     {
0102         // For a generic sky object, we don't know what to do. So
0103         // we just assume the default size.
0104         height = width = dss_default_size;
0105     }
0106     // There's no point in tiny DSS images that are smaller than dss_default_size
0107     if (height < dss_default_size)
0108         height = dss_default_size;
0109     if (width < dss_default_size)
0110         width = dss_default_size;
0111 
0112     return getDSSURL(p->ra0(), p->dec0(), width, height);
0113 }
0114 
0115 QString getDSSURL(const dms &ra, const dms &dec, float width, float height,
0116                   const QString &type)
0117 {
0118     const QString URLprefix("https://archive.stsci.edu/cgi-bin/dss_search?");
0119     QString URLsuffix             = QString("&e=J2000&f=%1&c=none&fov=NONE").arg(type);
0120     const double dss_default_size = Options::defaultDSSImageSize();
0121 
0122     char decsgn = (dec.Degrees() < 0.0) ? '-' : '+';
0123     int dd      = abs(dec.degree());
0124     int dm      = abs(dec.arcmin());
0125     int ds      = abs(dec.arcsec());
0126 
0127     // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size
0128     if (!qIsFinite(height) || height <= 0.0)
0129         height = dss_default_size;
0130     if (!qIsFinite(width) || width <= 0.0)
0131         width = dss_default_size;
0132 
0133     // DSS accepts images that are no larger than 75 arcminutes
0134     if (height > 75.0)
0135         height = 75.0;
0136     if (width > 75.0)
0137         width = 75.0;
0138 
0139     QString RAString, DecString, SizeString;
0140     DecString = QString::asprintf("&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds);
0141     RAString = QString::asprintf("r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second());
0142     SizeString = QString::asprintf("&h=%02.1f&w=%02.1f", height, width);
0143 
0144     return (URLprefix + RAString + DecString + SizeString + URLsuffix);
0145 }
0146 
0147 QString toDirectionString(dms angle)
0148 {
0149     // TODO: Instead of doing it this way, it would be nicer to
0150     // compute the string to arbitrary precision. Although that will
0151     // not be easy to localize.  (Consider, for instance, Indian
0152     // languages that have special names for the intercardinal points)
0153     // -- asimha
0154 
0155     static const char *directions[] =
0156     {
0157         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "N"),
0158         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNE"),
0159         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NE"),
0160         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ENE"),
0161         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "E"),
0162         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ESE"),
0163         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SE"),
0164         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSE"),
0165         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "S"),
0166         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSW"),
0167         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SW"),
0168         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WSW"),
0169         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "W"),
0170         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WNW"),
0171         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NW"),
0172         I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNW"),
0173         I18N_NOOP2("Unknown cardinal / intercardinal direction", "???")
0174     };
0175 
0176     int index = (int)((angle.reduce().Degrees() + 11.25) /
0177                       22.5); // A number between 0 and 16 (inclusive) is expected
0178 
0179     if (index < 0 || index > 16)
0180         index = 16; // Something went wrong.
0181     else
0182         index = (index == 16 ? 0 : index);
0183 
0184     return i18nc("Abbreviated cardinal / intercardinal etc. direction",
0185                  directions[index]);
0186 }
0187 
0188 QList<SkyObject *> *castStarObjListToSkyObjList(QList<StarObject *> *starObjList)
0189 {
0190     QList<SkyObject *> *skyObjList = new QList<SkyObject *>();
0191 
0192     foreach (StarObject *so, *starObjList)
0193     {
0194         skyObjList->append(so);
0195     }
0196     return skyObjList;
0197 }
0198 
0199 QString constGenetiveFromAbbrev(const QString &code)
0200 {
0201     if (code == "And")
0202         return QString("Andromedae");
0203     if (code == "Ant")
0204         return QString("Antliae");
0205     if (code == "Aps")
0206         return QString("Apodis");
0207     if (code == "Aqr")
0208         return QString("Aquarii");
0209     if (code == "Aql")
0210         return QString("Aquilae");
0211     if (code == "Ara")
0212         return QString("Arae");
0213     if (code == "Ari")
0214         return QString("Arietis");
0215     if (code == "Aur")
0216         return QString("Aurigae");
0217     if (code == "Boo")
0218         return QString("Bootis");
0219     if (code == "Cae")
0220         return QString("Caeli");
0221     if (code == "Cam")
0222         return QString("Camelopardalis");
0223     if (code == "Cnc")
0224         return QString("Cancri");
0225     if (code == "CVn")
0226         return QString("Canum Venaticorum");
0227     if (code == "CMa")
0228         return QString("Canis Majoris");
0229     if (code == "CMi")
0230         return QString("Canis Minoris");
0231     if (code == "Cap")
0232         return QString("Capricorni");
0233     if (code == "Car")
0234         return QString("Carinae");
0235     if (code == "Cas")
0236         return QString("Cassiopeiae");
0237     if (code == "Cen")
0238         return QString("Centauri");
0239     if (code == "Cep")
0240         return QString("Cephei");
0241     if (code == "Cet")
0242         return QString("Ceti");
0243     if (code == "Cha")
0244         return QString("Chamaeleontis");
0245     if (code == "Cir")
0246         return QString("Circini");
0247     if (code == "Col")
0248         return QString("Columbae");
0249     if (code == "Com")
0250         return QString("Comae Berenices");
0251     if (code == "CrA")
0252         return QString("Coronae Austrinae");
0253     if (code == "CrB")
0254         return QString("Coronae Borealis");
0255     if (code == "Crv")
0256         return QString("Corvi");
0257     if (code == "Crt")
0258         return QString("Crateris");
0259     if (code == "Cru")
0260         return QString("Crucis");
0261     if (code == "Cyg")
0262         return QString("Cygni");
0263     if (code == "Del")
0264         return QString("Delphini");
0265     if (code == "Dor")
0266         return QString("Doradus");
0267     if (code == "Dra")
0268         return QString("Draconis");
0269     if (code == "Equ")
0270         return QString("Equulei");
0271     if (code == "Eri")
0272         return QString("Eridani");
0273     if (code == "For")
0274         return QString("Fornacis");
0275     if (code == "Gem")
0276         return QString("Geminorum");
0277     if (code == "Gru")
0278         return QString("Gruis");
0279     if (code == "Her")
0280         return QString("Herculis");
0281     if (code == "Hor")
0282         return QString("Horologii");
0283     if (code == "Hya")
0284         return QString("Hydrae");
0285     if (code == "Hyi")
0286         return QString("Hydri");
0287     if (code == "Ind")
0288         return QString("Indi");
0289     if (code == "Lac")
0290         return QString("Lacertae");
0291     if (code == "Leo")
0292         return QString("Leonis");
0293     if (code == "LMi")
0294         return QString("Leonis Minoris");
0295     if (code == "Lep")
0296         return QString("Leporis");
0297     if (code == "Lib")
0298         return QString("Librae");
0299     if (code == "Lup")
0300         return QString("Lupi");
0301     if (code == "Lyn")
0302         return QString("Lyncis");
0303     if (code == "Lyr")
0304         return QString("Lyrae");
0305     if (code == "Men")
0306         return QString("Mensae");
0307     if (code == "Mic")
0308         return QString("Microscopii");
0309     if (code == "Mon")
0310         return QString("Monocerotis");
0311     if (code == "Mus")
0312         return QString("Muscae");
0313     if (code == "Nor")
0314         return QString("Normae");
0315     if (code == "Oct")
0316         return QString("Octantis");
0317     if (code == "Oph")
0318         return QString("Ophiuchi");
0319     if (code == "Ori")
0320         return QString("Orionis");
0321     if (code == "Pav")
0322         return QString("Pavonis");
0323     if (code == "Peg")
0324         return QString("Pegasi");
0325     if (code == "Per")
0326         return QString("Persei");
0327     if (code == "Phe")
0328         return QString("Phoenicis");
0329     if (code == "Pic")
0330         return QString("Pictoris");
0331     if (code == "Psc")
0332         return QString("Piscium");
0333     if (code == "PsA")
0334         return QString("Piscis Austrini");
0335     if (code == "Pup")
0336         return QString("Puppis");
0337     if (code == "Pyx")
0338         return QString("Pyxidis");
0339     if (code == "Ret")
0340         return QString("Reticuli");
0341     if (code == "Sge")
0342         return QString("Sagittae");
0343     if (code == "Sgr")
0344         return QString("Sagittarii");
0345     if (code == "Sco")
0346         return QString("Scorpii");
0347     if (code == "Scl")
0348         return QString("Sculptoris");
0349     if (code == "Sct")
0350         return QString("Scuti");
0351     if (code == "Ser")
0352         return QString("Serpentis");
0353     if (code == "Sex")
0354         return QString("Sextantis");
0355     if (code == "Tau")
0356         return QString("Tauri");
0357     if (code == "Tel")
0358         return QString("Telescopii");
0359     if (code == "Tri")
0360         return QString("Trianguli");
0361     if (code == "TrA")
0362         return QString("Trianguli Australis");
0363     if (code == "Tuc")
0364         return QString("Tucanae");
0365     if (code == "UMa")
0366         return QString("Ursae Majoris");
0367     if (code == "UMi")
0368         return QString("Ursae Minoris");
0369     if (code == "Vel")
0370         return QString("Velorum");
0371     if (code == "Vir")
0372         return QString("Virginis");
0373     if (code == "Vol")
0374         return QString("Volantis");
0375     if (code == "Vul")
0376         return QString("Vulpeculae");
0377     return code;
0378 }
0379 
0380 QString constNameFromAbbrev(const QString &code)
0381 {
0382     if (code == "And")
0383         return QString("Andromeda");
0384     if (code == "Ant")
0385         return QString("Antlia");
0386     if (code == "Aps")
0387         return QString("Apus");
0388     if (code == "Aqr")
0389         return QString("Aquarius");
0390     if (code == "Aql")
0391         return QString("Aquila");
0392     if (code == "Ara")
0393         return QString("Ara");
0394     if (code == "Ari")
0395         return QString("Aries");
0396     if (code == "Aur")
0397         return QString("Auriga");
0398     if (code == "Boo")
0399         return QString("Bootes");
0400     if (code == "Cae")
0401         return QString("Caelum");
0402     if (code == "Cam")
0403         return QString("Camelopardalis");
0404     if (code == "Cnc")
0405         return QString("Cancer");
0406     if (code == "CVn")
0407         return QString("Canes Venatici");
0408     if (code == "CMa")
0409         return QString("Canis Major");
0410     if (code == "CMi")
0411         return QString("Canis Minor");
0412     if (code == "Cap")
0413         return QString("Capricornus");
0414     if (code == "Car")
0415         return QString("Carina");
0416     if (code == "Cas")
0417         return QString("Cassiopeia");
0418     if (code == "Cen")
0419         return QString("Centaurus");
0420     if (code == "Cep")
0421         return QString("Cepheus");
0422     if (code == "Cet")
0423         return QString("Cetus");
0424     if (code == "Cha")
0425         return QString("Chamaeleon");
0426     if (code == "Cir")
0427         return QString("Circinus");
0428     if (code == "Col")
0429         return QString("Columba");
0430     if (code == "Com")
0431         return QString("Coma Berenices");
0432     if (code == "CrA")
0433         return QString("Corona Australis");
0434     if (code == "CrB")
0435         return QString("Corona Borealis");
0436     if (code == "Crv")
0437         return QString("Corvus");
0438     if (code == "Crt")
0439         return QString("Crater");
0440     if (code == "Cru")
0441         return QString("Crux");
0442     if (code == "Cyg")
0443         return QString("Cygnus");
0444     if (code == "Del")
0445         return QString("Delphinus");
0446     if (code == "Dor")
0447         return QString("Doradus");
0448     if (code == "Dra")
0449         return QString("Draco");
0450     if (code == "Equ")
0451         return QString("Equuleus");
0452     if (code == "Eri")
0453         return QString("Eridanus");
0454     if (code == "For")
0455         return QString("Fornax");
0456     if (code == "Gem")
0457         return QString("Gemini");
0458     if (code == "Gru")
0459         return QString("Grus");
0460     if (code == "Her")
0461         return QString("Hercules");
0462     if (code == "Hor")
0463         return QString("Horologium");
0464     if (code == "Hya")
0465         return QString("Hydra");
0466     if (code == "Hyi")
0467         return QString("Hydrus");
0468     if (code == "Ind")
0469         return QString("Indus");
0470     if (code == "Lac")
0471         return QString("Lacerta");
0472     if (code == "Leo")
0473         return QString("Leo");
0474     if (code == "LMi")
0475         return QString("Leo Minor");
0476     if (code == "Lep")
0477         return QString("Lepus");
0478     if (code == "Lib")
0479         return QString("Libra");
0480     if (code == "Lup")
0481         return QString("Lupus");
0482     if (code == "Lyn")
0483         return QString("Lynx");
0484     if (code == "Lyr")
0485         return QString("Lyra");
0486     if (code == "Men")
0487         return QString("Mensa");
0488     if (code == "Mic")
0489         return QString("Microscopium");
0490     if (code == "Mon")
0491         return QString("Monoceros");
0492     if (code == "Mus")
0493         return QString("Musca");
0494     if (code == "Nor")
0495         return QString("Norma");
0496     if (code == "Oct")
0497         return QString("Octans");
0498     if (code == "Oph")
0499         return QString("Ophiuchus");
0500     if (code == "Ori")
0501         return QString("Orion");
0502     if (code == "Pav")
0503         return QString("Pavo");
0504     if (code == "Peg")
0505         return QString("Pegasus");
0506     if (code == "Per")
0507         return QString("Perseus");
0508     if (code == "Phe")
0509         return QString("Phoenix");
0510     if (code == "Pic")
0511         return QString("Pictor");
0512     if (code == "Psc")
0513         return QString("Pisces");
0514     if (code == "PsA")
0515         return QString("Piscis Austrinus");
0516     if (code == "Pup")
0517         return QString("Puppis");
0518     if (code == "Pyx")
0519         return QString("Pyxis");
0520     if (code == "Ret")
0521         return QString("Reticulum");
0522     if (code == "Sge")
0523         return QString("Sagitta");
0524     if (code == "Sgr")
0525         return QString("Sagittarius");
0526     if (code == "Sco")
0527         return QString("Scorpius");
0528     if (code == "Scl")
0529         return QString("Sculptor");
0530     if (code == "Sct")
0531         return QString("Scutum");
0532     if (code == "Ser")
0533         return QString("Serpens");
0534     if (code == "Sex")
0535         return QString("Sextans");
0536     if (code == "Tau")
0537         return QString("Taurus");
0538     if (code == "Tel")
0539         return QString("Telescopium");
0540     if (code == "Tri")
0541         return QString("Triangulum");
0542     if (code == "TrA")
0543         return QString("Triangulum Australe");
0544     if (code == "Tuc")
0545         return QString("Tucana");
0546     if (code == "UMa")
0547         return QString("Ursa Major");
0548     if (code == "UMi")
0549         return QString("Ursa Minor");
0550     if (code == "Vel")
0551         return QString("Vela");
0552     if (code == "Vir")
0553         return QString("Virgo");
0554     if (code == "Vol")
0555         return QString("Volans");
0556     if (code == "Vul")
0557         return QString("Vulpecula");
0558     return code;
0559 }
0560 
0561 QString constNameToAbbrev(const QString &fullName_)
0562 {
0563     QString fullName = fullName_.toLower();
0564     if (fullName == "andromeda")
0565         return QString("And");
0566     if (fullName == "antlia")
0567         return QString("Ant");
0568     if (fullName == "apus")
0569         return QString("Aps");
0570     if (fullName == "aquarius")
0571         return QString("Aqr");
0572     if (fullName == "aquila")
0573         return QString("Aql");
0574     if (fullName == "ara")
0575         return QString("Ara");
0576     if (fullName == "aries")
0577         return QString("Ari");
0578     if (fullName == "auriga")
0579         return QString("Aur");
0580     if (fullName == "bootes")
0581         return QString("Boo");
0582     if (fullName == "caelum")
0583         return QString("Cae");
0584     if (fullName == "camelopardalis")
0585         return QString("Cam");
0586     if (fullName == "cancer")
0587         return QString("Cnc");
0588     if (fullName == "canes venatici")
0589         return QString("CVn");
0590     if (fullName == "canis major")
0591         return QString("CMa");
0592     if (fullName == "canis minor")
0593         return QString("CMi");
0594     if (fullName == "capricornus")
0595         return QString("Cap");
0596     if (fullName == "carina")
0597         return QString("Car");
0598     if (fullName == "cassiopeia")
0599         return QString("Cas");
0600     if (fullName == "centaurus")
0601         return QString("Cen");
0602     if (fullName == "cepheus")
0603         return QString("Cep");
0604     if (fullName == "cetus")
0605         return QString("Cet");
0606     if (fullName == "chamaeleon")
0607         return QString("Cha");
0608     if (fullName == "circinus")
0609         return QString("Cir");
0610     if (fullName == "columba")
0611         return QString("Col");
0612     if (fullName == "coma berenices")
0613         return QString("Com");
0614     if (fullName == "corona australis")
0615         return QString("CrA");
0616     if (fullName == "corona borealis")
0617         return QString("CrB");
0618     if (fullName == "corvus")
0619         return QString("Crv");
0620     if (fullName == "crater")
0621         return QString("Crt");
0622     if (fullName == "crux")
0623         return QString("Cru");
0624     if (fullName == "cygnus")
0625         return QString("Cyg");
0626     if (fullName == "delphinus")
0627         return QString("Del");
0628     if (fullName == "doradus")
0629         return QString("Dor");
0630     if (fullName == "draco")
0631         return QString("Dra");
0632     if (fullName == "equuleus")
0633         return QString("Equ");
0634     if (fullName == "eridanus")
0635         return QString("Eri");
0636     if (fullName == "fornax")
0637         return QString("For");
0638     if (fullName == "gemini")
0639         return QString("Gem");
0640     if (fullName == "grus")
0641         return QString("Gru");
0642     if (fullName == "hercules")
0643         return QString("Her");
0644     if (fullName == "horologium")
0645         return QString("Hor");
0646     if (fullName == "hydra")
0647         return QString("Hya");
0648     if (fullName == "hydrus")
0649         return QString("Hyi");
0650     if (fullName == "indus")
0651         return QString("Ind");
0652     if (fullName == "lacerta")
0653         return QString("Lac");
0654     if (fullName == "leo")
0655         return QString("Leo");
0656     if (fullName == "leo minor")
0657         return QString("LMi");
0658     if (fullName == "lepus")
0659         return QString("Lep");
0660     if (fullName == "libra")
0661         return QString("Lib");
0662     if (fullName == "lupus")
0663         return QString("Lup");
0664     if (fullName == "lynx")
0665         return QString("Lyn");
0666     if (fullName == "lyra")
0667         return QString("Lyr");
0668     if (fullName == "mensa")
0669         return QString("Men");
0670     if (fullName == "microscopium")
0671         return QString("Mic");
0672     if (fullName == "monoceros")
0673         return QString("Mon");
0674     if (fullName == "musca")
0675         return QString("Mus");
0676     if (fullName == "norma")
0677         return QString("Nor");
0678     if (fullName == "octans")
0679         return QString("Oct");
0680     if (fullName == "ophiuchus")
0681         return QString("Oph");
0682     if (fullName == "orion")
0683         return QString("Ori");
0684     if (fullName == "pavo")
0685         return QString("Pav");
0686     if (fullName == "pegasus")
0687         return QString("Peg");
0688     if (fullName == "perseus")
0689         return QString("Per");
0690     if (fullName == "phoenix")
0691         return QString("Phe");
0692     if (fullName == "pictor")
0693         return QString("Pic");
0694     if (fullName == "pisces")
0695         return QString("Psc");
0696     if (fullName == "piscis austrinus")
0697         return QString("PsA");
0698     if (fullName == "puppis")
0699         return QString("Pup");
0700     if (fullName == "pyxis")
0701         return QString("Pyx");
0702     if (fullName == "reticulum")
0703         return QString("Ret");
0704     if (fullName == "sagitta")
0705         return QString("Sge");
0706     if (fullName == "sagittarius")
0707         return QString("Sgr");
0708     if (fullName == "scorpius")
0709         return QString("Sco");
0710     if (fullName == "sculptor")
0711         return QString("Scl");
0712     if (fullName == "scutum")
0713         return QString("Sct");
0714     if (fullName == "serpens")
0715         return QString("Ser");
0716     if (fullName == "sextans")
0717         return QString("Sex");
0718     if (fullName == "taurus")
0719         return QString("Tau");
0720     if (fullName == "telescopium")
0721         return QString("Tel");
0722     if (fullName == "triangulum")
0723         return QString("Tri");
0724     if (fullName == "triangulum australe")
0725         return QString("TrA");
0726     if (fullName == "tucana")
0727         return QString("Tuc");
0728     if (fullName == "ursa major")
0729         return QString("UMa");
0730     if (fullName == "ursa minor")
0731         return QString("UMi");
0732     if (fullName == "vela")
0733         return QString("Vel");
0734     if (fullName == "virgo")
0735         return QString("Vir");
0736     if (fullName == "volans")
0737         return QString("Vol");
0738     if (fullName == "vulpecula")
0739         return QString("Vul");
0740     return fullName_;
0741 }
0742 
0743 QString constGenetiveToAbbrev(const QString &genetive_)
0744 {
0745     QString genetive = genetive_.toLower();
0746     if (genetive == "andromedae")
0747         return QString("And");
0748     if (genetive == "antliae")
0749         return QString("Ant");
0750     if (genetive == "apodis")
0751         return QString("Aps");
0752     if (genetive == "aquarii")
0753         return QString("Aqr");
0754     if (genetive == "aquilae")
0755         return QString("Aql");
0756     if (genetive == "arae")
0757         return QString("Ara");
0758     if (genetive == "arietis")
0759         return QString("Ari");
0760     if (genetive == "aurigae")
0761         return QString("Aur");
0762     if (genetive == "bootis")
0763         return QString("Boo");
0764     if (genetive == "caeli")
0765         return QString("Cae");
0766     if (genetive == "camelopardalis")
0767         return QString("Cam");
0768     if (genetive == "cancri")
0769         return QString("Cnc");
0770     if (genetive == "canum venaticorum")
0771         return QString("CVn");
0772     if (genetive == "canis majoris")
0773         return QString("CMa");
0774     if (genetive == "canis minoris")
0775         return QString("CMi");
0776     if (genetive == "capricorni")
0777         return QString("Cap");
0778     if (genetive == "carinae")
0779         return QString("Car");
0780     if (genetive == "cassiopeiae")
0781         return QString("Cas");
0782     if (genetive == "centauri")
0783         return QString("Cen");
0784     if (genetive == "cephei")
0785         return QString("Cep");
0786     if (genetive == "ceti")
0787         return QString("Cet");
0788     if (genetive == "chamaeleontis")
0789         return QString("Cha");
0790     if (genetive == "circini")
0791         return QString("Cir");
0792     if (genetive == "columbae")
0793         return QString("Col");
0794     if (genetive == "comae berenices")
0795         return QString("Com");
0796     if (genetive == "coronae austrinae")
0797         return QString("CrA");
0798     if (genetive == "coronae borealis")
0799         return QString("CrB");
0800     if (genetive == "corvi")
0801         return QString("Crv");
0802     if (genetive == "crateris")
0803         return QString("Crt");
0804     if (genetive == "crucis")
0805         return QString("Cru");
0806     if (genetive == "cygni")
0807         return QString("Cyg");
0808     if (genetive == "delphini")
0809         return QString("Del");
0810     if (genetive == "doradus")
0811         return QString("Dor");
0812     if (genetive == "draconis")
0813         return QString("Dra");
0814     if (genetive == "equulei")
0815         return QString("Equ");
0816     if (genetive == "eridani")
0817         return QString("Eri");
0818     if (genetive == "fornacis")
0819         return QString("For");
0820     if (genetive == "geminorum")
0821         return QString("Gem");
0822     if (genetive == "gruis")
0823         return QString("Gru");
0824     if (genetive == "herculis")
0825         return QString("Her");
0826     if (genetive == "horologii")
0827         return QString("Hor");
0828     if (genetive == "hydrae")
0829         return QString("Hya");
0830     if (genetive == "hydri")
0831         return QString("Hyi");
0832     if (genetive == "indi")
0833         return QString("Ind");
0834     if (genetive == "lacertae")
0835         return QString("Lac");
0836     if (genetive == "leonis")
0837         return QString("Leo");
0838     if (genetive == "leonis minoris")
0839         return QString("LMi");
0840     if (genetive == "leporis")
0841         return QString("Lep");
0842     if (genetive == "librae")
0843         return QString("Lib");
0844     if (genetive == "lupi")
0845         return QString("Lup");
0846     if (genetive == "lyncis")
0847         return QString("Lyn");
0848     if (genetive == "lyrae")
0849         return QString("Lyr");
0850     if (genetive == "mensae")
0851         return QString("Men");
0852     if (genetive == "microscopii")
0853         return QString("Mic");
0854     if (genetive == "monocerotis")
0855         return QString("Mon");
0856     if (genetive == "muscae")
0857         return QString("Mus");
0858     if (genetive == "normae")
0859         return QString("Nor");
0860     if (genetive == "octantis")
0861         return QString("Oct");
0862     if (genetive == "ophiuchi")
0863         return QString("Oph");
0864     if (genetive == "orionis")
0865         return QString("Ori");
0866     if (genetive == "pavonis")
0867         return QString("Pav");
0868     if (genetive == "pegasi")
0869         return QString("Peg");
0870     if (genetive == "persei")
0871         return QString("Per");
0872     if (genetive == "phoenicis")
0873         return QString("Phe");
0874     if (genetive == "pictoris")
0875         return QString("Pic");
0876     if (genetive == "piscium")
0877         return QString("Psc");
0878     if (genetive == "piscis austrini")
0879         return QString("PsA");
0880     if (genetive == "puppis")
0881         return QString("Pup");
0882     if (genetive == "pyxidis")
0883         return QString("Pyx");
0884     if (genetive == "reticuli")
0885         return QString("Ret");
0886     if (genetive == "sagittae")
0887         return QString("Sge");
0888     if (genetive == "sagittarii")
0889         return QString("Sgr");
0890     if (genetive == "scorpii")
0891         return QString("Sco");
0892     if (genetive == "sculptoris")
0893         return QString("Scl");
0894     if (genetive == "scuti")
0895         return QString("Sct");
0896     if (genetive == "serpentis")
0897         return QString("Ser");
0898     if (genetive == "sextantis")
0899         return QString("Sex");
0900     if (genetive == "tauri")
0901         return QString("Tau");
0902     if (genetive == "telescopii")
0903         return QString("Tel");
0904     if (genetive == "trianguli")
0905         return QString("Tri");
0906     if (genetive == "trianguli australis")
0907         return QString("TrA");
0908     if (genetive == "tucanae")
0909         return QString("Tuc");
0910     if (genetive == "ursae majoris")
0911         return QString("UMa");
0912     if (genetive == "ursae minoris")
0913         return QString("UMi");
0914     if (genetive == "velorum")
0915         return QString("Vel");
0916     if (genetive == "virginis")
0917         return QString("Vir");
0918     if (genetive == "volantis")
0919         return QString("Vol");
0920     if (genetive == "vulpeculae")
0921         return QString("Vul");
0922     return genetive_;
0923 }
0924 
0925 QString Logging::_filename;
0926 
0927 void Logging::UseFile()
0928 {
0929     if (_filename.isEmpty())
0930     {
0931         QDir dir;
0932         QString path =
0933             QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0934             .filePath("logs" + QDir::separator() + QDateTime::currentDateTime().toString("yyyy-MM-dd"));
0935         dir.mkpath(path);
0936         QString name =
0937             "log_" + QDateTime::currentDateTime().toString("HH-mm-ss") + ".txt";
0938         _filename = path + QDir::separator() + name;
0939 
0940         // Clear file contents
0941         QFile file(_filename);
0942         file.open(QFile::WriteOnly);
0943         file.close();
0944     }
0945 
0946     qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} "
0947                        "%{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{"
0948                        "endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] "
0949                        "%{if-category}[%{category}]%{endif} - %{message}");
0950     qInstallMessageHandler(File);
0951 }
0952 
0953 void Logging::File(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0954 {
0955     QFile file(_filename);
0956     if (file.open(QFile::Append | QIODevice::Text))
0957     {
0958         QTextStream stream(&file);
0959         Write(stream, type, context, msg);
0960     }
0961 }
0962 
0963 void Logging::UseStdout()
0964 {
0965     qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} "
0966                        "%{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{"
0967                        "endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] "
0968                        "%{if-category}[%{category}]%{endif} - %{message}");
0969     qInstallMessageHandler(Stdout);
0970 }
0971 
0972 void Logging::Stdout(QtMsgType type, const QMessageLogContext &context,
0973                      const QString &msg)
0974 {
0975     QTextStream stream(stdout, QIODevice::WriteOnly);
0976     Write(stream, type, context, msg);
0977 }
0978 
0979 void Logging::UseStderr()
0980 {
0981     qInstallMessageHandler(Stderr);
0982 }
0983 
0984 void Logging::Stderr(QtMsgType type, const QMessageLogContext &context,
0985                      const QString &msg)
0986 {
0987     QTextStream stream(stderr, QIODevice::WriteOnly);
0988     Write(stream, type, context, msg);
0989 }
0990 
0991 void Logging::Write(QTextStream &stream, QtMsgType type,
0992                     const QMessageLogContext &context, const QString &msg)
0993 {
0994     stream << QDateTime::currentDateTime().toString("[yyyy-MM-ddThh:mm:ss.zzz t ");
0995 
0996     switch (type)
0997     {
0998         case QtInfoMsg:
0999             stream << "INFO ]";
1000             break;
1001         case QtDebugMsg:
1002             stream << "DEBG ]";
1003             break;
1004         case QtWarningMsg:
1005             stream << "WARN ]";
1006             break;
1007         case QtCriticalMsg:
1008             stream << "CRIT ]";
1009             break;
1010         case QtFatalMsg:
1011             stream << "FATL ]";
1012             break;
1013         default:
1014             stream << "UNKN ]";
1015     }
1016 
1017     stream << "[" << qSetFieldWidth(30) << context.category << qSetFieldWidth(0)
1018            << "] - ";
1019     stream << msg << '\n';
1020     stream.flush();
1021     //stream << qFormatLogMessage(type, context, msg) << Qt::endl;
1022 }
1023 
1024 void Logging::UseDefault()
1025 {
1026     qInstallMessageHandler(nullptr);
1027 }
1028 
1029 void Logging::Disable()
1030 {
1031     qInstallMessageHandler(Disabled);
1032 }
1033 
1034 void Logging::Disabled(QtMsgType, const QMessageLogContext &, const QString &) {}
1035 
1036 void Logging::SyncFilterRules()
1037 {
1038     //    QString rules = QString("org.kde.kstars.ekos.debug=%1\n"
1039     //                            "org.kde.kstars.indi.debug=%2\n"
1040     //                            "org.kde.kstars.fits.debug=%3\n"
1041     //                            "org.kde.kstars.ekos.capture.debug=%4\n"
1042     //                            "org.kde.kstars.ekos.focus.debug=%5\n"
1043     //                            "org.kde.kstars.ekos.guide.debug=%6\n"
1044     //                            "org.kde.kstars.ekos.align.debug=%7\n"
1045     //                            "org.kde.kstars.ekos.mount.debug=%8\n"
1046     //                            "org.kde.kstars.ekos.scheduler.debug=%9\n").arg(
1047     //                        Options::verboseLogging() ? "true" : "false",
1048     //                        Options::iNDILogging() ? "true" : "false",
1049     //                        Options::fITSLogging() ? "true" : "false",
1050     //                        Options::captureLogging() ? "true" : "false",
1051     //                        Options::focusLogging() ? "true" : "false",
1052     //                        Options::guideLogging() ? "true" : "false",
1053     //                        Options::alignmentLogging() ? "true" : "false",
1054     //                        Options::mountLogging() ? "true" : "false",
1055     //                        Options::schedulerLogging() ? "true" : "false")
1056     //            .append(QString("org.kde.kstars.ekos.observatory.debug=%2\n"
1057     //                            "org.kde.kstars.debug=%1").arg(
1058     //                        Options::verboseLogging() ? "true" : "false",
1059     //                        Options::observatoryLogging() ? "true" : "false"));
1060 
1061     QStringList rules;
1062 
1063     rules << "org.kde.kstars.ekos.debug"
1064           << (Options::verboseLogging() ? "true" : "false");
1065     rules << "org.kde.kstars.indi.debug" << (Options::iNDILogging() ? "true" : "false");
1066     rules << "org.kde.kstars.fits.debug" << (Options::fITSLogging() ? "true" : "false");
1067     rules << "org.kde.kstars.ekos.capture.debug"
1068           << (Options::captureLogging() ? "true" : "false");
1069     rules << "org.kde.kstars.ekos.focus.debug"
1070           << (Options::focusLogging() ? "true" : "false");
1071     rules << "org.kde.kstars.ekos.guide.debug"
1072           << (Options::guideLogging() ? "true" : "false");
1073     rules << "org.kde.kstars.ekos.align.debug"
1074           << (Options::alignmentLogging() ? "true" : "false");
1075     rules << "org.kde.kstars.ekos.mount.debug"
1076           << (Options::mountLogging() ? "true" : "false");
1077     rules << "org.kde.kstars.ekos.scheduler.debug"
1078           << (Options::schedulerLogging() ? "true" : "false");
1079     rules << "org.kde.kstars.ekos.observatory.debug"
1080           << (Options::observatoryLogging() ? "true" : "false");
1081     rules << "org.kde.kstars.debug" << (Options::verboseLogging() ? "true" : "false");
1082 
1083     QString formattedRules;
1084     for (int i = 0; i < rules.size(); i += 2)
1085         formattedRules.append(QString("%1=%2\n").arg(rules[i], rules[i + 1]));
1086 
1087     QLoggingCategory::setFilterRules(formattedRules);
1088 }
1089 
1090 /**
1091   This method provides a centralized location for the default paths to important external files used in the Options
1092   on different operating systems.  Note that on OS X, if the user builds the app without indi, astrometry, and xplanet internally
1093   then the options below will be used.  If the user drags the app from a dmg and has to install the KStars data directory,
1094   then most of these paths will be overwritten since it is preferred to use the internal versions.
1095 **/
1096 
1097 QString getDefaultPath(const QString &option)
1098 {
1099     // We support running within Snaps, Flatpaks, and AppImage
1100     // The path should accomodate the differences between the different
1101     // packaging solutions
1102     QString snap   = QProcessEnvironment::systemEnvironment().value("SNAP");
1103     QString flat   = QProcessEnvironment::systemEnvironment().value("FLATPAK_DEST");
1104     QString appimg = QProcessEnvironment::systemEnvironment().value("APPDIR");
1105 
1106     // User prefix is the primary mounting point
1107     QString userPrefix = "/usr";
1108     // By default /usr is the prefix
1109     QString prefix = userPrefix;
1110     // Detect if we are within an App Image
1111     if (QProcessEnvironment::systemEnvironment().value("APPIMAGE").isEmpty() == false &&
1112             appimg.isEmpty() == false)
1113         prefix = appimg + userPrefix;
1114     else if (flat.isEmpty() == false)
1115         // Detect if we are within a Flatpak
1116         prefix = flat;
1117     // Detect if we are within a snap
1118     else if (snap.isEmpty() == false)
1119         prefix = snap + userPrefix;
1120 
1121     if (option == "fitsDir")
1122     {
1123         return QDir::toNativeSeparators(QDir::homePath());
1124     }
1125     else if (option == "indiServer")
1126     {
1127 #if defined(INDI_PREFIX)
1128         return QString(INDI_PREFIX "/bin/indiserver");
1129 #elif defined(Q_OS_OSX)
1130         return "/usr/local/bin/indiserver";
1131 #endif
1132         return prefix + "/bin/indiserver";
1133     }
1134     else if (option == "PlaceholderFormat")
1135     {
1136 #if defined(Q_OS_WIN)
1137         return "\\%t\\%T\\%F\\%t_%T_%F";
1138 #else
1139         return "/%t/%T/%F/%t_%T_%F";
1140 #endif
1141     }
1142     else if (option == "INDIHubAgent")
1143     {
1144 #if defined(INDI_PREFIX)
1145         return QString(INDI_PREFIX "/bin/indihub-agent");
1146 #elif defined(Q_OS_OSX)
1147         return "/usr/local/bin/indihub-agent";
1148 #endif
1149         return prefix + "/bin/indihub-agent";
1150     }
1151     else if (option == "indiDriversDir")
1152     {
1153 #if defined(INDI_PREFIX)
1154         return QString(INDI_PREFIX "/share/indi");
1155 #elif defined(Q_OS_OSX)
1156         return "/usr/local/share/indi";
1157 #elif defined(Q_OS_LINUX)
1158         return prefix + "/share/indi";
1159 #else
1160         return QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, "indi",
1161                                       QStandardPaths::LocateDirectory);
1162 #endif
1163     }
1164     else if (option == "AstrometrySolverBinary")
1165     {
1166 #if defined(ASTROMETRY_PREFIX)
1167         return QString(ASTROMETRY_PREFIX "/bin/solve-field");
1168 #elif defined(Q_OS_OSX)
1169         return "/usr/local/bin/solve-field";
1170 #elif defined(Q_OS_WIN)
1171         return QDir::homePath() +
1172                "/AppData/Local/cygwin_ansvr/lib/astrometry/bin/solve-field.exe";
1173 #endif
1174         return prefix + "/bin/solve-field";
1175     }
1176     else if (option == "WatneyBinary")
1177     {
1178 #if defined(ASTROMETRY_PREFIX)
1179         return QString(ASTROMETRY_PREFIX "/opt/watney/watney-solve");
1180 #elif defined(Q_OS_OSX)
1181         return "/usr/local/bin/watney-solve";
1182 #elif defined(Q_OS_WIN)
1183         return "C:/watney/watney-solve.exe";
1184 #endif
1185         return prefix + "/opt/watney/watney-solve";
1186     }
1187     else if (option == "SextractorBinary")
1188     {
1189 #if defined(SEXTRACTOR_PREFIX)
1190         return QString(SEXTRACTOR_PREFIX "/bin/sextractor");
1191 #elif defined(Q_OS_OSX)
1192         return "/usr/local/bin/sex";
1193 #endif
1194         return prefix + "/bin/sextractor";
1195     }
1196     else if (option == "AstrometryWCSInfo")
1197     {
1198 #if defined(ASTROMETRY_PREFIX)
1199         return QString(ASTROMETRY_PREFIX "/bin/wcsinfo");
1200 #elif defined(Q_OS_OSX)
1201         return "/usr/local/bin/wcsinfo";
1202 #elif defined(Q_OS_WIN)
1203         return QDir::homePath() +
1204                "/AppData/Local/cygwin_ansvr/lib/astrometry/bin/wcsinfo.exe";
1205 #endif
1206         return prefix + "/bin/wcsinfo";
1207     }
1208     else if (option == "AstrometryConfFile")
1209     {
1210 #if defined(ASTROMETRY_CONF_IN_PREFIX) && defined(ASTROMETRY_PREFIX)
1211         return QString(ASTROMETRY_PREFIX "/etc/astrometry.cfg");
1212 #elif defined(Q_OS_OSX)
1213         return "/usr/local/etc/astrometry.cfg";
1214 #elif defined(Q_OS_WIN)
1215         return QDir::homePath() +
1216                "/AppData/Local/cygwin_ansvr/etc/astrometry/backend.cfg";
1217 #endif
1218         // We move /usr
1219         prefix.remove(userPrefix);
1220         return prefix + "/etc/astrometry.cfg";
1221     }
1222     else if (option == "AstrometryIndexFileLocation")
1223     {
1224 #if defined(ASTROMETRY_PREFIX)
1225         return QString(ASTROMETRY_PREFIX "/share/astrometry");
1226 #elif defined(Q_OS_OSX)
1227         return QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
1228                .filePath("Astrometry/");
1229 #endif
1230         return prefix + "/share/astrometry/";
1231     }
1232     else if (option == "AstrometryLogFilepath")
1233     {
1234         return QDir::tempPath() + "/astrometryLog.txt";
1235     }
1236     else if (option == "XplanetPath")
1237     {
1238 #if defined(XPLANET_PREFIX)
1239         return QString(XPLANET_PREFIX "/bin/xplanet");
1240 #elif defined(Q_OS_OSX)
1241         return "/usr/local/bin/xplanet";
1242 #endif
1243         return prefix + "/bin/xplanet";
1244     }
1245     else if (option == "ASTAP")
1246     {
1247 #if defined(Q_OS_OSX)
1248         return "/Applications/ASTAP.app/Contents/MacOS/astap";
1249 #elif defined(Q_OS_WIN)
1250         return "C:/Program Files/astap/astap.exe";
1251 #endif
1252         return "/opt/astap/astap";
1253     }
1254 
1255     return QString();
1256 }
1257 
1258 QStringList getAstrometryDefaultIndexFolderPaths()
1259 {
1260     QStringList folderPaths;
1261     const QString confDir =
1262         QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
1263         .filePath(QLatin1String("astrometry"));
1264     folderPaths << confDir;
1265     // Check if directory already exists, if it doesn't create one
1266     QDir writableDir(confDir);
1267     if (writableDir.exists() == false)
1268     {
1269         if (writableDir.mkdir(confDir) == false)
1270         {
1271             qCCritical(KSTARS) << "Failed to create local astrometry directory";
1272             folderPaths.clear();
1273         }
1274     }
1275 
1276 #ifdef HAVE_STELLARSOLVER
1277     folderPaths.append(StellarSolver::getDefaultIndexFolderPaths());
1278 #endif
1279     return folderPaths;
1280 }
1281 
1282 #if defined(Q_OS_OSX)
1283 //Note that this will copy and will not overwrite, so that the user's changes in the files are preserved.
1284 void copyResourcesFolderFromAppBundle(QString folder)
1285 {
1286     QString folderLocation = QStandardPaths::locate(
1287                                  QStandardPaths::GenericDataLocation, folder, QStandardPaths::LocateDirectory);
1288     QDir folderSourceDir;
1289     if (folder == "kstars")
1290         folderSourceDir =
1291             QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars")
1292             .absolutePath();
1293     else
1294         folderSourceDir =
1295             QDir(QCoreApplication::applicationDirPath() + "/../Resources/" + folder)
1296             .absolutePath();
1297     if (folderSourceDir.exists())
1298     {
1299         folderLocation =
1300             QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + '/' +
1301             folder;
1302         QDir writableDir;
1303         writableDir.mkdir(folderLocation);
1304         copyRecursively(folderSourceDir.absolutePath(), folderLocation);
1305     }
1306 }
1307 
1308 bool setupMacKStarsIfNeeded() // This method will return false if the KStars data directory doesn't exist when it's done
1309 {
1310     //This will copy the locale folder, the notifications folder, and the sounds folder and any missing files in them to Application Support if needed.
1311     copyResourcesFolderFromAppBundle("locale");
1312     copyResourcesFolderFromAppBundle("knotifications5");
1313     copyResourcesFolderFromAppBundle("sounds");
1314 
1315     //This will copy the KStars data directory
1316     copyResourcesFolderFromAppBundle("kstars");
1317     copyResourcesFolderFromAppBundle("kstars/xplanet");
1318 
1319     if (Options::kStarsFirstRun())
1320     {
1321         //This sets some important OS X options.
1322         Options::setIndiServerIsInternal(true);
1323         Options::setIndiServer("*Internal INDI Server*");
1324         Options::setIndiDriversAreInternal(true);
1325         Options::setIndiDriversDir("*Internal INDI Drivers*");
1326         Options::setXplanetIsInternal(true);
1327         Options::setXplanetPath("*Internal XPlanet*");
1328     }
1329 
1330     QString dataLocation = QStandardPaths::locate(
1331                                QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
1332     if (dataLocation.isEmpty()) //If there is no kstars user data directory
1333         return false;
1334 
1335     return true;
1336 }
1337 
1338 bool configureAstrometry()
1339 {
1340     QStringList astrometryDataDirs = getAstrometryDataDirs();
1341     if (astrometryDataDirs.count() == 0)
1342         return false;
1343     QString defaultAstrometryDataDir = getDefaultPath("AstrometryIndexFileLocation");
1344     if (astrometryDataDirs.contains("IndexFileLocationNotYetSet"))
1345         replaceIndexFileNotYetSet();
1346     if (QDir(defaultAstrometryDataDir).exists() == false)
1347     {
1348         if (KMessageBox::warningYesNo(
1349                     nullptr,
1350                     i18n("The selected Astrometry Index File Location:\n %1 \n does not "
1351                          "exist.  Do you want to make the directory?",
1352                          defaultAstrometryDataDir),
1353                     i18n("Make Astrometry Index File Directory?")) == KMessageBox::Yes)
1354         {
1355             if (QDir(defaultAstrometryDataDir).mkdir(defaultAstrometryDataDir))
1356             {
1357                 KSNotification::info(
1358                     i18n("The Default Astrometry Index File Location was created."));
1359             }
1360             else
1361             {
1362                 KSNotification::sorry(
1363                     i18n("The Default Astrometry Index File Directory does not exist and "
1364                          "was not able to be created."));
1365             }
1366         }
1367         else
1368         {
1369             return false;
1370         }
1371     }
1372 
1373     return true;
1374 }
1375 
1376 bool replaceIndexFileNotYetSet()
1377 {
1378     QString confPath = KSUtils::getAstrometryConfFilePath();
1379 
1380     QFile confFile(confPath);
1381     QString contents;
1382     if (confFile.open(QIODevice::ReadOnly) == false)
1383     {
1384         KSNotification::error(i18n("Astrometry Configuration File Read Error."));
1385         return false;
1386     }
1387     else
1388     {
1389         QByteArray fileContent = confFile.readAll();
1390         confFile.close();
1391         QString contents = QString::fromLatin1(fileContent);
1392         contents.replace("IndexFileLocationNotYetSet",
1393                          getDefaultPath("AstrometryIndexFileLocation"));
1394 
1395         if (confFile.open(QIODevice::WriteOnly) == false)
1396         {
1397             KSNotification::error(
1398                 i18n("Internal Astrometry Configuration File Write Error."));
1399             return false;
1400         }
1401         else
1402         {
1403             QTextStream out(&confFile);
1404             out << contents;
1405             confFile.close();
1406         }
1407     }
1408     return true;
1409 }
1410 
1411 bool copyRecursively(QString sourceFolder, QString destFolder)
1412 {
1413     QDir sourceDir(sourceFolder);
1414 
1415     if (!sourceDir.exists())
1416         return false;
1417 
1418     QDir destDir(destFolder);
1419     if (!destDir.exists())
1420         destDir.mkdir(destFolder);
1421 
1422     QStringList files = sourceDir.entryList(QDir::Files);
1423     for (int i = 0; i < files.count(); i++)
1424     {
1425         QString srcName  = sourceFolder + QDir::separator() + files[i];
1426         QString destName = destFolder + QDir::separator() + files[i];
1427         QFile::copy(srcName, destName); //Note this does not overwrite files
1428     }
1429 
1430     files.clear();
1431     files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
1432     for (int i = 0; i < files.count(); i++)
1433     {
1434         QString srcName  = sourceFolder + QDir::separator() + files[i];
1435         QString destName = destFolder + QDir::separator() + files[i];
1436         copyRecursively(srcName, destName);
1437     }
1438 
1439     return true;
1440 }
1441 #endif
1442 
1443 QString getAstrometryConfFilePath()
1444 {
1445     return Options::astrometryConfFile();
1446 }
1447 
1448 QStringList getAstrometryDataDirs()
1449 {
1450     QStringList optionsDataDirs = Options::astrometryIndexFolderList();
1451 
1452     bool updated = false;
1453 
1454     // Cleaning up the list of directories in options.
1455     for (int dir = 0; dir < optionsDataDirs.count(); dir++)
1456     {
1457         QString optionsDataDirName = optionsDataDirs.at(dir);
1458         QDir optionsDataDir(optionsDataDirName);
1459         if (optionsDataDir.exists())
1460         {
1461             //This will replace directory names that aren't the absolute path
1462             if (optionsDataDir.absolutePath() != optionsDataDirName)
1463             {
1464                 optionsDataDirs.replace(dir, optionsDataDir.absolutePath());
1465                 updated = true;
1466             }
1467         }
1468         else
1469         {
1470             //This removes directories that do not exist from the list.
1471             optionsDataDirs.removeAt(dir);
1472             dir--;
1473             updated = true;
1474         }
1475     }
1476 
1477     //This will load the conf file if it exists
1478     QFile confFile(KSUtils::getAstrometryConfFilePath());
1479     if (confFile.open(QIODevice::ReadOnly))
1480     {
1481         QStringList confDataDirs;
1482         QTextStream in(&confFile);
1483         QString line;
1484 
1485         //This will find the index file paths in the conf file
1486         while (!in.atEnd())
1487         {
1488             line = in.readLine();
1489             if (line.isEmpty() || line.startsWith('#'))
1490                 continue;
1491 
1492             line = line.trimmed();
1493             if (line.startsWith(QLatin1String("add_path")))
1494             {
1495                 confDataDirs << line.mid(9).trimmed();
1496             }
1497         }
1498 
1499         //This will search through the paths and compare them to the index folder list
1500         //It will add them if they aren't in there.
1501         for (QString astrometryDataDirName : confDataDirs)
1502         {
1503             QDir astrometryDataDir(astrometryDataDirName);
1504             //This rejects any that do not exist
1505             if (!astrometryDataDir.exists())
1506                 continue;
1507             QString astrometryDataDirPath = astrometryDataDir.absolutePath();
1508             if (!optionsDataDirs.contains(astrometryDataDirPath))
1509             {
1510                 optionsDataDirs.append(astrometryDataDirPath);
1511                 updated = true;
1512             }
1513         }
1514     }
1515 
1516     //This will remove any duplicate entries.
1517     if (optionsDataDirs.removeDuplicates() != 0)
1518         updated = true;
1519 
1520     //This updates the list in Options if it changed.
1521     if (updated)
1522         Options::setAstrometryIndexFolderList(optionsDataDirs);
1523 
1524     return optionsDataDirs;
1525 }
1526 
1527 bool addAstrometryDataDir(const QString &dataDir)
1528 {
1529     //This will need to be fixed!
1530     //if(Options::astrometryIndexFileLocation() != dataDir)
1531     //    Options::setAstrometryIndexFileLocation(dataDir);
1532 
1533     QString confPath               = KSUtils::getAstrometryConfFilePath();
1534     QStringList astrometryDataDirs = getAstrometryDataDirs();
1535 
1536     QFile confFile(confPath);
1537     QString contents;
1538     if (confFile.open(QIODevice::ReadOnly) == false)
1539     {
1540         KSNotification::error(i18n("Astrometry Configuration File Read Error."));
1541         return false;
1542     }
1543     else
1544     {
1545         QTextStream in(&confFile);
1546         QString line;
1547         bool foundSpot = false;
1548         while (!in.atEnd())
1549         {
1550             line = in.readLine();
1551 
1552             if (line.trimmed().startsWith(QLatin1String("add_path")))
1553             {
1554                 if (!foundSpot)
1555                 {
1556                     foundSpot = true;
1557                     for (QString astrometryDataDir : astrometryDataDirs)
1558                         contents += "add_path " + astrometryDataDir + '\n';
1559                     contents += "add_path " + dataDir + '\n';
1560                 }
1561                 else
1562                 {
1563                     //Do not keep adding the other add_paths because they just got added in the seciton above.
1564                 }
1565             }
1566             else
1567             {
1568                 contents += line + '\n';
1569             }
1570         }
1571         if (!foundSpot)
1572         {
1573             for (QString astrometryDataDir : astrometryDataDirs)
1574                 contents += "add_path " + astrometryDataDir + '\n';
1575             contents += "add_path " + dataDir + '\n';
1576         }
1577 
1578         confFile.close();
1579 
1580         if (confFile.open(QIODevice::WriteOnly) == false)
1581         {
1582             KSNotification::error(
1583                 i18n("Internal Astrometry Configuration File Write Error."));
1584             return false;
1585         }
1586         else
1587         {
1588             QTextStream out(&confFile);
1589             out << contents;
1590             confFile.close();
1591         }
1592     }
1593     return true;
1594 }
1595 
1596 bool removeAstrometryDataDir(const QString &dataDir)
1597 {
1598     QString confPath               = KSUtils::getAstrometryConfFilePath();
1599     QStringList astrometryDataDirs = getAstrometryDataDirs();
1600 
1601     QFile confFile(confPath);
1602     QString contents;
1603     if (confFile.open(QIODevice::ReadOnly) == false)
1604     {
1605         KSNotification::error(i18n("Astrometry Configuration File Read Error."));
1606         return false;
1607     }
1608     else
1609     {
1610         QTextStream in(&confFile);
1611         QString line;
1612         while (!in.atEnd())
1613         {
1614             line = in.readLine();
1615             if (line.mid(9).trimmed() != dataDir)
1616             {
1617                 contents += line + '\n';
1618             }
1619         }
1620         confFile.close();
1621 
1622         if (confFile.open(QIODevice::WriteOnly) == false)
1623         {
1624             KSNotification::error(
1625                 i18n("Internal Astrometry Configuration File Write Error."));
1626             return false;
1627         }
1628         else
1629         {
1630             QTextStream out(&confFile);
1631             out << contents;
1632             confFile.close();
1633         }
1634     }
1635     return true;
1636 }
1637 
1638 QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataFields,
1639                              const QVector<JPLFilter> &filters)
1640 {
1641     /* For example:
1642       https://ssd-api.jpl.nasa.gov/sbdb_query.api?fields=full_name,neo,H,G,diameter,extent,albedo,rot_per,orbit_id,epoch.mjd,e,a,q,i,om,w,ma,per.y,moid,class&full-prec=false&sb-cdata=%7B%22AND%22:%5B%22H%7CLT%7C10%22%5D%7D&sb-kind=a&sb-ns=n&www=1
1643     */
1644 
1645     QByteArray query("sb-kind=" + kind + "&full-prec=true");
1646 
1647     // Apply filters:
1648     if (filters.size() > 0)
1649     {
1650         QByteArray filter_string("{\"AND\":[");
1651         for (const auto &item : filters)
1652         {
1653             filter_string += "\"" + item.item + "|" + item.op + "|" + item.value + "\",";
1654         }
1655 
1656         filter_string.chop(1);
1657         filter_string += "]}";
1658 
1659         query += "&sb-cdata=" + filter_string;
1660     }
1661 
1662     // Apply query data fields...
1663     query += "&fields=" + dataFields;
1664 
1665     return query;
1666 }
1667 
1668 bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMessage)
1669 {
1670 #ifndef HAVE_LIBRAW
1671     errorMessage = i18n("Unable to find dcraw and cjpeg. Please install the required "
1672                         "tools to convert CR2/NEF to JPEG.");
1673     return false;
1674 #else
1675     int ret = 0;
1676     // Creation of image processing object
1677     LibRaw RawProcessor;
1678 
1679     // Let us open the file
1680     if ((ret = RawProcessor.open_file(rawImage.toLatin1().data())) != LIBRAW_SUCCESS)
1681     {
1682         errorMessage = i18n("Cannot open %1: %2", rawImage, libraw_strerror(ret));
1683         RawProcessor.recycle();
1684         return false;
1685     }
1686 
1687     // Let us unpack the thumbnail
1688     if ((ret = RawProcessor.unpack_thumb()) != LIBRAW_SUCCESS)
1689     {
1690         errorMessage = i18n("Cannot unpack_thumb %1: %2", rawImage, libraw_strerror(ret));
1691         RawProcessor.recycle();
1692         return false;
1693     }
1694     else
1695         // We have successfully unpacked the thumbnail, now let us write it to a file
1696     {
1697         //snprintf(thumbfn,sizeof(thumbfn),"%s.%s",av[i],T.tformat == LIBRAW_THUMBNAIL_JPEG ? "thumb.jpg" : "thumb.ppm");
1698         if (LIBRAW_SUCCESS !=
1699                 (ret = RawProcessor.dcraw_thumb_writer(output.toLatin1().data())))
1700         {
1701             errorMessage = i18n("Cannot write %s %1: %2", output, libraw_strerror(ret));
1702             RawProcessor.recycle();
1703             return false;
1704         }
1705     }
1706     return true;
1707 #endif
1708 }
1709 
1710 double getAvailableRAM()
1711 {
1712 #if defined(Q_OS_OSX)
1713     int mib[] = { CTL_HW, HW_MEMSIZE };
1714     size_t length;
1715     length = sizeof(int64_t);
1716     int64_t RAMcheck;
1717     if (sysctl(mib, 2, &RAMcheck, &length, NULL, 0))
1718         return false; // On Error
1719     //Until I can figure out how to get free RAM on Mac
1720     return RAMcheck;
1721 #elif defined(Q_OS_LINUX)
1722     QProcess p;
1723     p.start("awk", QStringList() << "/MemAvailable/ { print $2 }"
1724             << "/proc/meminfo");
1725     p.waitForFinished();
1726     QString memory = p.readAllStandardOutput();
1727     p.close();
1728     //kB to bytes
1729     return (memory.toLong() * 1024.0);
1730 #elif defined(Q_OS_WIN32)
1731     MEMORYSTATUSEX memory_status;
1732     ZeroMemory(&memory_status, sizeof(MEMORYSTATUSEX));
1733     memory_status.dwLength = sizeof(MEMORYSTATUSEX);
1734     if (GlobalMemoryStatusEx(&memory_status))
1735     {
1736         return memory_status.ullAvailPhys;
1737     }
1738     else
1739     {
1740         return 0;
1741     }
1742 #endif
1743     return 0;
1744 }
1745 
1746 JPLParser::JPLParser(const QString &path)
1747 {
1748     QFile jpl_file(path);
1749     if (!jpl_file.open(QIODevice::ReadOnly))
1750     {
1751         throw std::runtime_error("Could not open file.");
1752     }
1753 
1754     const auto &ast_json = QJsonDocument::fromJson(jpl_file.readAll());
1755     const auto &fields   = ast_json["fields"].toArray();
1756     m_data               = ast_json["data"].toArray();
1757 
1758     {
1759         int i = 0;
1760         for (const auto &field : fields)
1761         {
1762             m_field_map[field.toString()] = i++;
1763         }
1764     }
1765 }
1766 
1767 MPCParser::MPCParser(const QString &path)
1768 {
1769     QFile mpc_file(path);
1770     if (!mpc_file.open(QIODevice::ReadOnly))
1771     {
1772         throw std::runtime_error("Could not open file.");
1773     }
1774 
1775     gzFile file = gzopen(path.toLatin1().constData(), "r");
1776     if (file)
1777     {
1778 
1779         QByteArray data;
1780         const uint32_t len = 32768;
1781 
1782         while (!gzeof(file))
1783         {
1784             char buffer[len] = {0};
1785             int bytes_read = gzread(file, buffer, len - 1);
1786             if (bytes_read < 0)
1787                 break;
1788             buffer[bytes_read] = 0;
1789             data.append(buffer);
1790         }
1791 
1792         gzclose(file);
1793         const auto &ast_json = QJsonDocument::fromJson(data);
1794         m_data               = ast_json.array();
1795     }
1796     else
1797         qCritical(KSTARS) << "Failed to read MPC comets data file" << path;
1798 }
1799 
1800 void setGlobalSettings(const QVariantMap &settings)
1801 {
1802     for (auto &key : settings.keys())
1803     {
1804         auto property = key;
1805         // Anything starting with kcfg_ must be processed to remove the
1806         // prefix and ensure first letter is lower case.
1807         if (property.startsWith("kcfg_"))
1808         {
1809             property.remove("kcfg_");
1810             property.replace(0, 1, property.at(0).toLower());
1811         }
1812         Options::self()->setProperty(property.toLatin1(), settings[key]);
1813     }
1814 
1815     Options::self()->save();
1816 }
1817 
1818 QString sanitize(const QString &text)
1819 {
1820     static const QRegularExpression re1("\\s|/|\\(|\\)|:|\\*|\\+|~|\"" );
1821     static const QRegularExpression re2("_{2,}");
1822     static const QRegularExpression re3("_$");
1823 
1824     QString sanitized = text;
1825     if (sanitized != i18n("unnamed"))
1826     {
1827         // Remove illegal characters that can be problematic
1828         sanitized = sanitized.replace(re1, "_" )
1829                     // Remove any two or more __
1830                     .replace( re2, "_")
1831                     // Remove any _ at the end
1832                     .replace( re3, "");
1833     }
1834     return sanitized;
1835 }
1836 
1837 double rangePA(double pa)
1838 {
1839     while (pa > 180)
1840         pa -= 360;
1841     while (pa < -180)
1842         pa += 360;
1843     return pa;
1844 }
1845 
1846 double range360(double r)
1847 {
1848     double res = r;
1849     while (res < 0.00)
1850         res += 360.00;
1851     while (res > 359.99)  // uniqueness of angle (360 = 0)
1852         res -= 360.00;
1853     return res;
1854 }
1855 
1856 double rotationToPositionAngle(double value)
1857 {
1858     double pa = value + 180;
1859     while (pa > 180)
1860         pa -= 360;
1861     while (pa < -180)
1862         pa += 360;
1863     return pa;
1864 }
1865 
1866 double positionAngleToRotation(double value)
1867 {
1868     double rotation = value - 180;
1869     while (rotation > 180)
1870         rotation -= 360;
1871     while (rotation < -180)
1872         rotation += 360;
1873     return rotation;
1874 }
1875 
1876 
1877 } // namespace KSUtils