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