Kstars

skymaplite.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Artem Fedoskin <[email protected]>
3  SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "skymaplite.h"
7 #include "kstarsdata.h"
8 #include "kstarslite.h"
9 
10 #include "indi/inditelescopelite.h"
11 #include "indi/clientmanagerlite.h"
12 #include "kstarslite/skyitems/telescopesymbolsitem.h"
13 
14 #include "projections/projector.h"
15 #include "projections/lambertprojector.h"
16 #include "projections/gnomonicprojector.h"
17 #include "projections/stereographicprojector.h"
18 #include "projections/orthographicprojector.h"
19 #include "projections/azimuthalequidistantprojector.h"
20 #include "projections/equirectangularprojector.h"
21 
22 #include "kstarslite/skypointlite.h"
23 #include "kstarslite/skyobjectlite.h"
24 
25 #include "skylabeler.h"
26 #include "Options.h"
27 #include "skymesh.h"
28 
29 #include "kstarslite/skyitems/rootnode.h"
30 #include "kstarslite/skyitems/skynodes/skynode.h"
31 
32 #include "ksplanetbase.h"
33 #include "ksutils.h"
34 
35 #include <QSGSimpleRectNode>
36 //#include <QSGNode>
37 #include <QBitmap>
38 #include <QSGTexture>
39 #include <QQuickWindow>
40 #include <QLinkedList>
41 #include <QQmlContext>
42 #include <QScreen>
43 
44 #include "kstarslite/deviceorientation.h"
45 
46 namespace
47 {
48 // Draw bitmap for zoom cursor. Width is size of pen to draw with.
49 QBitmap zoomCursorBitmap(int width)
50 {
51  QBitmap b(32, 32);
52  b.fill(Qt::color0);
53  int mx = 16, my = 16;
54  // Begin drawing
55  QPainter p;
56  p.begin(&b);
57  p.setPen(QPen(Qt::color1, width));
58  p.drawEllipse(mx - 7, my - 7, 14, 14);
59  p.drawLine(mx + 5, my + 5, mx + 11, my + 11);
60  p.end();
61  return b;
62 }
63 
64 // Draw bitmap for default cursor. Width is size of pen to draw with.
65 QBitmap defaultCursorBitmap(int width)
66 {
67  QBitmap b(32, 32);
68  b.fill(Qt::color0);
69  int mx = 16, my = 16;
70  // Begin drawing
71  QPainter p;
72  p.begin(&b);
73  p.setPen(QPen(Qt::color1, width));
74  // 1. diagonal
75  p.drawLine(mx - 2, my - 2, mx - 8, mx - 8);
76  p.drawLine(mx + 2, my + 2, mx + 8, mx + 8);
77  // 2. diagonal
78  p.drawLine(mx - 2, my + 2, mx - 8, mx + 8);
79  p.drawLine(mx + 2, my - 2, mx + 8, mx - 8);
80  p.end();
81  return b;
82 }
83 }
84 
85 SkyMapLite *SkyMapLite::pinstance = nullptr;
86 
87 RootNode *SkyMapLite::m_rootNode = nullptr;
88 
89 int SkyMapLite::starColorMode = 0;
90 
91 SkyMapLite::SkyMapLite()
92  : data(KStarsData::Instance())
93 #if defined(Q_OS_ANDROID)
94  ,
95  m_deviceOrientation(new DeviceOrientation(this))
96 #endif
97 {
98  setAcceptHoverEvents(true);
99  setAcceptedMouseButtons(Qt::AllButtons);
100  setFlag(ItemHasContents, true);
101 
102  m_rootNode = nullptr;
103  m_magLim = 2.222 * log10(static_cast<double>(Options::starDensity())) + 0.35;
104 
105  setSlewing(false);
106 
107  m_ClickedObjectLite = new SkyObjectLite;
108  m_ClickedPointLite = new SkyPointLite;
109 
110  qmlRegisterType<SkyObjectLite>("KStarsLite", 1, 0, "SkyObjectLite");
111  qmlRegisterType<SkyPointLite>("KStarsLite", 1, 0, "SkyPointLite");
112 
113  m_tapBeganTimer.setSingleShot(true);
114 
115  setupProjector();
116 
117  // Set pinstance to yourself
118  pinstance = this;
119 
120  connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus()));
121  connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool)));
122 
124 
125  connect(clientMng, &ClientManagerLite::telescopeAdded,
126  [this](TelescopeLite * newTelescope)
127  {
128  this->m_newTelescopes.append(newTelescope->getDevice());
129  });
130  connect(clientMng, &ClientManagerLite::telescopeRemoved,
131  [this](TelescopeLite * newTelescope)
132  {
133  this->m_delTelescopes.append(newTelescope->getDevice());
134  });
135 #if defined(Q_OS_ANDROID)
136  //Automatic mode
137  automaticModeTimer.setInterval(5);
138  connect(&automaticModeTimer, SIGNAL(timeout()), this, SLOT(updateAutomaticMode()));
139  setAutomaticMode(false);
140 #endif
141 }
142 
143 QSGNode *SkyMapLite::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
144 {
145  Q_UNUSED(updatePaintNodeData);
146  RootNode *n = static_cast<RootNode *>(oldNode);
147 
148  /* This code deletes all nodes that are representing dynamic stars and not needed anymore (under construction) */
149  //qDeleteAll(m_deleteNodes);
150  //m_deleteNodes.clear();
151 
152  if (m_loadingFinished && isInitialized)
153  {
154  if (!n)
155  {
156  n = new RootNode();
157  m_rootNode = n;
158  }
159  /** Add or delete telescope crosshairs **/
160  if (m_newTelescopes.count() > 0)
161  {
162  foreach (INDI::BaseDevice *telescope, m_newTelescopes)
163  {
164  n->telescopeSymbolsItem()->addTelescope(telescope);
165  }
166  m_newTelescopes.clear();
167  }
168 
169  if (m_delTelescopes.count() > 0)
170  {
171  foreach (INDI::BaseDevice *telescope, m_delTelescopes)
172  {
173  n->telescopeSymbolsItem()->removeTelescope(telescope);
174  }
175  m_delTelescopes.clear();
176  }
177  //Notify RootNode that textures for point node should be recreated
178  n->update(clearTextures);
179  clearTextures = false;
180  }
181 
182  //Memory Leaks test
183  /*if(m_loadingFinished) {
184  if(!n) {
185  n = new RootNode();
186  }
187  n->testLeakAdd();
188  n->update();
189  m_loadingFinished = false;
190  } else {
191  if (n) {
192  n->testLeakDelete();
193  }
194  m_loadingFinished = true;
195  }*/
196  return n;
197 }
198 
200 {
201  double lim = (MAXZOOM / MINZOOM) / sqrt(Options::zoomFactor()) / 3; //(MAXZOOM/MINZOOM - Options::zoomFactor())/130;
202  return lim;
203 }
204 
206 {
207  m_deleteNodes.append(skyNode);
208 }
209 
211 {
212  return textureCache[harvardToIndex(spType)][size];
213 }
214 
216 {
217  delete pinstance;
218  pinstance = new SkyMapLite();
219  return pinstance;
220 }
221 
223 {
224  if (parent)
225  {
227  // Whenever the wrapper's(parent) dimensions changed, change SkyMapLite too
230 
231  isInitialized = true;
232  }
233 
234  resizeItem(); /* Set initial size pf SkyMapLite. Without it on Android SkyMapLite is
235  not displayed until screen orientation is not changed*/
236 
237  //Initialize images for stars
238  initStarImages();
239 }
240 
242 {
243  // Delete image cache
244  for (auto &imgCache : imageCache)
245  qDeleteAll(imgCache);
246 
247  // Delete textures generated from image cache
248  for (auto &tCache : textureCache)
249  qDeleteAll(tCache);
250 }
251 
253 {
254  setFocus(p->ra(), p->dec());
255 }
256 
257 void SkyMapLite::setFocus(const dms &ra, const dms &dec)
258 {
259  Options::setFocusRA(ra.Hours());
260  Options::setFocusDec(dec.Degrees());
261 
262  focus()->set(ra, dec);
263  focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
264 }
265 
266 void SkyMapLite::setFocusAltAz(const dms &alt, const dms &az)
267 {
268  Options::setFocusRA(focus()->ra().Hours());
269  Options::setFocusDec(focus()->dec().Degrees());
270  focus()->setAlt(alt);
271  focus()->setAz(az);
272  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
273 
274  setSlewing(false);
275  forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work.
276 }
277 
279 {
280  setDestination(p.ra(), p.dec());
281 }
282 
283 void SkyMapLite::setDestination(const dms &ra, const dms &dec)
284 {
285  destination()->set(ra, dec);
286  destination()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
287  emit destinationChanged();
288 }
289 
290 void SkyMapLite::setDestinationAltAz(const dms &alt, const dms &az, bool altIsRefracted)
291 {
292  if (altIsRefracted)
293  {
294  // The alt in the SkyPoint is always actual, not apparent
296  }
297  else
298  {
299  destination()->setAlt(alt);
300  }
301  destination()->setAz(az);
302  destination()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
303  emit destinationChanged();
304 }
305 
307 {
308  ClickedPoint = *f;
309  m_ClickedPointLite->setPoint(f);
310 }
311 
313 {
314  ClickedObject = o;
315  m_ClickedObjectLite->setObject(o);
316 }
317 
319 {
320  FocusObject = o;
321  if (FocusObject)
322  Options::setFocusObject(FocusObject->name());
323  else
324  Options::setFocusObject(i18n("nothing"));
325 }
326 
328 {
329  /*KStars* kstars = KStars::Instance();
330  TrailObject* trailObj = dynamic_cast<TrailObject*>( focusObject() );*/
331 
333  if (Options::useAltAz())
334  {
335  focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
336  focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
337  }
338  else
339  {
340  focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
341  }
342  qDebug() << "Centering on " << focusPoint()->ra().toHMSString() << " " << focusPoint()->dec().toDMSString();
343 
344  //clear the planet trail of old focusObject, if it was temporary
345  /*if( trailObj && data->temporaryTrail ) {
346  trailObj->clearTrail();
347  data->temporaryTrail = false;
348  }*/
349 
350  //If the requested object is below the opaque horizon, issue a warning message
351  //(unless user is already pointed below the horizon)
352  if (Options::useAltAz() && Options::showGround() && focus()->alt().Degrees() > SkyPoint::altCrit &&
353  focusPoint()->alt().Degrees() <= SkyPoint::altCrit)
354  {
355  QString caption = i18n("Requested Position Below Horizon");
356  QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?");
357  /*if ( KMessageBox::warningYesNo( this, message, caption,
358  KGuiItem(i18n("Go Anyway")), KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz" )==KMessageBox::No ) {
359  setClickedObject( nullptr );
360  setFocusObject( nullptr );
361  Options::setIsTracking( false );
362 
363  return;
364  }*/
365  }
366 
367  //set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset
368  //destination to previous object...
369  setFocusObject(ClickedObject);
370  Options::setIsTracking(true);
371  /*if ( kstars ) {
372  kstars->actionCollection()->action("track_object")->setIcon( QIcon::fromTheme("document-encrypt") );
373  kstars->actionCollection()->action("track_object")->setText( i18n( "Stop &Tracking" ) );
374  }*/
375 
376  //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail
377 
378  /*if( Options::useAutoTrail() && trailObj && trailObj->hasTrail() ) {
379  trailObj->addToTrail();
380  data->temporaryTrail = true;
381  }*/
382 
383  //update the destination to the selected coordinates
384  if (Options::useAltAz())
385  {
386  setDestinationAltAz(focusPoint()->alt(), focusPoint()->az(), false);
387  }
388  else
389  {
391  }
392 
393  focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
394 
395  //display coordinates in statusBar
397  //showFocusCoords(); //update FocusBox
398  //Lock center so that user could only zoom on touch-enabled devices
399 }
400 
402 {
403  //Don't slew if the mouse button is pressed
404  //Also, no animated slews if the Manual Clock is active
405  //08/2002: added possibility for one-time skipping of slew with snapNextFocus
406  if (!mouseButtonDown)
407  {
408  bool goSlew = (Options::useAnimatedSlewing() && !data->snapNextFocus()) &&
409  !(data->clock()->isManualMode() && data->clock()->isActive());
410  if (goSlew)
411  {
412  double dX, dY;
413  double maxstep = 10.0;
414  if (Options::useAltAz())
415  {
416  dX = destination()->az().Degrees() - focus()->az().Degrees();
417  dY = destination()->alt().Degrees() - focus()->alt().Degrees();
418  }
419  else
420  {
421  dX = destination()->ra().Degrees() - focus()->ra().Degrees();
422  dY = destination()->dec().Degrees() - focus()->dec().Degrees();
423  }
424 
425  //switch directions to go the short way around the celestial sphere, if necessary.
426  dX = KSUtils::reduceAngle(dX, -180.0, 180.0);
427 
428  double r0 = sqrt(dX * dX + dY * dY);
429  if (r0 < 20.0) //smaller slews have smaller maxstep
430  {
431  maxstep *= (10.0 + 0.5 * r0) / 20.0;
432  }
433  double step = 0.1;
434  double r = r0;
435  while (r > step)
436  {
437  //DEBUG
438  //qDebug() << step << ": " << r << ": " << r0 << endl;
439  double fX = dX / r;
440  double fY = dY / r;
441 
442  if (Options::useAltAz())
443  {
444  focus()->setAlt(focus()->alt().Degrees() + fY * step);
445  focus()->setAz(dms(focus()->az().Degrees() + fX * step).reduce());
446  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
447  }
448  else
449  {
450  fX = fX / 15.; //convert RA degrees to hours
451  SkyPoint newFocus(focus()->ra().Hours() + fX * step, focus()->dec().Degrees() + fY * step);
452  setFocus(&newFocus);
453  focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
454  }
455 
456  setSlewing(true);
457 
458  forceUpdate();
459  qApp->processEvents(); //keep up with other stuff
460 
461  if (Options::useAltAz())
462  {
463  dX = destination()->az().Degrees() - focus()->az().Degrees();
464  dY = destination()->alt().Degrees() - focus()->alt().Degrees();
465  }
466  else
467  {
468  dX = destination()->ra().Degrees() - focus()->ra().Degrees();
469  dY = destination()->dec().Degrees() - focus()->dec().Degrees();
470  }
471 
472  //switch directions to go the short way around the celestial sphere, if necessary.
473  dX = KSUtils::reduceAngle(dX, -180.0, 180.0);
474  r = sqrt(dX * dX + dY * dY);
475 
476  //Modify step according to a cosine-shaped profile
477  //centered on the midpoint of the slew
478  //NOTE: don't allow the full range from -PI/2 to PI/2
479  //because the slew will never reach the destination as
480  //the speed approaches zero at the end!
481  double t = dms::PI * (r - 0.5 * r0) / (1.05 * r0);
482  step = cos(t) * maxstep;
483  }
484  }
485 
486  //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination
487  //set focus=destination.
488  if (Options::useAltAz())
489  {
490  setFocusAltAz(destination()->alt(), destination()->az());
491  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
492  }
493  else
494  {
496  focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
497  }
498 
499  setSlewing(false);
500 
501  //Turn off snapNextFocus, we only want it to happen once
502  if (data->snapNextFocus())
503  {
504  data->setSnapNextFocus(false);
505  }
506 
507  //Start the HoverTimer. if the user leaves the mouse in place after a slew,
508  //we want to attach a label to the nearest object.
509  if (Options::useHoverLabel())
510  m_HoverTimer.start(HOVER_INTERVAL);
511 
512  forceUpdate();
513  }
514 }
515 
517 {
518  //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock.
519  if ((fabs(data->clock()->scale()) > Options::slewTimeScale()) ^ clockSlewing)
520  {
521  data->clock()->setManualMode(!clockSlewing);
522  clockSlewing = !clockSlewing;
523  // don't change automatically the DST status
524  KStarsLite *kstars = KStarsLite::Instance();
525  if (kstars)
526  kstars->updateTime(false);
527  }
528 }
529 
530 /*void SkyMapLite::updateFocus() {
531  if( slewing )
532  return;
533 
534  //Tracking on an object
535  if ( Options::isTracking() && focusObject() != nullptr ) {
536  if ( Options::useAltAz() ) {
537  //Tracking any object in Alt/Az mode requires focus updates
538  focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
539  setFocusAltAz( focusObject()->alt(), focusObject()->az() );
540  focus()->HorizontalToEquatorial( data->lst(), data->geo()->lat() );
541  setDestination( *focus() );
542  } else {
543  //Tracking in equatorial coords
544  setFocus( focusObject() );
545  focus()->EquatorialToHorizontal( data->lst(), data->geo()->lat() );
546  setDestination( *focus() );
547  }
548 
549  //Tracking on empty sky
550  } else if ( Options::isTracking() && focusPoint() != nullptr ) {
551  if ( Options::useAltAz() ) {
552  //Tracking on empty sky in Alt/Az mode
553  setFocus( focusPoint() );
554  focus()->EquatorialToHorizontal( data->lst(), data->geo()->lat() );
555  setDestination( *focus() );
556  }
557 
558  // Not tracking and not slewing, let sky drift by
559  // This means that horizontal coordinates are constant.
560  } else {
561  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat() );
562  }
563 }*/
564 
566 {
567  if (parentItem())
568  {
569  setWidth(parentItem()->width());
571  }
572  forceUpdate();
573 }
574 
576 {
577  setZoomFactor(Options::zoomFactor() * DZOOM);
578 }
579 
581 {
582  setZoomFactor(Options::zoomFactor() / DZOOM);
583 }
584 
586 {
587  setZoomFactor(DEFAULTZOOM);
588 }
589 
591 {
592  ClickedPoint = *skyObj;
593  ClickedObject = skyObj;
594  /*if ( Options::useAltAz() ) {
595  setDestinationAltAz( skyObj->altRefracted(), skyObj->az() );
596  } else {
597  setDestination( *skyObj );
598  }*/
599  //Update selected SkyObject (used in FindDialog, DetailDialog)
600  m_ClickedObjectLite->setObject(skyObj);
601  emit objectLiteChanged();
602  slotCenter();
603 }
604 
605 void SkyMapLite::setSkyRotation(double skyRotation)
606 {
607  if (m_skyRotation != skyRotation)
608  {
609  m_skyRotation = skyRotation;
610  emit skyRotationChanged(skyRotation);
611 
612  if (skyRotation >= 0 && skyRotation < 90)
613  {
614  m_skyMapOrientation = SkyMapOrientation::Top0;
615  }
616  else if (skyRotation >= 90 && skyRotation < 180)
617  {
618  m_skyMapOrientation = SkyMapOrientation::Right90;
619  }
620  else if (skyRotation >= 180 && skyRotation < 270)
621  {
622  m_skyMapOrientation = SkyMapOrientation::Bottom180;
623  }
624  else if (skyRotation >= 270 && skyRotation < 360)
625  {
626  m_skyMapOrientation = SkyMapOrientation::Left270;
627  }
628  }
629 }
630 
631 void SkyMapLite::setZoomFactor(double factor)
632 {
633  Options::setZoomFactor(KSUtils::clamp(factor, MINZOOM, MAXZOOM));
634 
635  forceUpdate();
636  emit zoomChanged();
637 }
638 
640 {
641  setupProjector();
642 
643  // We delay one draw cycle before re-indexing
644  // we MUST ensure CLines do not get re-indexed while we use DRAW_BUF
645  // so we do it here.
646  //m_CLines->reindex( &m_reindexNum );
647  // This queues re-indexing for the next draw cycle
648  //m_reindexNum = KSNumbers( data->updateNum()->julianDay() );
649 
650  // This ensures that the JIT updates are synchronized for the entire draw
651  // cycle so the sky moves as a single sheet. May not be needed.
652  data->syncUpdateIDs();
653 
654  SkyMesh *m_skyMesh = SkyMesh::Instance(3);
655  if (m_skyMesh)
656  {
657  // prepare the aperture
658  // FIXME_FOV: We may want to rejigger this to allow
659  // wide-angle views --hdevalence
660  double radius = m_proj->fov();
661  if (radius > 180.0)
662  radius = 180.0;
663 
664  if (m_skyMesh->inDraw())
665  {
666  printf("Warning: aborting concurrent SkyMapComposite::draw()\n");
667  return;
668  }
669 
670  //m_skyMesh->inDraw( true );
671  m_skyMesh->aperture(&Focus, radius + 1.0, DRAW_BUF); // divide by 2 for testing
672 
673  // create the no-precess aperture if needed
674  if (Options::showEquatorialGrid() || Options::showHorizontalGrid() || Options::showCBounds() ||
675  Options::showEquator())
676  {
677  m_skyMesh->index(&Focus, radius + 1.0, NO_PRECESS_BUF);
678  }
679  }
680  update();
681 }
682 
684 {
685  Q_UNUSED(now);
686  updateFocus();
687  forceUpdate();
688 }
689 
691 {
692  if (getSlewing())
693  return;
694 
695  //Tracking on an object
696  if (Options::isTracking() && focusObject() != nullptr)
697  {
698  if (Options::useAltAz())
699  {
700  //Tracking any object in Alt/Az mode requires focus updates
701  focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
702  setFocusAltAz(focusObject()->alt(), focusObject()->az());
703  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
704  setDestination(*focus());
705  }
706  else
707  {
708  //Tracking in equatorial coords
710  focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
711  setDestination(*focus());
712  }
713 
714  //Tracking on empty sky
715  }
716  else if (Options::isTracking() && focusPoint() != nullptr)
717  {
718  if (Options::useAltAz())
719  {
720  //Tracking on empty sky in Alt/Az mode
721  setFocus(focusPoint());
722  focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
723  setDestination(*focus());
724  }
725 
726  // Not tracking and not slewing, let sky drift by
727  // This means that horizontal coordinates are constant.
728  }
729  else
730  {
731  focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
732  }
733 }
734 
736 {
737  //Update View Parameters for projection
738  ViewParams p;
739  p.focus = focus();
740  p.height = height();
741  p.width = width();
742  p.useAltAz = Options::useAltAz();
743  p.useRefraction = Options::useRefraction();
744  p.zoomFactor = Options::zoomFactor();
745  p.fillGround = Options::showGround();
746 
747  //Check if we need a new projector
748  if (m_proj && Options::projection() == m_proj->type())
749  m_proj->setViewParams(p);
750  else
751  {
752  delete m_proj;
753  switch (Options::projection())
754  {
755  case Projector::Gnomonic:
756  m_proj = new GnomonicProjector(p);
757  break;
758  case Projector::Stereographic:
759  m_proj = new StereographicProjector(p);
760  break;
761  case Projector::Orthographic:
762  m_proj = new OrthographicProjector(p);
763  break;
764  case Projector::AzimuthalEquidistant:
765  m_proj = new AzimuthalEquidistantProjector(p);
766  break;
767  case Projector::Equirectangular:
768  m_proj = new EquirectangularProjector(p);
769  break;
770  case Projector::Lambert:
771  default:
772  //TODO: implement other projection classes
773  m_proj = new LambertProjector(p);
774  break;
775  }
776  }
777 }
778 
779 void SkyMapLite::setZoomMouseCursor()
780 {
781  mouseMoveCursor = false; // no mousemove cursor
782  QBitmap cursor = zoomCursorBitmap(2);
783  QBitmap mask = zoomCursorBitmap(4);
784  setCursor(QCursor(cursor, mask));
785 }
786 
787 void SkyMapLite::setDefaultMouseCursor()
788 {
789  mouseMoveCursor = false; // no mousemove cursor
790  QBitmap cursor = defaultCursorBitmap(2);
791  QBitmap mask = defaultCursorBitmap(3);
792  setCursor(QCursor(cursor, mask));
793 }
794 
795 void SkyMapLite::setMouseMoveCursor()
796 {
797  if (mouseButtonDown)
798  {
799  setCursor(Qt::SizeAllCursor); // cursor shape defined in qt
800  mouseMoveCursor = true;
801  }
802 }
803 
805 {
806  return (getSlewing() || (clockSlewing && data->clock()->isActive()));
807 }
808 
810 {
811  // Convert spectral class to numerical index.
812  // If spectral class is invalid return index for white star (A class)
813 
814  switch (c)
815  {
816  case 'o':
817  case 'O':
818  return 0;
819  case 'b':
820  case 'B':
821  return 1;
822  case 'a':
823  case 'A':
824  return 2;
825  case 'f':
826  case 'F':
827  return 3;
828  case 'g':
829  case 'G':
830  return 4;
831  case 'k':
832  case 'K':
833  return 5;
834  case 'm':
835  case 'M':
836  return 6;
837  // For unknown spectral class assume A class (white star)
838  default:
839  return 2;
840  }
841 }
842 
844 {
845  return imageCache;
846 }
847 
849 {
850  if (isInitialized)
851  {
852  QFont f;
853 
854  if (zoomFont)
855  {
856  f = SkyLabeler::Instance()->drawFont();
857  }
858  else
859  {
860  f = SkyLabeler::Instance()->stdFont();
861  }
862 
863  qreal ratio = window()->effectiveDevicePixelRatio();
864 
865  QFontMetrics fm(f);
866 
867  int width = fm.width(text);
868  int height = fm.height();
869  // double rotation = 0;
870 
871  //// switch(m_skyMapOrientation) {
872  //// case(SkyMapOrientation::Top0):
873  //// width = fm.width(text);
874  //// height = fm.height();
875  //// rotation = 0;
876  //// break;
877  //// case(SkyMapOrientation::Right90):
878  //// width = fm.height();
879  //// height = fm.width(text);
880  //// rotation = 90;
881  //// case(SkyMapOrientation::Bottom180):
882  ////// width = ;
883  ////// height = ;
884  //// rotation = 180;
885  //// case(SkyMapOrientation::Left270):
886  ////// width = ;
887  ////// height;
888  //// rotation = 270;
889  //// }
890 
891  f.setPointSizeF(f.pointSizeF() * ratio);
892 
893  QImage label((width)*ratio, (height)*ratio, QImage::Format_ARGB32_Premultiplied);
894 
895  label.fill(Qt::transparent);
896 
897  m_painter.begin(&label);
898 
899  m_painter.setFont(f);
900 
901  m_painter.setPen(color);
902 
903  // m_painter.drawRect(0,0, label.width(), label.height());
904  // m_painter.rotate(getSkyRotation());
905  m_painter.drawText(0, (height - fm.descent()) * ratio, text);
906 
907  m_painter.end();
908 
910  return texture;
911  }
912  else
913  {
914  return nullptr;
915  }
916 }
917 
918 void SkyMapLite::addFOVSymbol(const QString &FOVName, bool initialState)
919 {
920  m_FOVSymbols.append(FOVName);
921  //Emit signal whenever new value was added
922  emit symbolsFOVChanged(m_FOVSymbols);
923 
924  m_FOVSymVisible.append(initialState);
925 }
926 
928 {
929  return m_FOVSymVisible.value(index);
930 }
931 
932 void SkyMapLite::setFOVVisible(int index, bool visible)
933 {
934  if (index >= 0 && index < m_FOVSymVisible.size())
935  {
936  m_FOVSymVisible[index] = visible;
937  forceUpdate();
938  }
939 }
940 
941 void SkyMapLite::setSlewing(bool newSlewing)
942 {
943  if (m_slewing != newSlewing)
944  {
945  m_slewing = newSlewing;
946  emit slewingChanged(newSlewing);
947  }
948 }
949 
950 void SkyMapLite::setCenterLocked(bool centerLocked)
951 {
952  m_centerLocked = centerLocked;
953  emit centerLockedChanged(centerLocked);
954 }
955 
956 void SkyMapLite::setAutomaticMode(bool automaticMode)
957 {
958 #if defined(Q_OS_ANDROID)
959  if (m_automaticMode != automaticMode)
960  {
961  m_automaticMode = automaticMode;
962  if (automaticMode)
963  {
964  m_deviceOrientation->startSensors();
965  automaticModeTimer.start();
966  }
967  else
968  {
969  automaticModeTimer.stop();
970  m_deviceOrientation->stopSensors();
971  }
972  }
973 #else
974  Q_UNUSED(automaticMode);
975 #endif
976 }
977 
979 {
980 #if defined(Q_OS_ANDROID)
981  m_deviceOrientation->getOrientation();
982  if (Options::useRefraction() && Options::useAltAz())
983  {
984  setFocusAltAz(SkyPoint::unrefract(dms(m_deviceOrientation->getAltitude())), dms(m_deviceOrientation->getAzimuth()));
985  }
986  else
987  {
988  setFocusAltAz(dms(m_deviceOrientation->getAltitude()), dms(m_deviceOrientation->getAzimuth()));
989  }
990 
991  setSkyRotation(-1 * m_deviceOrientation->getRoll());
992 #endif
993 }
994 
996 {
997  if (isInitialized)
998  {
999  //Delete all existing pixmaps
1000  if (imageCache.length() != 0)
1001  {
1002  foreach (QVector<QPixmap *> vec, imageCache)
1003  {
1004  qDeleteAll(vec.begin(), vec.end());
1005  }
1006  clearTextures = true;
1007  }
1008 
1009  imageCache = QVector<QVector<QPixmap *>>(nSPclasses);
1010 
1011  QMap<char, QColor> ColorMap;
1012  const int starColorIntensity = Options::starColorIntensity();
1013 
1014  //On high-dpi screens star will look pixelized if don't multiply scaling factor by this ratio
1015  //Check PointNode::setNode() to see how it works
1016  qreal ratio = window()->effectiveDevicePixelRatio();
1017 
1018  switch (Options::starColorMode())
1019  {
1020  case 1: // Red stars.
1021  ColorMap.insert('O', QColor::fromRgb(255, 0, 0));
1022  ColorMap.insert('B', QColor::fromRgb(255, 0, 0));
1023  ColorMap.insert('A', QColor::fromRgb(255, 0, 0));
1024  ColorMap.insert('F', QColor::fromRgb(255, 0, 0));
1025  ColorMap.insert('G', QColor::fromRgb(255, 0, 0));
1026  ColorMap.insert('K', QColor::fromRgb(255, 0, 0));
1027  ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1028  break;
1029  case 2: // Black stars.
1030  ColorMap.insert('O', QColor::fromRgb(0, 0, 0));
1031  ColorMap.insert('B', QColor::fromRgb(0, 0, 0));
1032  ColorMap.insert('A', QColor::fromRgb(0, 0, 0));
1033  ColorMap.insert('F', QColor::fromRgb(0, 0, 0));
1034  ColorMap.insert('G', QColor::fromRgb(0, 0, 0));
1035  ColorMap.insert('K', QColor::fromRgb(0, 0, 0));
1036  ColorMap.insert('M', QColor::fromRgb(0, 0, 0));
1037  break;
1038  case 3: // White stars
1039  ColorMap.insert('O', QColor::fromRgb(255, 255, 255));
1040  ColorMap.insert('B', QColor::fromRgb(255, 255, 255));
1041  ColorMap.insert('A', QColor::fromRgb(255, 255, 255));
1042  ColorMap.insert('F', QColor::fromRgb(255, 255, 255));
1043  ColorMap.insert('G', QColor::fromRgb(255, 255, 255));
1044  ColorMap.insert('K', QColor::fromRgb(255, 255, 255));
1045  ColorMap.insert('M', QColor::fromRgb(255, 255, 255));
1046  case 0: // Real color
1047  default: // And use real color for everything else
1048  ColorMap.insert('O', QColor::fromRgb(0, 0, 255));
1049  ColorMap.insert('B', QColor::fromRgb(0, 200, 255));
1050  ColorMap.insert('A', QColor::fromRgb(0, 255, 255));
1051  ColorMap.insert('F', QColor::fromRgb(200, 255, 100));
1052  ColorMap.insert('G', QColor::fromRgb(255, 255, 0));
1053  ColorMap.insert('K', QColor::fromRgb(255, 100, 0));
1054  ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1055  }
1056 
1057  for (char color : ColorMap.keys())
1058  {
1059  //Add new spectral class
1060 
1061  QPixmap BigImage(15, 15);
1062  BigImage.fill(Qt::transparent);
1063 
1064  QPainter p;
1065  p.begin(&BigImage);
1066 
1067  if (Options::starColorMode() == 0)
1068  {
1069  qreal h, s, v, a;
1071  QColor starColor = ColorMap[color];
1072  starColor.getHsvF(&h, &s, &v, &a);
1073  for (int i = 0; i < 8; i++)
1074  {
1075  for (int j = 0; j < 8; j++)
1076  {
1077  qreal x = i - 7;
1078  qreal y = j - 7;
1079  qreal dist = sqrt(x * x + y * y) / 7.0;
1080  starColor.setHsvF(h, qMin(qreal(1), dist < (10 - starColorIntensity) / 10.0 ? 0 : dist), v,
1081  qMax(qreal(0), dist < (10 - starColorIntensity) / 20.0 ? 1 : 1 - dist));
1082  p.setPen(starColor);
1083  p.drawPoint(i, j);
1084  p.drawPoint((14 - i), j);
1085  p.drawPoint(i, (14 - j));
1086  p.drawPoint((14 - i), (14 - j));
1087  }
1088  }
1089  }
1090  else
1091  {
1093  p.setPen(QPen(ColorMap[color], 2.0));
1094  p.setBrush(p.pen().color());
1095  p.drawEllipse(QRectF(2, 2, 10, 10));
1096  }
1097  p.end();
1098  //[nSPclasses][nStarSizes];
1099  // Cache array slice
1100 
1101  QVector<QPixmap *> *pmap = &imageCache[harvardToIndex(color)];
1102  pmap->append(new QPixmap(BigImage));
1103  for (int size = 1; size < nStarSizes; size++)
1104  {
1105  pmap->append(new QPixmap(
1106  BigImage.scaled(size * ratio, size * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
1107  }
1108  }
1109  //}
1110  starColorMode = Options::starColorMode();
1111  }
1112 }
static double unrefract(const double alt, bool conditional=true)
Remove refraction correction, depending on conditional.
Definition: skypoint.cpp:1091
void append(const T &value)
const dms & alt() const
Definition: skypoint.h:281
bool centerLocked
true if SkyMapLite is centered on an object and only pinch-to-zoom needs to be available
Definition: skymaplite.h:74
This class loads QML files and connects SkyMapLite and KStarsData Unlike KStars class it is not a mai...
Definition: kstarslite.h:46
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition: skypoint.h:194
static constexpr double PI
PI is a const static member; it's public so that it can be used anywhere, as long as dms....
Definition: dms.h:380
void setPen(const QColor &color)
void getHsvF(qreal *h, qreal *s, qreal *v, qreal *a) const const
static SkyMapLite * createInstance()
Creates instance of SkyMapLite (delete the old one if any)
Definition: skymaplite.cpp:215
void append(const T &value)
void setFocus(SkyPoint *f)
sets the central focus point of the sky map.
Definition: skymaplite.cpp:252
Format_ARGB32_Premultiplied
bool isSlewing() const
Definition: skymaplite.cpp:804
A container for nodes that holds collection of textures for stars and provides clipping.
Definition: rootnode.h:59
void updateAutomaticMode()
updates focus of SkyMapLite according to data from DeviceOrientation (Smartphone's sensors)
Definition: skymaplite.cpp:978
void drawEllipse(const QRectF &rectangle)
int width(const QString &text, int len) const const
SkyPoint * focusPoint()
retrieve the FocusPoint position.
Definition: skymaplite.h:149
QVector::iterator begin()
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
virtual QSGNode * updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override
Updates SkyMapLite by calling RootNode::update(), which in turn initiates update of all child nodes.
Definition: skymaplite.cpp:143
int count(const T &value) const const
QSizeF size() const const
QQuickWindow * window() const const
void aperture(SkyPoint *center, double radius, MeshBufNum_t bufNum=DRAW_BUF)
finds the set of trixels that cover the circular aperture specified after first performing a reverse ...
Definition: skymesh.cpp:56
void append(const T &value)
CachingDms * lst()
Definition: kstarsdata.h:223
void destinationChanged()
Emitted by setDestination(), and connected to slewFocus().
virtual QString name(void) const
Definition: skyobject.h:145
static KStarsLite * Instance()
Definition: kstarslite.h:77
void fill(const QColor &color)
SkyPoint * destination()
retrieve the Destination position.
Definition: skymaplite.h:135
void setDestination(const SkyPoint &f)
sets the destination point of the sky map.
Definition: skymaplite.cpp:278
void slotSelectObject(SkyObject *skyObj)
centres skyObj in SkyMap and opens context drawer with skyObj Used in FindDialogLite
Definition: skymaplite.cpp:590
void initialize(QQuickItem *parent)
Bind size to parent's size and initialize star images.
Definition: skymaplite.cpp:222
void slewFocus()
Step the Focus point toward the Destination point.
Definition: skymaplite.cpp:401
int harvardToIndex(char c)
Returns index for a Harvard spectral classification.
Definition: skymaplite.cpp:809
void setFocusAltAz(const dms &alt, const dms &az)
sets the focus point of the sky map, using its alt/az coordinates
Definition: skymaplite.cpp:266
void setupProjector()
Call to set up the projector before update of SkyItems positions begins.
Definition: skymaplite.cpp:735
int descent() const const
const QPen & pen() const const
AllButtons
void slotUpdateSky(bool now)
Update the focus point and call forceUpdate()
Definition: skymaplite.cpp:683
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
Definition: skypoint.cpp:77
void drawText(const QPointF &position, const QString &text)
void updateTime(const bool automaticDSTchange=true)
Update time-dependent data and (possibly) repaint the sky map.
Definition: kstarslite.cpp:234
void slotCenter()
Center the display at the point ClickedPoint.
Definition: skymaplite.cpp:327
void slotZoomIn()
Zoom in one step.
Definition: skymaplite.cpp:575
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:370
void updateFocus()
Update the focus position according to current options.
Definition: skymaplite.cpp:690
QSGTexture * createTextureFromImage(const QImage &image) const const
void slotClockSlewing()
Checks whether the timestep exceeds a threshold value.
Definition: skymaplite.cpp:516
virtual Q_INVOKABLE Projection type() const =0
Return the type of this projection.
double scale() const
Definition: simclock.h:53
bool begin(QPaintDevice *device)
static double deleteLimit()
return limit of hides for the node to delete it
Definition: skymaplite.cpp:199
int size() const const
void update()
bool getSlewing() const
Proxy method for SkyMapDrawAbstract::drawObjectLabels()
Definition: skymaplite.h:422
Q_INVOKABLE void addFOVSymbol(const QString &FOVName, bool initialState=false)
adds FOV symbol to m_FOVSymbols
Definition: skymaplite.cpp:918
void start(int msec)
virtual void updateCoords(const KSNumbers *num, bool includePlanets=true, const CachingDms *lat=nullptr, const CachingDms *LST=nullptr, bool forceRecompute=false)
Determine the current coordinates (RA, Dec) from the catalog coordinates (RA0, Dec0),...
Definition: skypoint.cpp:582
bool end()
KeepAspectRatio
QCursor cursor() const const
QString i18n(const char *text, const TYPE &arg...)
void slewingChanged(bool)
Emitted when SkyMapLite is being slewed or slewing is finished.
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
const CachingDms & dec() const
Definition: skypoint.h:269
const CachingDms * lat() const
Definition: geolocation.h:70
QSGTexture * getCachedTexture(int size, char spType)
returns cached texture from textureCache.
Definition: skymaplite.cpp:210
GeoLocation * geo()
Definition: kstarsdata.h:229
SizeAllCursor
Q_INVOKABLE SimClock * clock()
Definition: kstarsdata.h:217
bool isManualMode() const
Manual Mode is a new (04/2002) addition to the SimClock.
Definition: simclock.h:68
This is just a container that holds information needed to do projections.
Definition: projector.h:36
SkyObject * focusObject() const
Retrieve the object which is centered in the sky map.
Definition: skymaplite.h:252
void setObject(SkyObject *object)
sets SkyObject that is needed to be wrapped
QSGTexture * textToTexture(QString text, QColor color=QColor(255, 255, 255), bool zoomFont=false)
creates QImage from text and converts it to QSGTexture
Definition: skymaplite.cpp:848
void heightChanged()
QVector< QVector< QPixmap * > > getImageCache()
returns cache of star images
Definition: skymaplite.cpp:843
QColor fromRgb(QRgb rgb)
void setBrush(const QBrush &brush)
static const double altCrit
Critical height for atmospheric refraction corrections.
Definition: skypoint.h:718
void set(const dms &r, const dms &d)
Sets RA, Dec and RA0, Dec0 according to arguments.
Definition: skypoint.cpp:63
void setPoint(SkyPoint *point)
sets SkyPoint that is needed to be wrapped
void setFocusPoint(SkyPoint *f)
set the FocusPoint; the position that is to be the next Destination.
Definition: skymaplite.h:204
void symbolsFOVChanged(QStringList)
Emitted when FOVSymbols list was changed (new value appended)
void resizeItem()
Called whenever wrappers' width or height are changed.
Definition: skymaplite.cpp:565
bool fillGround
If the ground is filled, then points below horizon are invisible.
Definition: projector.h:43
static SkyMesh * Instance()
returns the default instance of SkyMesh or null if it has not yet been created.
Definition: skymesh.cpp:39
void mousePointChanged(SkyPoint *)
Emitted when position under mouse changed.
void slotZoomOut()
Zoom out one step.
Definition: skymaplite.cpp:580
void setViewParams(const ViewParams &p)
Update cached values for projector.
Definition: projector.cpp:46
void setFocusObject(SkyObject *o)
Set the FocusObject pointer to the argument.
Definition: skymaplite.cpp:318
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
void setSnapNextFocus(bool b=true)
Disable or re-enable the slewing animation for the next Focus change.
Definition: kstarsdata.h:282
Q_INVOKABLE void setFOVVisible(int index, bool visible)
updates visibility of FOV symbol according to visible
Definition: skymaplite.cpp:932
bool snapNextFocus() const
Definition: kstarsdata.h:267
QVector::iterator end()
Item for displaying sky objects; also handles user interaction events.
Definition: skymaplite.h:58
void forceUpdate()
Recalculates the positions of objects in the sky, and then repaints the sky map.
Definition: skymaplite.cpp:639
void setCursor(const QCursor &cursor)
QPixmap scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
void deleteSkyNode(SkyNode *skyNode)
skyNode will be deleted on the next call to updatePaintNode (currently used only in StarNode(struct i...
Definition: skymaplite.cpp:205
void setAz(dms az)
Sets Az, the Azimuth.
Definition: skypoint.h:230
void slotZoomDefault()
Set default zoom.
Definition: skymaplite.cpp:585
void drawPoint(const QPointF &position)
const CachingDms & ra() const
Definition: skypoint.h:263
void setManualMode(bool on=true)
Sets Manual Mode on/off according to the bool argument.
Definition: simclock.cpp:61
double fov() const
Return the FOV of this projection.
Definition: projector.cpp:88
const double & Degrees() const
Definition: dms.h:141
void drawLine(const QLineF &line)
void setCenterLocked(bool centerLocked)
sets whether SkyMapLite is centered on an object and locked(olny pinch-to-zoom is available)
Definition: skymaplite.cpp:950
QColor color() const const
int length() const const
void setClickedPoint(SkyPoint *f)
Set the ClickedPoint to the skypoint given as an argument.
Definition: skymaplite.cpp:306
void clear()
SkyPoint * focus()
Retrieve the Focus point; the position on the sky at the center of the skymap.
Definition: skymaplite.h:125
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
Definition: skypoint.cpp:143
void setDestinationAltAz(const dms &alt, const dms &az, bool altIsRefracted)
sets the destination point of the sky map, using its alt/az coordinates.
Definition: skymaplite.cpp:290
ClientManagerLite * clientManagerLite() const
Definition: kstarslite.h:107
void setZoomFactor(double factor)
@ Set zoom factor.
Definition: skymaplite.cpp:631
Provides virtual functions for update of coordinates and nodes hiding.
Definition: skynode.h:27
SkyPoint * clickedPoint()
Retrieve the ClickedPoint position.
Definition: skymaplite.h:217
void setHsvF(qreal h, qreal s, qreal v, qreal a)
void setRenderHint(QPainter::RenderHint hint, bool on)
Q_INVOKABLE bool isActive()
Whether the clock is active or not is a bit complicated by the introduction of "manual mode".
Definition: simclock.cpp:96
void setFont(const QFont &font)
Trixel index(const SkyPoint *p)
returns the index of the trixel containing p.
Definition: skymesh.cpp:86
void skyRotationChanged(double skyRotation)
Emitted when skyRotation used to rotate coordinates of SkyPoints is changed.
SmoothTransformation
void setSlewing(bool newSlewing)
sets whether SkyMapLite is being slewed
Definition: skymaplite.cpp:941
~SkyMapLite()
Destructor.
Definition: skymaplite.cpp:241
bool isFOVVisible(int index)
Definition: skymaplite.cpp:927
void zoomChanged()
Emitted when zoom level is changed.
void objectLiteChanged()
Wrapper of ClickedObject for QML.
Information about an object in the sky.
Definition: skyobject.h:41
int height() const const
qreal effectiveDevicePixelRatio() const const
void initStarImages()
Initializes images of Stars and puts them in cache (copied from SkyQPainter)
Definition: skymaplite.cpp:995
QString message
T value(int i) const const
double Hours() const
Definition: dms.h:168
void widthChanged()
void addTelescope(INDI::BaseDevice *bd)
Add telescope symbol for device bd.
Q_INVOKABLE void setAutomaticMode(bool automaticMode)
switch automatic mode on/off according to isOn parameter
Definition: skymaplite.cpp:956
void setClickedObject(SkyObject *o)
Set the ClickedObject pointer to the argument.
Definition: skymaplite.cpp:312
void removeTelescope(INDI::BaseDevice *bd)
Remove telescope symbol of device bd.
const dms & az() const
Definition: skypoint.h:275
QString & fill(QChar ch, int size)
void update(bool clearTextures=false)
update positions of all child SkyItems
Definition: rootnode.cpp:216
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 15 2022 04:04:05 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.