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 ¢er) 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 }