File indexing completed on 2024-04-21 14:43:58

0001 /*
0002     SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "fov.h"
0008 
0009 #include "geolocation.h"
0010 #include "kspaths.h"
0011 #ifndef KSTARS_LITE
0012 #include "kstars.h"
0013 #endif
0014 #include "kstarsdata.h"
0015 #include "Options.h"
0016 #include "skymap.h"
0017 #include "projections/projector.h"
0018 #include "fovadaptor.h"
0019 
0020 #include <QPainter>
0021 #include <QTextStream>
0022 #include <QFile>
0023 #include <QDebug>
0024 #include <QStandardPaths>
0025 
0026 #include <algorithm>
0027 
0028 QList<FOV *> FOVManager::m_FOVs;
0029 int FOV::m_ID = 1;
0030 
0031 FOVManager::~FOVManager()
0032 {
0033     qDeleteAll(m_FOVs);
0034 }
0035 
0036 QList<FOV *> FOVManager::defaults()
0037 {
0038     QList<FOV *> fovs;
0039     fovs << new FOV(i18nc("use field-of-view for binoculars", "7x35 Binoculars"), 558, 558, 0, 0, 0, FOV::CIRCLE,
0040                     "#AAAAAA")
0041          << new FOV(i18nc("use a Telrad field-of-view indicator", "Telrad"), 30, 30, 0, 0, 0, FOV::BULLSEYE, "#AA0000")
0042          << new FOV(i18nc("use 1-degree field-of-view indicator", "One Degree"), 60, 60, 0, 0, 0, FOV::CIRCLE,
0043                     "#AAAAAA")
0044          << new FOV(i18nc("use HST field-of-view indicator", "HST WFPC2"), 2.4, 2.4, 0, 0, 0, FOV::SQUARE, "#AAAAAA")
0045          << new FOV(i18nc("use Radiotelescope HPBW", "30m at 1.3cm"), 1.79, 1.79, 0, 0, 0, FOV::SQUARE, "#AAAAAA");
0046     return fovs;
0047 }
0048 
0049 bool FOVManager::save()
0050 {
0051     QFile f;
0052 
0053     // TODO: Move FOVs to user database instead of file!!
0054     f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("fov.dat"));
0055 
0056     if (!f.open(QIODevice::WriteOnly))
0057     {
0058         qDebug() << Q_FUNC_INFO << "Could not open fov.dat.";
0059         return false;
0060     }
0061 
0062     QTextStream ostream(&f);
0063     foreach (FOV *fov, m_FOVs)
0064     {
0065         ostream << fov->name() << ':' << fov->sizeX() << ':' << fov->sizeY() << ':' << fov->offsetX() << ':'
0066                 << fov->offsetY() << ':' << fov->PA() << ':' << QString::number(fov->shape())
0067                 << ':' << fov->color()
0068                 << ':' << (fov->lockCelestialPole() ? 1 : 0)
0069                 << '\n';
0070     }
0071     f.close();
0072 
0073     return true;
0074 }
0075 
0076 const QList<FOV *> &FOVManager::readFOVs()
0077 {
0078     qDeleteAll(m_FOVs);
0079     m_FOVs.clear();
0080 
0081     QFile f;
0082     f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("fov.dat"));
0083 
0084     if (!f.exists())
0085     {
0086         m_FOVs = defaults();
0087         save();
0088         return m_FOVs;
0089     }
0090 
0091     if (f.open(QIODevice::ReadOnly))
0092     {
0093         QTextStream istream(&f);
0094         while (!istream.atEnd())
0095         {
0096             QStringList fields = istream.readLine().split(':');
0097             bool ok;
0098             QString name, color;
0099             float sizeX, sizeY, xoffset, yoffset, rot;
0100             bool lockedCP = false;
0101             FOV::Shape shape;
0102             if (fields.count() >= 8)
0103             {
0104                 name  = fields[0];
0105                 sizeX = fields[1].toFloat(&ok);
0106                 if (!ok)
0107                 {
0108                     return m_FOVs;
0109                 }
0110                 sizeY = fields[2].toFloat(&ok);
0111                 if (!ok)
0112                 {
0113                     return m_FOVs;
0114                 }
0115                 xoffset = fields[3].toFloat(&ok);
0116                 if (!ok)
0117                 {
0118                     return m_FOVs;
0119                 }
0120 
0121                 yoffset = fields[4].toFloat(&ok);
0122                 if (!ok)
0123                 {
0124                     return m_FOVs;
0125                 }
0126 
0127                 rot = fields[5].toFloat(&ok);
0128                 if (!ok)
0129                 {
0130                     return m_FOVs;
0131                 }
0132 
0133                 shape = static_cast<FOV::Shape>(fields[6].toInt(&ok));
0134                 if (!ok)
0135                 {
0136                     return m_FOVs;
0137                 }
0138                 color = fields[7];
0139 
0140                 if (fields.count() == 9)
0141                     lockedCP = (fields[8].toInt(&ok) == 1);
0142             }
0143             else
0144             {
0145                 continue;
0146             }
0147 
0148             m_FOVs.append(new FOV(name, sizeX, sizeY, xoffset, yoffset, rot, shape, color, lockedCP));
0149         }
0150     }
0151     return m_FOVs;
0152 }
0153 
0154 void FOVManager::releaseCache()
0155 {
0156     qDeleteAll(m_FOVs);
0157     m_FOVs.clear();
0158 }
0159 
0160 FOV::FOV(const QString &n, float a, float b, float xoffset, float yoffset, float rot, Shape sh, const QString &col,
0161          bool useLockedCP) : QObject()
0162 {
0163     qRegisterMetaType<FOV::Shape>("FOV::Shape");
0164     qDBusRegisterMetaType<FOV::Shape>();
0165 
0166     new FovAdaptor(this);
0167     QDBusConnection::sessionBus().registerObject(QString("/KStars/FOV/%1").arg(getID()), this);
0168 
0169     m_name  = n;
0170     m_sizeX = a;
0171     m_sizeY = (b < 0.0) ? a : b;
0172 
0173     m_offsetX  = xoffset;
0174     m_offsetY  = yoffset;
0175     m_PA = rot;
0176     m_shape    = sh;
0177     m_color    = col;
0178     m_northPA  = 0;
0179     m_center.setRA(0);
0180     m_center.setDec(0);
0181     m_imageDisplay = false;
0182     m_lockCelestialPole = useLockedCP;
0183 }
0184 
0185 FOV::FOV() : QObject()
0186 {
0187     qRegisterMetaType<FOV::Shape>("FOV::Shape");
0188     qDBusRegisterMetaType<FOV::Shape>();
0189 
0190     new FovAdaptor(this);
0191     QDBusConnection::sessionBus().registerObject(QString("/KStars/FOV/%1").arg(getID()), this);
0192 
0193     m_name  = i18n("No FOV");
0194     m_color = "#FFFFFF";
0195 
0196     m_sizeX = m_sizeY = 0;
0197     m_shape           = SQUARE;
0198     m_imageDisplay = false;
0199     m_lockCelestialPole = false;
0200 }
0201 
0202 FOV::FOV(const FOV &other) : QObject()
0203 {
0204     m_name   = other.m_name;
0205     m_color  = other.m_color;
0206     m_sizeX  = other.m_sizeX;
0207     m_sizeY  = other.m_sizeY;
0208     m_shape  = other.m_shape;
0209     m_offsetX = other.m_offsetX;
0210     m_offsetY = other.m_offsetY;
0211     m_PA     = other.m_PA;
0212     m_imageDisplay = other.m_imageDisplay;
0213     m_lockCelestialPole = other.m_lockCelestialPole;
0214 }
0215 
0216 void FOV::sync(const FOV &other)
0217 {
0218     m_name   = other.m_name;
0219     m_color  = other.m_color;
0220     m_sizeX  = other.m_sizeX;
0221     m_sizeY  = other.m_sizeY;
0222     m_shape  = other.m_shape;
0223     m_offsetX = other.m_offsetX;
0224     m_offsetY = other.m_offsetY;
0225     m_PA     = other.m_PA;
0226     m_imageDisplay = other.m_imageDisplay;
0227     m_lockCelestialPole = other.m_lockCelestialPole;
0228 }
0229 
0230 void FOV::draw(QPainter &p, float zoomFactor)
0231 {
0232     // Do not draw empty FOVs
0233     if (m_sizeX == 0 || m_sizeY == 0)
0234         return;
0235 
0236     p.setPen(QColor(color()));
0237     p.setBrush(Qt::NoBrush);
0238 
0239     p.setRenderHint(QPainter::Antialiasing, Options::useAntialias());
0240 
0241     float pixelSizeX = sizeX() * zoomFactor / 57.3 / 60.0;
0242     float pixelSizeY = sizeY() * zoomFactor / 57.3 / 60.0;
0243 
0244     float offsetXPixelSize = offsetX() * zoomFactor / 57.3 / 60.0;
0245     float offsetYPixelSize = offsetY() * zoomFactor / 57.3 / 60.0;
0246 
0247     p.save();
0248 
0249     if (m_center.ra().Degrees() > 0)
0250     {
0251         m_center.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
0252         QPointF skypoint_center = KStars::Instance()->map()->projector()->toScreen(&m_center);
0253         p.translate(skypoint_center.toPoint());
0254     }
0255     else
0256         p.translate(p.viewport().center());
0257 
0258     p.translate(offsetXPixelSize, offsetYPixelSize);
0259     p.rotate( (m_PA - m_northPA) * -1);
0260 
0261     QPointF center(0, 0);
0262 
0263     switch (shape())
0264     {
0265         case SQUARE:
0266         {
0267             QRect targetRect(center.x() - pixelSizeX / 2, center.y() - pixelSizeY / 2, pixelSizeX, pixelSizeY);
0268             if (m_imageDisplay)
0269                 p.drawImage(targetRect, m_image);
0270 
0271             p.drawRect(targetRect);
0272             p.drawRect(center.x(), center.y() - (3 * pixelSizeY / 5), pixelSizeX / 40, pixelSizeX / 10);
0273             p.drawLine(center.x() - pixelSizeX / 30, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 20,
0274                        center.y() - (3 * pixelSizeY / 5));
0275             p.drawLine(center.x() - pixelSizeX / 30, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 70,
0276                        center.y() - (0.7 * pixelSizeY));
0277             p.drawLine(center.x() + pixelSizeX / 20, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 70,
0278                        center.y() - (0.7 * pixelSizeY));
0279 
0280             if (name().count() > 0)
0281             {
0282                 int fontSize = pixelSizeX / 15;
0283                 fontSize *= 14.0 / name().count();
0284 
0285                 // Don't let the font size get larger than the vertical space allotted.
0286                 const int maxYPixelSize = (14.0 / 15.0) * (pixelSizeY / 8);
0287                 fontSize = std::min(maxYPixelSize, fontSize);
0288 
0289                 if (fontSize <= 4)
0290                     break;
0291 
0292                 QFont font = p.font();
0293                 font.setPixelSize(fontSize);
0294                 p.setFont(font);
0295 
0296                 QRect nameRect(targetRect.topLeft().x(), targetRect.topLeft().y() - (pixelSizeY / 8), targetRect.width() / 2,
0297                                pixelSizeX / 10);
0298                 p.drawText(nameRect, Qt::AlignCenter, name());
0299 
0300                 // Maybe make the font size smaller for the field-of-view dimensions.
0301                 QString fovString = QString("%1'x%2'").arg(QString::number(m_sizeX, 'f', 1), QString::number(m_sizeY, 'f', 1));
0302                 int fovFontSize = (pixelSizeX / 15) * (14.0 / fovString.count());
0303                 fovFontSize = std::min(maxYPixelSize, fovFontSize);
0304                 fontSize = std::min(fovFontSize, fontSize);
0305                 font.setPixelSize(fontSize);
0306                 p.setFont(font);
0307 
0308                 QRect sizeRect(targetRect.center().x(), targetRect.topLeft().y() - (pixelSizeY / 8), targetRect.width() / 2,
0309                                pixelSizeX / 10);
0310                 p.drawText(sizeRect, Qt::AlignCenter, QString("%1'x%2'").arg(QString::number(m_sizeX, 'f', 1), QString::number(m_sizeY, 'f',
0311                            1)));
0312             }
0313         }
0314         break;
0315         case CIRCLE:
0316             p.drawEllipse(center, pixelSizeX / 2, pixelSizeY / 2);
0317             break;
0318         case CROSSHAIRS:
0319             //Draw radial lines
0320             p.drawLine(center.x() + 0.5 * pixelSizeX, center.y(), center.x() + 1.5 * pixelSizeX, center.y());
0321             p.drawLine(center.x() - 0.5 * pixelSizeX, center.y(), center.x() - 1.5 * pixelSizeX, center.y());
0322             p.drawLine(center.x(), center.y() + 0.5 * pixelSizeY, center.x(), center.y() + 1.5 * pixelSizeY);
0323             p.drawLine(center.x(), center.y() - 0.5 * pixelSizeY, center.x(), center.y() - 1.5 * pixelSizeY);
0324             //Draw circles at 0.5 & 1 degrees
0325             p.drawEllipse(center, 0.5 * pixelSizeX, 0.5 * pixelSizeY);
0326             p.drawEllipse(center, pixelSizeX, pixelSizeY);
0327             break;
0328         case BULLSEYE:
0329             p.drawEllipse(center, 0.5 * pixelSizeX, 0.5 * pixelSizeY);
0330             p.drawEllipse(center, 2.0 * pixelSizeX, 2.0 * pixelSizeY);
0331             p.drawEllipse(center, 4.0 * pixelSizeX, 4.0 * pixelSizeY);
0332             break;
0333         case SOLIDCIRCLE:
0334         {
0335             QColor colorAlpha = color();
0336             colorAlpha.setAlpha(127);
0337             p.setBrush(QBrush(colorAlpha));
0338             p.drawEllipse(center, pixelSizeX / 2, pixelSizeY / 2);
0339             p.setBrush(Qt::NoBrush);
0340             break;
0341         }
0342         default:
0343             ;
0344     }
0345 
0346     p.restore();
0347 }
0348 
0349 void FOV::draw(QPainter &p, float x, float y)
0350 {
0351     float xfactor    = x / sizeX() * 57.3 * 60.0;
0352     float yfactor    = y / sizeY() * 57.3 * 60.0;
0353     float zoomFactor = std::min(xfactor, yfactor);
0354     switch (shape())
0355     {
0356         case CROSSHAIRS:
0357             zoomFactor /= 3;
0358             break;
0359         case BULLSEYE:
0360             zoomFactor /= 8;
0361             break;
0362         default:
0363             ;
0364     }
0365     draw(p, zoomFactor);
0366 }
0367 
0368 SkyPoint FOV::center() const
0369 {
0370     return m_center;
0371 }
0372 
0373 void FOV::setCenter(const SkyPoint &center)
0374 {
0375     m_center = center;
0376 }
0377 
0378 float FOV::northPA() const
0379 {
0380     return m_northPA;
0381 }
0382 
0383 void FOV::setNorthPA(float northPA)
0384 {
0385     m_northPA = northPA;
0386 }
0387 
0388 void FOV::setImage(const QImage &image)
0389 {
0390     m_image = image;
0391 }
0392 
0393 void FOV::setImageDisplay(bool value)
0394 {
0395     m_imageDisplay = value;
0396 }
0397 
0398 bool FOV::lockCelestialPole() const
0399 {
0400     return m_lockCelestialPole;
0401 }
0402 
0403 void FOV::setLockCelestialPole(bool lockCelestialPole)
0404 {
0405     m_lockCelestialPole = lockCelestialPole;
0406 }
0407 
0408 QDBusArgument &operator<<(QDBusArgument &argument, const FOV::Shape &source)
0409 {
0410     argument.beginStructure();
0411     argument << static_cast<int>(source);
0412     argument.endStructure();
0413     return argument;
0414 }
0415 
0416 const QDBusArgument &operator>>(const QDBusArgument &argument, FOV::Shape &dest)
0417 {
0418     int a;
0419     argument.beginStructure();
0420     argument >> a;
0421     argument.endStructure();
0422     dest = static_cast<FOV::Shape>(a);
0423     return argument;
0424 }