1 /*
2     SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "fov.h"
8 
9 #include "geolocation.h"
10 #include "kspaths.h"
11 #ifndef KSTARS_LITE
12 #include "kstars.h"
13 #endif
14 #include "kstarsdata.h"
15 #include "Options.h"
16 #include "skymap.h"
17 #include "projections/projector.h"
18 #include "fovadaptor.h"
19 
20 #include <QPainter>
21 #include <QTextStream>
22 #include <QFile>
23 #include <QDebug>
24 #include <QStandardPaths>
25 
26 #include <algorithm>
27 
28 QList<FOV *> FOVManager::m_FOVs;
29 int FOV::m_ID = 1;
30 
~FOVManager()31 FOVManager::~FOVManager()
32 {
33     qDeleteAll(m_FOVs);
34 }
35 
defaults()36 QList<FOV *> FOVManager::defaults()
37 {
38     QList<FOV *> fovs;
39     fovs << new FOV(i18nc("use field-of-view for binoculars", "7x35 Binoculars"), 558, 558, 0, 0, 0, FOV::CIRCLE,
40                     "#AAAAAA")
41          << new FOV(i18nc("use a Telrad field-of-view indicator", "Telrad"), 30, 30, 0, 0, 0, FOV::BULLSEYE, "#AA0000")
42          << new FOV(i18nc("use 1-degree field-of-view indicator", "One Degree"), 60, 60, 0, 0, 0, FOV::CIRCLE,
43                     "#AAAAAA")
44          << new FOV(i18nc("use HST field-of-view indicator", "HST WFPC2"), 2.4, 2.4, 0, 0, 0, FOV::SQUARE, "#AAAAAA")
45          << new FOV(i18nc("use Radiotelescope HPBW", "30m at 1.3cm"), 1.79, 1.79, 0, 0, 0, FOV::SQUARE, "#AAAAAA");
46     return fovs;
47 }
48 
save()49 bool FOVManager::save()
50 {
51     QFile f;
52 
53     // TODO: Move FOVs to user database instead of file!!
54     f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("fov.dat"));
55 
56     if (!f.open(QIODevice::WriteOnly))
57     {
58         qDebug() << "Could not open fov.dat.";
59         return false;
60     }
61 
62     QTextStream ostream(&f);
63     foreach (FOV *fov, m_FOVs)
64     {
65         ostream << fov->name() << ':' << fov->sizeX() << ':' << fov->sizeY() << ':' << fov->offsetX() << ':'
66                 << fov->offsetY() << ':' << fov->PA() << ':' << QString::number(fov->shape())
67                 << ':' << fov->color()
68                 << ':' << (fov->lockCelestialPole() ? 1 : 0)
69                 << '\n';
70     }
71     f.close();
72 
73     return true;
74 }
75 
readFOVs()76 const QList<FOV *> &FOVManager::readFOVs()
77 {
78     qDeleteAll(m_FOVs);
79     m_FOVs.clear();
80 
81     QFile f;
82     f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("fov.dat"));
83 
84     if (!f.exists())
85     {
86         m_FOVs = defaults();
87         save();
88         return m_FOVs;
89     }
90 
91     if (f.open(QIODevice::ReadOnly))
92     {
93         QTextStream istream(&f);
94         while (!istream.atEnd())
95         {
96             QStringList fields = istream.readLine().split(':');
97             bool ok;
98             QString name, color;
99             float sizeX, sizeY, xoffset, yoffset, rot;
100             bool lockedCP = false;
101             FOV::Shape shape;
102             if (fields.count() >= 8)
103             {
104                 name  = fields[0];
105                 sizeX = fields[1].toFloat(&ok);
106                 if (!ok)
107                 {
108                     return m_FOVs;
109                 }
110                 sizeY = fields[2].toFloat(&ok);
111                 if (!ok)
112                 {
113                     return m_FOVs;
114                 }
115                 xoffset = fields[3].toFloat(&ok);
116                 if (!ok)
117                 {
118                     return m_FOVs;
119                 }
120 
121                 yoffset = fields[4].toFloat(&ok);
122                 if (!ok)
123                 {
124                     return m_FOVs;
125                 }
126 
127                 rot = fields[5].toFloat(&ok);
128                 if (!ok)
129                 {
130                     return m_FOVs;
131                 }
132 
133                 shape = static_cast<FOV::Shape>(fields[6].toInt(&ok));
134                 if (!ok)
135                 {
136                     return m_FOVs;
137                 }
138                 color = fields[7];
139 
140                 if (fields.count() == 9)
141                     lockedCP = (fields[8].toInt(&ok) == 1);
142             }
143             else
144             {
145                 continue;
146             }
147 
148             m_FOVs.append(new FOV(name, sizeX, sizeY, xoffset, yoffset, rot, shape, color, lockedCP));
149         }
150     }
151     return m_FOVs;
152 }
153 
releaseCache()154 void FOVManager::releaseCache()
155 {
156     qDeleteAll(m_FOVs);
157     m_FOVs.clear();
158 }
159 
FOV(const QString & n,float a,float b,float xoffset,float yoffset,float rot,Shape sh,const QString & col,bool useLockedCP)160 FOV::FOV(const QString &n, float a, float b, float xoffset, float yoffset, float rot, Shape sh, const QString &col,
161          bool useLockedCP) : QObject()
162 {
163     qRegisterMetaType<FOV::Shape>("FOV::Shape");
164     qDBusRegisterMetaType<FOV::Shape>();
165 
166     new FovAdaptor(this);
167     QDBusConnection::sessionBus().registerObject(QString("/KStars/FOV/%1").arg(getID()), this);
168 
169     m_name  = n;
170     m_sizeX = a;
171     m_sizeY = (b < 0.0) ? a : b;
172 
173     m_offsetX  = xoffset;
174     m_offsetY  = yoffset;
175     m_PA = rot;
176     m_shape    = sh;
177     m_color    = col;
178     m_northPA  = 0;
179     m_center.setRA(0);
180     m_center.setDec(0);
181     m_imageDisplay = false;
182     m_lockCelestialPole = useLockedCP;
183 }
184 
FOV()185 FOV::FOV() : QObject()
186 {
187     qRegisterMetaType<FOV::Shape>("FOV::Shape");
188     qDBusRegisterMetaType<FOV::Shape>();
189 
190     new FovAdaptor(this);
191     QDBusConnection::sessionBus().registerObject(QString("/KStars/FOV/%1").arg(getID()), this);
192 
193     m_name  = i18n("No FOV");
194     m_color = "#FFFFFF";
195 
196     m_sizeX = m_sizeY = 0;
197     m_shape           = SQUARE;
198     m_imageDisplay = false;
199     m_lockCelestialPole = false;
200 }
201 
FOV(const FOV & other)202 FOV::FOV(const FOV &other) : QObject()
203 {
204     m_name   = other.m_name;
205     m_color  = other.m_color;
206     m_sizeX  = other.m_sizeX;
207     m_sizeY  = other.m_sizeY;
208     m_shape  = other.m_shape;
209     m_offsetX = other.m_offsetX;
210     m_offsetY = other.m_offsetY;
211     m_PA     = other.m_PA;
212     m_imageDisplay = other.m_imageDisplay;
213     m_lockCelestialPole = other.m_lockCelestialPole;
214 }
215 
sync(const FOV & other)216 void FOV::sync(const FOV &other)
217 {
218     m_name   = other.m_name;
219     m_color  = other.m_color;
220     m_sizeX  = other.m_sizeX;
221     m_sizeY  = other.m_sizeY;
222     m_shape  = other.m_shape;
223     m_offsetX = other.m_offsetX;
224     m_offsetY = other.m_offsetY;
225     m_PA     = other.m_PA;
226     m_imageDisplay = other.m_imageDisplay;
227     m_lockCelestialPole = other.m_lockCelestialPole;
228 }
229 
draw(QPainter & p,float zoomFactor)230 void FOV::draw(QPainter &p, float zoomFactor)
231 {
232     // Do not draw empty FOVs
233     if (m_sizeX == 0 || m_sizeY == 0)
234         return;
235 
236     p.setPen(QColor(color()));
237     p.setBrush(Qt::NoBrush);
238 
239     p.setRenderHint(QPainter::Antialiasing, Options::useAntialias());
240 
241     float pixelSizeX = sizeX() * zoomFactor / 57.3 / 60.0;
242     float pixelSizeY = sizeY() * zoomFactor / 57.3 / 60.0;
243 
244     float offsetXPixelSize = offsetX() * zoomFactor / 57.3 / 60.0;
245     float offsetYPixelSize = offsetY() * zoomFactor / 57.3 / 60.0;
246 
247     p.save();
248 
249     if (m_center.ra().Degrees() > 0)
250     {
251         m_center.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
252         QPointF skypoint_center = KStars::Instance()->map()->projector()->toScreen(&m_center);
253         p.translate(skypoint_center.toPoint());
254     }
255     else
256         p.translate(p.viewport().center());
257 
258     p.translate(offsetXPixelSize, offsetYPixelSize);
259     p.rotate( (m_PA - m_northPA) * -1);
260 
261     QPointF center(0, 0);
262 
263     switch (shape())
264     {
265         case SQUARE:
266         {
267             QRect targetRect(center.x() - pixelSizeX / 2, center.y() - pixelSizeY / 2, pixelSizeX, pixelSizeY);
268             if (m_imageDisplay)
269                 p.drawImage(targetRect, m_image);
270 
271             p.drawRect(targetRect);
272             p.drawRect(center.x(), center.y() - (3 * pixelSizeY / 5), pixelSizeX / 40, pixelSizeX / 10);
273             p.drawLine(center.x() - pixelSizeX / 30, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 20,
274                        center.y() - (3 * pixelSizeY / 5));
275             p.drawLine(center.x() - pixelSizeX / 30, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 70,
276                        center.y() - (0.7 * pixelSizeY));
277             p.drawLine(center.x() + pixelSizeX / 20, center.y() - (3 * pixelSizeY / 5), center.x() + pixelSizeX / 70,
278                        center.y() - (0.7 * pixelSizeY));
279 
280             if (name().count() > 0)
281             {
282                 int fontSize = pixelSizeX / 15;
283                 fontSize *= 14.0 / name().count();
284                 if (fontSize <= 4)
285                     break;
286 
287                 QFont font = p.font();
288                 font.setPixelSize(fontSize);
289                 p.setFont(font);
290 
291                 QRect nameRect(targetRect.topLeft().x(), targetRect.topLeft().y() - (pixelSizeY / 8), targetRect.width() / 2,
292                                pixelSizeX / 10);
293                 p.drawText(nameRect, Qt::AlignCenter, name());
294 
295                 QRect sizeRect(targetRect.center().x(), targetRect.topLeft().y() - (pixelSizeY / 8), targetRect.width() / 2,
296                                pixelSizeX / 10);
297                 p.drawText(sizeRect, Qt::AlignCenter, QString("%1'x%2'").arg(QString::number(m_sizeX, 'f', 1), QString::number(m_sizeY, 'f',
298                            1)));
299             }
300         }
301         break;
302         case CIRCLE:
303             p.drawEllipse(center, pixelSizeX / 2, pixelSizeY / 2);
304             break;
305         case CROSSHAIRS:
306             //Draw radial lines
307             p.drawLine(center.x() + 0.5 * pixelSizeX, center.y(), center.x() + 1.5 * pixelSizeX, center.y());
308             p.drawLine(center.x() - 0.5 * pixelSizeX, center.y(), center.x() - 1.5 * pixelSizeX, center.y());
309             p.drawLine(center.x(), center.y() + 0.5 * pixelSizeY, center.x(), center.y() + 1.5 * pixelSizeY);
310             p.drawLine(center.x(), center.y() - 0.5 * pixelSizeY, center.x(), center.y() - 1.5 * pixelSizeY);
311             //Draw circles at 0.5 & 1 degrees
312             p.drawEllipse(center, 0.5 * pixelSizeX, 0.5 * pixelSizeY);
313             p.drawEllipse(center, pixelSizeX, pixelSizeY);
314             break;
315         case BULLSEYE:
316             p.drawEllipse(center, 0.5 * pixelSizeX, 0.5 * pixelSizeY);
317             p.drawEllipse(center, 2.0 * pixelSizeX, 2.0 * pixelSizeY);
318             p.drawEllipse(center, 4.0 * pixelSizeX, 4.0 * pixelSizeY);
319             break;
320         case SOLIDCIRCLE:
321         {
322             QColor colorAlpha = color();
323             colorAlpha.setAlpha(127);
324             p.setBrush(QBrush(colorAlpha));
325             p.drawEllipse(center, pixelSizeX / 2, pixelSizeY / 2);
326             p.setBrush(Qt::NoBrush);
327             break;
328         }
329         default:
330             ;
331     }
332 
333     p.restore();
334 }
335 
draw(QPainter & p,float x,float y)336 void FOV::draw(QPainter &p, float x, float y)
337 {
338     float xfactor    = x / sizeX() * 57.3 * 60.0;
339     float yfactor    = y / sizeY() * 57.3 * 60.0;
340     float zoomFactor = std::min(xfactor, yfactor);
341     switch (shape())
342     {
343         case CROSSHAIRS:
344             zoomFactor /= 3;
345             break;
346         case BULLSEYE:
347             zoomFactor /= 8;
348             break;
349         default:
350             ;
351     }
352     draw(p, zoomFactor);
353 }
354 
center() const355 SkyPoint FOV::center() const
356 {
357     return m_center;
358 }
359 
setCenter(const SkyPoint & center)360 void FOV::setCenter(const SkyPoint &center)
361 {
362     m_center = center;
363 }
364 
northPA() const365 float FOV::northPA() const
366 {
367     return m_northPA;
368 }
369 
setNorthPA(float northPA)370 void FOV::setNorthPA(float northPA)
371 {
372     m_northPA = northPA;
373 }
374 
setImage(const QImage & image)375 void FOV::setImage(const QImage &image)
376 {
377     m_image = image;
378 }
379 
setImageDisplay(bool value)380 void FOV::setImageDisplay(bool value)
381 {
382     m_imageDisplay = value;
383 }
384 
lockCelestialPole() const385 bool FOV::lockCelestialPole() const
386 {
387     return m_lockCelestialPole;
388 }
389 
setLockCelestialPole(bool lockCelestialPole)390 void FOV::setLockCelestialPole(bool lockCelestialPole)
391 {
392     m_lockCelestialPole = lockCelestialPole;
393 }
394 
operator <<(QDBusArgument & argument,const FOV::Shape & source)395 QDBusArgument &operator<<(QDBusArgument &argument, const FOV::Shape &source)
396 {
397     argument.beginStructure();
398     argument << static_cast<int>(source);
399     argument.endStructure();
400     return argument;
401 }
402 
operator >>(const QDBusArgument & argument,FOV::Shape & dest)403 const QDBusArgument &operator>>(const QDBusArgument &argument, FOV::Shape &dest)
404 {
405     int a;
406     argument.beginStructure();
407     argument >> a;
408     argument.endStructure();
409     dest = static_cast<FOV::Shape>(a);
410     return argument;
411 }
412