summaryrefslogtreecommitdiffstats
path: root/kstars/kstars/skymap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kstars/kstars/skymap.cpp')
-rw-r--r--kstars/kstars/skymap.cpp1376
1 files changed, 1376 insertions, 0 deletions
diff --git a/kstars/kstars/skymap.cpp b/kstars/kstars/skymap.cpp
new file mode 100644
index 00000000..051ab6d0
--- /dev/null
+++ b/kstars/kstars/skymap.cpp
@@ -0,0 +1,1376 @@
+/***************************************************************************
+ skymap.cpp - K Desktop Planetarium
+ -------------------
+ begin : Sat Feb 10 2001
+ copyright : (C) 2001 by Jason Harris
+ email : jharris@30doradus.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <kstatusbar.h>
+#include <kmessagebox.h>
+#include <kaction.h>
+#include <kstandarddirs.h>
+
+#include <qmemarray.h>
+#include <qpointarray.h>
+#include <qcursor.h>
+#include <qbitmap.h>
+#include <qpainter.h>
+
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "skymap.h"
+#include "Options.h"
+#include "kstars.h"
+#include "kstarsdata.h"
+#include "imageviewer.h"
+#include "infoboxes.h"
+#include "detaildialog.h"
+#include "addlinkdialog.h"
+#include "kspopupmenu.h"
+#include "simclock.h"
+#include "skyobject.h"
+#include "deepskyobject.h"
+#include "ksmoon.h"
+#include "ksasteroid.h"
+#include "kscomet.h"
+#include "starobject.h"
+#include "customcatalog.h"
+
+SkyMap::SkyMap(KStarsData *d, QWidget *parent, const char *name )
+ : QWidget (parent,name), computeSkymap(true), angularDistanceMode(false),
+ ksw(0), data(d), pmenu(0), sky(0), sky2(0), IBoxes(0),
+ ClickedObject(0), FocusObject(0), TransientObject(0),
+ starpix(0), pts(0), sp(0)
+{
+ if ( parent ) ksw = (KStars*) parent->parent();
+ else ksw = 0;
+
+ pts = new QPointArray( 2000 ); // points for milkyway and horizon
+ sp = new SkyPoint(); // needed by coordinate grid
+
+ ZoomRect = QRect();
+
+ setDefaultMouseCursor(); // set the cross cursor
+
+ // load the pixmaps of stars
+ starpix = new StarPixmap( data->colorScheme()->starColorMode(), data->colorScheme()->starColorIntensity() );
+
+ setBackgroundColor( QColor( data->colorScheme()->colorNamed( "SkyColor" ) ) );
+ setBackgroundMode( QWidget::NoBackground );
+ setFocusPolicy( QWidget::StrongFocus );
+ setMinimumSize( 380, 250 );
+ setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
+
+ setMouseTracking (true); //Generate MouseMove events!
+ midMouseButtonDown = false;
+ mouseButtonDown = false;
+ slewing = false;
+ clockSlewing = false;
+
+ ClickedObject = NULL;
+ FocusObject = NULL;
+
+ sky = new QPixmap();
+ sky2 = new QPixmap();
+ pmenu = new KSPopupMenu( ksw );
+
+ //Initialize Transient label stuff
+ TransientTimeout = 100; //fade label color every 0.2 sec
+ connect( &HoverTimer, SIGNAL( timeout() ), this, SLOT( slotTransientLabel() ) );
+ connect( &TransientTimer, SIGNAL( timeout() ), this, SLOT( slotTransientTimeout() ) );
+
+ IBoxes = new InfoBoxes( Options::windowWidth(), Options::windowHeight(),
+ Options::positionTimeBox(), Options::shadeTimeBox(),
+ Options::positionGeoBox(), Options::shadeGeoBox(),
+ Options::positionFocusBox(), Options::shadeFocusBox(),
+ data->colorScheme()->colorNamed( "BoxTextColor" ),
+ data->colorScheme()->colorNamed( "BoxGrabColor" ),
+ data->colorScheme()->colorNamed( "BoxBGColor" ) );
+
+ IBoxes->showTimeBox( Options::showTimeBox() );
+ IBoxes->showFocusBox( Options::showFocusBox() );
+ IBoxes->showGeoBox( Options::showGeoBox() );
+ IBoxes->timeBox()->setAnchorFlag( Options::stickyTimeBox() );
+ IBoxes->geoBox()->setAnchorFlag( Options::stickyGeoBox() );
+ IBoxes->focusBox()->setAnchorFlag( Options::stickyFocusBox() );
+
+ IBoxes->geoChanged( data->geo() );
+
+ connect( IBoxes->timeBox(), SIGNAL( shaded(bool) ), data, SLOT( saveTimeBoxShaded(bool) ) );
+ connect( IBoxes->geoBox(), SIGNAL( shaded(bool) ), data, SLOT( saveGeoBoxShaded(bool) ) );
+ connect( IBoxes->focusBox(), SIGNAL( shaded(bool) ), data, SLOT( saveFocusBoxShaded(bool) ) );
+ connect( IBoxes->timeBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveTimeBoxPos(QPoint) ) );
+ connect( IBoxes->geoBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveGeoBoxPos(QPoint) ) );
+ connect( IBoxes->focusBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveFocusBoxPos(QPoint) ) );
+
+ connect( this, SIGNAL( destinationChanged() ), this, SLOT( slewFocus() ) );
+
+ //Initialize Refraction correction lookup table arrays. RefractCorr1 is for calculating
+ //the apparent altitude from the true altitude, and RefractCorr2 is for the reverse.
+ for ( unsigned int index = 0; index <184; ++index ) {
+ double alt = -1.75 + index*0.5; //start at -1.75 degrees to get midpoint value for each interval.
+
+ RefractCorr1[index] = 1.02 / tan( dms::PI*( alt + 10.3/(alt + 5.11) )/180.0 ) / 60.0; //correction in degrees.
+ RefractCorr2[index] = -1.0 / tan( dms::PI*( alt + 7.31/(alt + 4.4) )/180.0 ) / 60.0;
+ }
+}
+
+SkyMap::~SkyMap() {
+ delete starpix;
+ delete pts;
+ delete sp;
+ delete sky;
+ delete sky2;
+ delete pmenu;
+ delete IBoxes;
+
+//Deprecated...DeepSkyObject dtor now handles this itself.
+/*//delete any remaining object Image pointers
+ for ( DeepSkyObject *obj = data->deepSkyListMessier.first(); obj; obj = data->deepSkyListMessier.next() ) {
+ if ( obj->image() ) obj->deleteImage();
+ }
+ for ( DeepSkyObject *obj = data->deepSkyListNGC.first(); obj; obj = data->deepSkyListNGC.next() ) {
+ if ( obj->image() ) obj->deleteImage();
+ }
+ for ( DeepSkyObject *obj = data->deepSkyListIC.first(); obj; obj = data->deepSkyListIC.next() ) {
+ if ( obj->image() ) obj->deleteImage();
+ }
+ for ( DeepSkyObject *obj = data->deepSkyListOther.first(); obj; obj = data->deepSkyListOther.next() ) {
+ if ( obj->image() ) obj->deleteImage();
+ }*/
+}
+
+void SkyMap::setGeometry( int x, int y, int w, int h ) {
+ QWidget::setGeometry( x, y, w, h );
+ sky->resize( w, h );
+ sky2->resize( w, h );
+}
+
+void SkyMap::setGeometry( const QRect &r ) {
+ QWidget::setGeometry( r );
+ sky->resize( r.width(), r.height() );
+ sky2->resize( r.width(), r.height() );
+}
+
+
+void SkyMap::showFocusCoords( bool coordsOnly ) {
+ if ( ! coordsOnly ) {
+ //display object info in infoBoxes
+ QString oname;
+ oname = i18n( "nothing" );
+ if ( focusObject() != NULL && Options::isTracking() )
+ oname = focusObject()->translatedLongName();
+
+ infoBoxes()->focusObjChanged(oname);
+ }
+
+ if ( Options::useAltAz() && Options::useRefraction() ) {
+ SkyPoint corrFocus( *(focus()) );
+ corrFocus.setAlt( refract( focus()->alt(), false ) );
+ corrFocus.HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ corrFocus.setAlt( refract( focus()->alt(), true ) );
+ infoBoxes()->focusCoordChanged( &corrFocus );
+ } else {
+ infoBoxes()->focusCoordChanged( focus() );
+ }
+}
+
+SkyObject* SkyMap::objectNearest( SkyPoint *p ) {
+ double r0 = 200.0/Options::zoomFactor(); //the maximum search radius
+ double rmin = r0;
+
+ //Search stars database for nearby object.
+ double rstar_min = r0;
+ double starmag_min = 20.0; //absurd initial value
+ int istar_min = -1;
+
+ if ( Options::showStars() ) { //Can only click on a star if it's being drawn!
+
+ //test RA and dec to see if this star is roughly nearby
+
+ for ( register unsigned int i=0; i<data->starList.count(); ++i ) {
+ SkyObject *test = (SkyObject *)data->starList.at(i);
+
+ double dRA = test->ra()->Hours() - p->ra()->Hours();
+ double dDec = test->dec()->Degrees() - p->dec()->Degrees();
+ //determine angular distance between this object and mouse cursor
+ double f = 15.0*cos( test->dec()->radians() );
+ double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if (r < r0 && test->mag() < starmag_min ) {
+ istar_min = i;
+ rstar_min = r;
+ starmag_min = test->mag();
+ }
+ }
+ }
+
+ //Next, find the nearest solar system body within r0
+ double r = 0.0;
+ double rsolar_min = r0;
+ SkyObject *solarminobj = NULL;
+
+ if ( Options::showPlanets() )
+ solarminobj = data->PCat->findClosest( p, r );
+
+ if ( r < r0 ) {
+ rsolar_min = r;
+ } else {
+ solarminobj = NULL;
+ }
+
+ //Moon
+ if ( Options::showMoon() ) {
+ double dRA = data->Moon->ra()->Hours() - p->ra()->Hours();
+ double dDec = data->Moon->dec()->Degrees() - p->dec()->Degrees();
+ double f = 15.0*cos( data->Moon->dec()->radians() );
+ r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if (r < rsolar_min) {
+ solarminobj= data->Moon;
+ rsolar_min = r;
+ }
+ }
+
+ //Asteroids
+ if ( Options::showAsteroids() ) {
+ for ( KSAsteroid *ast = data->asteroidList.first(); ast; ast = data->asteroidList.next() ) {
+ //test RA and dec to see if this object is roughly nearby
+ double dRA = ast->ra()->Hours() - p->ra()->Hours();
+ double dDec = ast->dec()->Degrees() - p->dec()->Degrees();
+ double f = 15.0*cos( ast->dec()->radians() );
+ double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if ( r < rsolar_min && ast->mag() < Options::magLimitAsteroid() ) {
+ solarminobj = ast;
+ rsolar_min = r;
+ }
+ }
+ }
+
+ //Comets
+ if ( Options::showComets() ) {
+ for ( KSComet *com = data->cometList.first(); com; com = data->cometList.next() ) {
+ //test RA and dec to see if this object is roughly nearby
+ double dRA = com->ra()->Hours() - p->ra()->Hours();
+ double dDec = com->dec()->Degrees() - p->dec()->Degrees();
+ double f = 15.0*cos( com->dec()->radians() );
+ double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if ( r < rsolar_min ) {
+ solarminobj = com;
+ rsolar_min = r;
+ }
+ }
+ }
+
+ //Next, search for nearest deep-sky object within r0
+ double rmess_min = r0;
+ double rngc_min = r0;
+ double ric_min = r0;
+ double rother_min = r0;
+ int imess_min = -1;
+ int ingc_min = -1;
+ int iic_min = -1;
+ int iother_min = -1;
+
+ for ( DeepSkyObject *o = data->deepSkyList.first(); o; o = data->deepSkyList.next() ) {
+ bool checkObject = false;
+ if ( o->isCatalogM() &&
+ ( Options::showMessier() || Options::showMessierImages() ) ) checkObject = true;
+ if ( o->isCatalogNGC() && Options::showNGC() ) checkObject = true;
+ if ( o->isCatalogIC() && Options::showIC() ) checkObject = true;
+ if ( o->catalog().isEmpty() && Options::showOther() ) checkObject = true;
+
+ if ( checkObject ) {
+ //test RA and dec to see if this object is roughly nearby
+ double dRA = o->ra()->Hours() - p->ra()->Hours();
+ double dDec = o->dec()->Degrees() - p->dec()->Degrees();
+ double f = 15.0*cos( o->dec()->radians() );
+ double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if ( o->isCatalogM() && r < rmess_min) {
+ imess_min = data->deepSkyList.at();
+ rmess_min = r;
+ }
+ if ( o->isCatalogNGC() && r < rngc_min) {
+ ingc_min = data->deepSkyList.at();
+ rngc_min = r;
+ }
+ if ( o->isCatalogIC() && r < ric_min) {
+ iic_min = data->deepSkyList.at();
+ ric_min = r;
+ }
+ if ( o->catalog().isEmpty() && r < rother_min) {
+ iother_min = data->deepSkyList.at();
+ rother_min = r;
+ }
+ }
+ }
+
+ //Next, search for nearest object within r0 among the custom catalogs
+ double rcust_min = r0;
+ int icust_min = -1;
+ int icust_cat = -1;
+
+ for ( register unsigned int j=0; j< data->CustomCatalogs.count(); ++j ) {
+ if ( Options::showCatalog()[j] ) {
+ QPtrList<SkyObject> catList = data->CustomCatalogs.at(j)->objList();
+
+ for ( register unsigned int i=0; i<catList.count(); ++i ) {
+ //test RA and dec to see if this object is roughly nearby
+ SkyObject *test = (SkyObject *)catList.at(i);
+ double dRA = test->ra()->Hours()-p->ra()->Hours();
+ double dDec = test->dec()->Degrees()-p->dec()->Degrees();
+ double f = 15.0*cos( test->dec()->radians() );
+ double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
+ if (r < rcust_min) {
+ icust_cat = j;
+ icust_min = i;
+ rcust_min = r;
+ }
+ }
+ }
+ }
+
+ int jmin(-1);
+ int icat(-1);
+
+ //Among the objects selected within r0, prioritize the selection by catalog:
+ //Planets, Messier, NGC, IC, stars
+ if ( istar_min >= 0 && rstar_min < r0 ) {
+ rmin = rstar_min;
+ icat = 0; //set catalog to star
+ }
+
+ //IC object overrides star, unless star is twice as close as IC object
+ if ( iic_min >= 0 && ric_min < r0 && rmin > 0.5*ric_min ) {
+ rmin = ric_min;
+ icat = 1; //set catalog to Deep Sky
+ jmin = iic_min;
+ }
+
+ //NGC object overrides previous selection, unless previous is twice as close
+ if ( ingc_min >= 0 && rngc_min < r0 && rmin > 0.5*rngc_min ) {
+ rmin = rngc_min;
+ icat = 1; //set catalog to Deep Sky
+ jmin = ingc_min;
+ }
+
+ //"other" object overrides previous selection, unless previous is twice as close
+ if ( iother_min >= 0 && rother_min < r0 && rmin > 0.5*rother_min ) {
+ rmin = rother_min;
+ icat = 1; //set catalog to Deep Sky
+ jmin = iother_min;
+ }
+
+ //Messier object overrides previous selection, unless previous is twice as close
+ if ( imess_min >= 0 && rmess_min < r0 && rmin > 0.5*rmess_min ) {
+ rmin = rmess_min;
+ icat = 1; //set catalog to Deep Sky
+ jmin = imess_min;
+ }
+
+ //Custom object overrides previous selection, unless previous is twice as close
+ if ( icust_min >= 0 && rcust_min < r0 && rmin > 0.5*rcust_min ) {
+ rmin = rcust_min;
+ icat = 2; //set catalog to Custom
+ }
+
+ //Solar system body overrides previous selection, unless previous selection is twice as close
+ if ( solarminobj != NULL && rmin > 0.5*rsolar_min ) {
+ rmin = rsolar_min;
+ icat = 3; //set catalog to solar system
+ }
+
+ QPtrList<SkyObject> cat;
+
+ switch (icat) {
+ case 0: //star
+ return data->starList.at(istar_min);
+ break;
+
+ case 1: //Deep-Sky Objects
+ return data->deepSkyList.at(jmin);
+ break;
+
+ case 2: //Custom Catalog Object
+ cat = data->CustomCatalogs.at(icust_cat)->objList();
+ return cat.at(icust_min);
+ break;
+
+ case 3: //solar system object
+ return solarminobj;
+ break;
+
+ default: //no object found
+ return NULL;
+ break;
+ }
+}
+
+void SkyMap::slotTransientLabel( void ) {
+ //This function is only called if the HoverTimer manages to timeout.
+ //(HoverTimer is restarted with every mouseMoveEvent; so if it times
+ //out, that means there was no mouse movement for HOVER_INTERVAL msec.)
+ //Identify the object nearest to the mouse cursor as the
+ //TransientObject. The TransientObject is automatically labeled
+ //in SkyMap::paintEvent().
+ //Note that when the TransientObject pointer is not NULL, the next
+ //mouseMoveEvent calls fadeTransientLabel(), which will fade out the
+ //TransientLabel and then set TransientObject to NULL.
+ //
+ //Do not show a transient label if the map is in motion, or if the mouse
+ //pointer is below the opaque horizon, or if the object has a permanent label
+ if ( ! slewing && ! ( Options::useAltAz() && Options::showGround() &&
+ mousePoint()->alt()->Degrees() < 0.0 ) ) {
+ SkyObject *so = objectNearest( mousePoint() );
+
+ if ( so && ! isObjectLabeled( so ) ) {
+ setTransientObject( so );
+
+ TransientColor = data->colorScheme()->colorNamed( "UserLabelColor" );
+ if ( TransientTimer.isActive() ) TransientTimer.stop();
+ update();
+ }
+ }
+}
+
+
+//Slots
+
+void SkyMap::slotTransientTimeout( void ) {
+ //Don't fade label if the transientObject is now the focusObject!
+ if ( transientObject() == focusObject() && Options::useAutoLabel() ) {
+ setTransientObject( NULL );
+ TransientTimer.stop();
+ return;
+ }
+
+ //to fade the labels, we will need to smoothly transition from UserLabelColor to SkyColor.
+ QColor c1 = data->colorScheme()->colorNamed( "UserLabelColor" );
+ QColor c2 = data->colorScheme()->colorNamed( "SkyColor" );
+
+ int dRed = ( c2.red() - c1.red() )/20;
+ int dGreen = ( c2.green() - c1.green() )/20;
+ int dBlue = ( c2.blue() - c1.blue() )/20;
+ int newRed = TransientColor.red() + dRed;
+ int newGreen = TransientColor.green() + dGreen;
+ int newBlue = TransientColor.blue() + dBlue;
+
+ //Check to see if we have arrived at the target color (SkyColor).
+ //If so, point TransientObject to NULL.
+ if ( abs(newRed-c2.red()) < abs(dRed) || abs(newGreen-c2.green()) < abs(dGreen) || abs(newBlue-c2.blue()) < abs(dBlue) ) {
+ setTransientObject( NULL );
+ TransientTimer.stop();
+ } else {
+ TransientColor.setRgb( newRed, newGreen, newBlue );
+ }
+
+ update();
+}
+
+void SkyMap::setFocusObject( SkyObject *o ) {
+ FocusObject = o;
+
+ if ( FocusObject )
+ Options::setFocusObject( FocusObject->name() );
+ else
+ Options::setFocusObject( i18n( "nothing" ) );
+}
+
+void SkyMap::slotCenter( void ) {
+ setFocusPoint( clickedPoint() );
+ if ( Options::useAltAz() )
+ focusPoint()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+
+ //clear the planet trail of old focusObject, if it was temporary
+ if ( focusObject() && focusObject()->isSolarSystem() && data->temporaryTrail ) {
+ ((KSPlanetBase*)focusObject())->clearTrail();
+ data->temporaryTrail = false;
+ }
+
+//If the requested object is below the opaque horizon, issue a warning message
+//(unless user is already pointed below the horizon)
+ if ( Options::useAltAz() && Options::showGround() &&
+ focus()->alt()->Degrees() > -1.0 && focusPoint()->alt()->Degrees() < -1.0 ) {
+
+ QString caption = i18n( "Requested Position Below Horizon" );
+ QString message = i18n( "The requested position is below the horizon.\nWould you like to go there anyway?" );
+ if ( KMessageBox::warningYesNo( this, message, caption,
+ i18n("Go Anyway"), i18n("Keep Position"), "dag_focus_below_horiz" )==KMessageBox::No ) {
+ setClickedObject( NULL );
+ setFocusObject( NULL );
+ Options::setIsTracking( false );
+
+ return;
+ }
+ }
+
+//set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset
+//destination to previous object...
+ setFocusObject( ClickedObject );
+ Options::setIsTracking( true );
+ if ( ksw ) {
+ ksw->actionCollection()->action("track_object")->setIconSet( BarIcon( "encrypted" ) );
+ ksw->toolBar( "mainToolBar" )->setButtonIconSet( 4, BarIcon( "encrypted" ) );
+ ksw->actionCollection()->action("track_object")->setText( i18n( "Stop &Tracking" ) );
+ }
+
+ //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail
+ if ( focusObject() && focusObject()->isSolarSystem()
+ && Options::useAutoTrail()
+ && ! ((KSPlanetBase*)focusObject())->hasTrail() ) {
+ ((KSPlanetBase*)focusObject())->addToTrail();
+ data->temporaryTrail = true;
+ }
+
+ //update the destination to the selected coordinates
+ if ( Options::useAltAz() ) {
+ if ( Options::useRefraction() )
+ setDestinationAltAz( refract( focusPoint()->alt(), true ).Degrees(), focusPoint()->az()->Degrees() );
+ else
+ setDestinationAltAz( focusPoint()->alt()->Degrees(), focusPoint()->az()->Degrees() );
+ } else {
+ setDestination( focusPoint() );
+ }
+
+ focusPoint()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+
+ //display coordinates in statusBar
+ if ( ksw ) {
+ QString sX = focusPoint()->az()->toDMSString();
+ QString sY = focusPoint()->alt()->toDMSString(true);
+ if ( Options::useAltAz() && Options::useRefraction() )
+ sY = refract( focusPoint()->alt(), true ).toDMSString(true);
+ QString s = sX + ", " + sY;
+ ksw->statusBar()->changeItem( s, 1 );
+ s = focusPoint()->ra()->toHMSString() + ", " + focusPoint()->dec()->toDMSString(true);
+ ksw->statusBar()->changeItem( s, 2 );
+ }
+
+ showFocusCoords(); //update FocusBox
+}
+
+void SkyMap::slotDSS( void ) {
+ QString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=1" );
+ QString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
+ dms ra(0.0), dec(0.0);
+ QString RAString, DecString;
+ char decsgn;
+
+ //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
+ //if we clicked on empty sky, we need to precess to J2000.
+ if ( clickedObject() ) {
+ ra.setH( clickedObject()->ra0()->Hours() );
+ dec.setD( clickedObject()->dec0()->Degrees() );
+ } else {
+ //move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
+ clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
+ clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
+ clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
+ ra.setH( clickedPoint()->ra()->Hours() );
+ dec.setD( clickedPoint()->dec()->Degrees() );
+
+ //restore coords from present epoch
+ clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
+ clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
+ }
+
+ RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
+
+ decsgn = '+';
+ if ( dec.Degrees() < 0.0 ) decsgn = '-';
+ int dd = abs( dec.degree() );
+ int dm = abs( dec.arcmin() );
+ int ds = abs( dec.arcsec() );
+ DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
+
+ //concat all the segments into the kview command line:
+ KURL url (URLprefix + RAString + DecString + URLsuffix);
+
+ QString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
+ new ImageViewer (&url, message, this);
+}
+
+void SkyMap::slotDSS2( void ) {
+ QString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=2r" );
+ QString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
+ dms ra(0.0), dec(0.0);
+ QString RAString, DecString;
+ char decsgn;
+
+ //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
+ //if we clicked on empty sky, we need to precess to J2000.
+ if ( clickedObject() ) {
+ ra.setH( clickedObject()->ra0()->Hours() );
+ dec.setD( clickedObject()->dec0()->Degrees() );
+ } else {
+ //move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
+ clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
+ clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
+ clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
+ ra.setH( clickedPoint()->ra()->Hours() );
+ dec.setD( clickedPoint()->dec()->Degrees() );
+
+ //restore coords from present epoch
+ clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
+ clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
+ }
+
+ RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
+
+ decsgn = '+';
+ if ( dec.Degrees() < 0.0 ) decsgn = '-';
+ int dd = abs( dec.degree() );
+ int dm = abs( dec.arcmin() );
+ int ds = abs( dec.arcsec() );
+
+ DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
+
+ //concat all the segments into the kview command line:
+ KURL url (URLprefix + RAString + DecString + URLsuffix);
+
+ QString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
+ new ImageViewer (&url, message, this);
+}
+
+void SkyMap::slotInfo( int id ) {
+ QStringList::Iterator it = clickedObject()->InfoList.at(id-200);
+ QString sURL = (*it);
+ KURL url ( sURL );
+ if (!url.isEmpty())
+ kapp->invokeBrowser(sURL);
+}
+
+void SkyMap::slotBeginAngularDistance(void) {
+ setPreviousClickedPoint( mousePoint() );
+ angularDistanceMode = true;
+ beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
+ endRulerPoint = QPoint( beginRulerPoint.x(),beginRulerPoint.y() );
+}
+
+void SkyMap::slotEndAngularDistance(void) {
+ dms angularDistance;
+ if(angularDistanceMode) {
+ if ( SkyObject *so = objectNearest( mousePoint() ) ) {
+ angularDistance = so->angularDistanceTo( previousClickedPoint() );
+ ksw->statusBar()->changeItem( so->translatedLongName() +
+ " " +
+ i18n("Angular distance: " ) +
+ angularDistance.toDMSString(), 0 );
+ } else {
+ angularDistance = mousePoint()->angularDistanceTo( previousClickedPoint() );
+ ksw->statusBar()->changeItem( i18n("Angular distance: " ) +
+ angularDistance.toDMSString(), 0 );
+ }
+ angularDistanceMode=false;
+ }
+}
+
+void SkyMap::slotCancelAngularDistance(void) {
+ angularDistanceMode=false;
+}
+
+void SkyMap::slotImage( int id ) {
+ QStringList::Iterator it = clickedObject()->ImageList.at(id-100);
+ QStringList::Iterator it2 = clickedObject()->ImageTitle.at(id-100);
+ QString sURL = (*it);
+ QString message = (*it2);
+ KURL url ( sURL );
+ if (!url.isEmpty())
+ new ImageViewer (&url, clickedObject()->messageFromTitle(message), this);
+}
+
+bool SkyMap::isObjectLabeled( SkyObject *object ) {
+ for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
+ if ( o == object ) return true;
+ }
+
+ return false;
+}
+
+void SkyMap::slotRemoveObjectLabel( void ) {
+ for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
+ if ( o == clickedObject() ) {
+ //remove object from list
+ data->ObjLabelList.remove();
+ break;
+ }
+ }
+
+ forceUpdate();
+}
+
+void SkyMap::slotAddObjectLabel( void ) {
+ data->ObjLabelList.append( clickedObject() );
+ //Since we just added a permanent label, we don't want it to fade away!
+ if ( transientObject() == clickedObject() ) setTransientObject( NULL );
+ forceUpdate();
+}
+
+void SkyMap::slotRemovePlanetTrail( void ) {
+ //probably don't need this if-statement, but just to be sure...
+ if ( clickedObject() && clickedObject()->isSolarSystem() ) {
+ ((KSPlanetBase*)clickedObject())->clearTrail();
+ forceUpdate();
+ }
+}
+
+void SkyMap::slotAddPlanetTrail( void ) {
+ //probably don't need this if-statement, but just to be sure...
+ if ( clickedObject() && clickedObject()->isSolarSystem() ) {
+ ((KSPlanetBase*)clickedObject())->addToTrail();
+ forceUpdate();
+ }
+}
+
+void SkyMap::slotDetail( void ) {
+// check if object is selected
+ if ( !clickedObject() ) {
+ KMessageBox::sorry( this, i18n("No object selected."), i18n("Object Details") );
+ return;
+ }
+ DetailDialog detail( clickedObject(), data->ut(), data->geo(), ksw );
+ detail.exec();
+}
+
+void SkyMap::slotClockSlewing() {
+//If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock.
+ if ( fabs( data->clock()->scale() ) > Options::slewTimeScale() ) {
+ if ( ! clockSlewing ) {
+ clockSlewing = true;
+ data->clock()->setManualMode( true );
+
+ // don't change automatically the DST status
+ if ( ksw ) ksw->updateTime( false );
+ }
+ } else {
+ if ( clockSlewing ) {
+ clockSlewing = false;
+ data->clock()->setManualMode( false );
+
+ // don't change automatically the DST status
+ if ( ksw ) ksw->updateTime( false );
+ }
+ }
+}
+
+void SkyMap::setFocus( SkyPoint *p ) {
+ setFocus( p->ra()->Hours(), p->dec()->Degrees() );
+}
+
+void SkyMap::setFocus( const dms &ra, const dms &dec ) {
+ setFocus( ra.Hours(), dec.Degrees() );
+}
+
+void SkyMap::setFocus( double ra, double dec ) {
+ Focus.set( ra, dec );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+}
+
+void SkyMap::setFocusAltAz( const dms &alt, const dms &az) {
+ setFocusAltAz( alt.Degrees(), az.Degrees() );
+}
+
+void SkyMap::setFocusAltAz(double alt, double az) {
+ focus()->setAlt(alt);
+ focus()->setAz(az);
+ focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ slewing = false;
+
+ oldfocus()->set( focus()->ra(), focus()->dec() );
+ oldfocus()->setAz( focus()->az()->Degrees() );
+ oldfocus()->setAlt( focus()->alt()->Degrees() );
+
+ double dHA = data->LST->Hours() - focus()->ra()->Hours();
+ while ( dHA < 0.0 ) dHA += 24.0;
+ data->HourAngle->setH( dHA );
+
+ forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work.
+}
+
+void SkyMap::setDestination( SkyPoint *p ) {
+ Destination.set( p->ra(), p->dec() );
+ destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ emit destinationChanged();
+}
+
+void SkyMap::setDestination( const dms &ra, const dms &dec ) {
+ Destination.set( ra, dec );
+ destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ emit destinationChanged();
+}
+
+void SkyMap::setDestination( double ra, double dec ) {
+ Destination.set( ra, dec );
+ destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ emit destinationChanged();
+}
+
+void SkyMap::setDestinationAltAz( const dms &alt, const dms &az) {
+ destination()->setAlt(alt);
+ destination()->setAz(az);
+ destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ emit destinationChanged();
+}
+
+void SkyMap::setDestinationAltAz(double alt, double az) {
+ destination()->setAlt(alt);
+ destination()->setAz(az);
+ destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ emit destinationChanged();
+}
+
+void SkyMap::updateFocus() {
+ if ( Options::isTracking() && focusObject() != NULL ) {
+ if ( Options::useAltAz() ) {
+ //Tracking any object in Alt/Az mode requires focus updates
+ double dAlt = focusObject()->alt()->Degrees();
+ if ( Options::useRefraction() )
+ dAlt = refract( focusObject()->alt(), true ).Degrees();
+ setFocusAltAz( dAlt, focusObject()->az()->Degrees() );
+ focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ setDestination( focus() );
+ } else {
+ //Tracking in equatorial coords
+ setFocus( focusObject() );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ setDestination( focus() );
+ }
+ } else if ( Options::isTracking() && focusPoint() != NULL ) {
+ if ( Options::useAltAz() ) {
+ //Tracking on empty sky in Alt/Az mode
+ setFocus( focusPoint() );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ setDestination( focus() );
+ }
+ } else if ( ! slewing ) {
+ //Not tracking and not slewing, let sky drift by
+ if ( Options::useAltAz() ) {
+ focus()->setAlt( destination()->alt()->Degrees() );
+ focus()->setAz( destination()->az()->Degrees() );
+ focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ //destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ } else {
+ focus()->setRA( data->LST->Hours() - data->HourAngle->Hours() );
+ setDestination( focus() );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ }
+ }
+
+ //Update the Hour Angle
+ data->setHourAngle( data->LST->Hours() - focus()->ra()->Hours() );
+
+ setOldFocus( focus() );
+ oldfocus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+}
+
+void SkyMap::slewFocus( void ) {
+ double dX, dY, fX, fY, r;
+ double step = 1.0;
+ SkyPoint newFocus;
+
+//Don't slew if the mouse button is pressed
+//Also, no animated slews if the Manual Clock is active
+//08/2002: added possibility for one-time skipping of slew with snapNextFocus
+ if ( !mouseButtonDown ) {
+ bool goSlew = ( Options::useAnimatedSlewing() &&
+ ! data->snapNextFocus() ) &&
+ !( data->clock()->isManualMode() && data->clock()->isActive() );
+ if ( goSlew ) {
+ if ( Options::useAltAz() ) {
+ dX = destination()->az()->Degrees() - focus()->az()->Degrees();
+ dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
+ } else {
+ dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
+ dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
+ }
+
+ //switch directions to go the short way around the celestial sphere, if necessary.
+ if ( dX < -180.0 ) dX = 360.0 + dX;
+ else if ( dX > 180.0 ) dX = -360.0 + dX;
+
+ r = sqrt( dX*dX + dY*dY );
+
+ while ( r > step ) {
+ fX = dX / r;
+ fY = dY / r;
+
+ if ( Options::useAltAz() ) {
+ focus()->setAlt( focus()->alt()->Degrees() + fY*step );
+ focus()->setAz( dms( focus()->az()->Degrees() + fX*step ).reduce() );
+ focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ } else {
+ fX = fX/15.; //convert RA degrees to hours
+ newFocus.set( focus()->ra()->Hours() + fX*step, focus()->dec()->Degrees() + fY*step );
+ setFocus( &newFocus );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ }
+
+ slewing = true;
+ //since we are slewing, fade out the transient label
+ if ( transientObject() && ! TransientTimer.isActive() )
+ fadeTransientLabel();
+
+ forceUpdate();
+ kapp->processEvents(10); //keep up with other stuff
+
+ if ( Options::useAltAz() ) {
+ dX = destination()->az()->Degrees() - focus()->az()->Degrees();
+ dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
+ } else {
+ dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
+ dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
+ }
+
+ //switch directions to go the short way around the celestial sphere, if necessary.
+ if ( dX < -180.0 ) dX = 360.0 + dX;
+ else if ( dX > 180.0 ) dX = -360.0 + dX;
+
+ r = sqrt( dX*dX + dY*dY );
+ }
+ }
+
+ //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination
+ //set focus=destination.
+ if ( Options::useAltAz() ) {
+ setFocusAltAz( destination()->alt()->Degrees(), destination()->az()->Degrees() );
+ focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
+ } else {
+ setFocus( destination() );
+ focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ }
+
+ data->HourAngle->setH( data->LST->Hours() - focus()->ra()->Hours() );
+ slewing = false;
+
+ //Turn off snapNextFocus, we only want it to happen once
+ if ( data->snapNextFocus() ) {
+ data->setSnapNextFocus(false);
+ }
+
+ //Start the HoverTimer. if the user leaves the mouse in place after a slew,
+ //we want to attach a label to the nearest object.
+ if ( Options::useHoverLabel() )
+ HoverTimer.start( HOVER_INTERVAL, true );
+
+ forceUpdate();
+ }
+}
+
+void SkyMap::invokeKey( int key ) {
+ QKeyEvent *e = new QKeyEvent( QEvent::KeyPress, key, 0, 0 );
+ keyPressEvent( e );
+ delete e;
+}
+
+double SkyMap::findPA( SkyObject *o, int x, int y, double scale ) {
+ //Find position angle of North using a test point displaced to the north
+ //displace by 100/zoomFactor radians (so distance is always 100 pixels)
+ //this is 5730/zoomFactor degrees
+ double newDec = o->dec()->Degrees() + 5730.0/Options::zoomFactor();
+ if ( newDec > 90.0 ) newDec = 90.0;
+ SkyPoint test( o->ra()->Hours(), newDec );
+ if ( Options::useAltAz() ) test.EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ QPoint t = getXY( &test, Options::useAltAz(), Options::useRefraction(), scale );
+ double dx = double( t.x() - x );
+ double dy = double( y - t.y() ); //backwards because QWidget Y-axis increases to the bottom
+ double north;
+ if ( dy ) {
+ north = atan( dx/dy )*180.0/dms::PI;
+ //resolve atan ambiguity:
+ if ( dy < 0.0 ) north += 180.0;
+ if ( north >= 360.0 ) north -= 360.;
+ } else {
+ north = 90.0;
+ if ( dx > 0 ) north = -90.0;
+ }
+
+ return ( north + o->pa() );
+}
+
+QPoint SkyMap::getXY( SkyPoint *o, bool Horiz, bool doRefraction, double scale ) {
+ QPoint p;
+
+ double Y, dX;
+ double sindX, cosdX, sinY, cosY, sinY0, cosY0;
+
+ int Width = int( width() * scale );
+ int Height = int( height() * scale );
+
+ double pscale = Options::zoomFactor() * scale;
+
+ if ( Horiz ) {
+ if ( doRefraction ) Y = refract( o->alt(), true ).radians(); //account for atmospheric refraction
+ else Y = o->alt()->radians();
+
+ if ( focus()->az()->Degrees() > 270.0 && o->az()->Degrees() < 90.0 ) {
+ dX = 2*dms::PI + focus()->az()->radians() - o->az()->radians();
+ } else {
+ dX = focus()->az()->radians() - o->az()->radians();
+ }
+
+ focus()->alt()->SinCos( sinY0, cosY0 );
+
+ } else {
+ if (focus()->ra()->Hours() > 18.0 && o->ra()->Hours() < 6.0) {
+ dX = 2*dms::PI + o->ra()->radians() - focus()->ra()->radians();
+ } else {
+ dX = o->ra()->radians() - focus()->ra()->radians();
+ }
+ Y = o->dec()->radians();
+ focus()->dec()->SinCos( sinY0, cosY0 );
+ }
+
+ //Convert dX, Y coords to screen pixel coords.
+ #if ( __GLIBC__ >= 2 && __GLIBC_MINOR__ >=1 ) && !defined(__UCLIBC__)
+ //GNU version
+ sincos( dX, &sindX, &cosdX );
+ sincos( Y, &sinY, &cosY );
+ #else
+ //ANSI version
+ sindX = sin(dX);
+ cosdX = cos(dX);
+ sinY = sin(Y);
+ cosY = cos(Y);
+ #endif
+
+ double c = sinY0*sinY + cosY0*cosY*cosdX;
+
+ if ( c < 0.0 ) { //Object is on "back side" of the celestial sphere; don't plot it.
+ p.setX( -10000000 );
+ p.setY( -10000000 );
+ return p;
+ }
+
+ double k = sqrt( 2.0/( 1 + c ) );
+
+ p.setX( int( 0.5*Width - pscale*k*cosY*sindX ) );
+ p.setY( int( 0.5*Height - pscale*k*( cosY0*sinY - sinY0*cosY*cosdX ) ) );
+
+ return p;
+}
+
+SkyPoint SkyMap::dXdYToRaDec( double dx, double dy, bool useAltAz, dms *LST, const dms *lat, bool doRefract ) {
+ //Determine RA and Dec of a point, given (dx, dy): it's pixel
+ //coordinates in the SkyMap with the center of the map as the origin.
+
+ SkyPoint result;
+ double sinDec, cosDec, sinDec0, cosDec0, sinc, cosc, sinlat, coslat;
+ double xx, yy;
+
+ double r = sqrt( dx*dx + dy*dy );
+ dms centerAngle;
+ centerAngle.setRadians( 2.0*asin(0.5*r) );
+
+ focus()->dec()->SinCos( sinDec0, cosDec0 );
+ centerAngle.SinCos( sinc, cosc );
+
+ if ( useAltAz ) {
+ dms HA;
+ dms Dec, alt, az, alt0, az0;
+ double A;
+ double sinAlt, cosAlt, sinAlt0, cosAlt0, sinAz, cosAz;
+// double HA0 = LST - focus.ra();
+ az0 = focus()->az()->Degrees();
+ alt0 = focus()->alt()->Degrees();
+ alt0.SinCos( sinAlt0, cosAlt0 );
+
+ dx = -dx; //Flip East-west (Az goes in opposite direction of RA)
+ yy = dx*sinc;
+ xx = r*cosAlt0*cosc - dy*sinAlt0*sinc;
+
+ A = atan( yy/xx );
+ //resolve ambiguity of atan():
+ if ( xx<0 ) A = A + dms::PI;
+// if ( xx>0 && yy<0 ) A = A + 2.0*dms::PI;
+
+ dms deltaAz;
+ deltaAz.setRadians( A );
+ az = focus()->az()->Degrees() + deltaAz.Degrees();
+ alt.setRadians( asin( cosc*sinAlt0 + ( dy*sinc*cosAlt0 )/r ) );
+
+ if ( doRefract ) alt.setD( refract( &alt, false ).Degrees() ); //find true altitude from apparent altitude
+
+ az.SinCos( sinAz, cosAz );
+ alt.SinCos( sinAlt, cosAlt );
+ lat->SinCos( sinlat, coslat );
+
+ Dec.setRadians( asin( sinAlt*sinlat + cosAlt*coslat*cosAz ) );
+ Dec.SinCos( sinDec, cosDec );
+
+ HA.setRadians( acos( ( sinAlt - sinlat*sinDec )/( coslat*cosDec ) ) );
+ if ( sinAz > 0.0 ) HA.setH( 24.0 - HA.Hours() );
+
+ result.setRA( LST->Hours() - HA.Hours() );
+ result.setRA( result.ra()->reduce() );
+ result.setDec( Dec.Degrees() );
+
+ return result;
+
+ } else {
+ yy = dx*sinc;
+ xx = r*cosDec0*cosc - dy*sinDec0*sinc;
+
+ double RARad = ( atan( yy / xx ) );
+ //resolve ambiguity of atan():
+ if ( xx<0 ) RARad = RARad + dms::PI;
+// if ( xx>0 && yy<0 ) RARad = RARad + 2.0*dms::PI;
+
+ dms deltaRA, Dec;
+ deltaRA.setRadians( RARad );
+ Dec.setRadians( asin( cosc*sinDec0 + (dy*sinc*cosDec0)/r ) );
+
+ result.setRA( focus()->ra()->Hours() + deltaRA.Hours() );
+ result.setRA( result.ra()->reduce() );
+ result.setDec( Dec.Degrees() );
+
+ return result;
+ }
+}
+
+dms SkyMap::refract( const dms *alt, bool findApparent ) {
+ if ( alt->Degrees() <= -2.000 ) return dms( alt->Degrees() );
+
+ int index = int( ( alt->Degrees() + 2.0 )*2. ); //RefractCorr arrays start at alt=-2.0 degrees.
+ dms result;
+
+ //Failsafe: if the index is out of range, return the original angle
+ if ( index < 0 || index > 183 ) {
+ return dms( alt->Degrees() );
+ }
+
+ if ( findApparent ) {
+ result.setD( alt->Degrees() + RefractCorr1[index] );
+ } else {
+ result.setD( alt->Degrees() + RefractCorr2[index] );
+ }
+
+ return result;
+}
+
+//---------------------------------------------------------------------------
+
+
+// force a new calculation of the skymap (used instead of update(), which may skip the redraw)
+// if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue
+// also, determine new coordinates of mouse cursor.
+void SkyMap::forceUpdate( bool now )
+{
+ QPoint mp( mapFromGlobal( QCursor::pos() ) );
+ double dx = ( 0.5*width() - mp.x() )/Options::zoomFactor();
+ double dy = ( 0.5*height() - mp.y() )/Options::zoomFactor();
+
+ if (! unusablePoint (dx, dy)) {
+ //determine RA, Dec of mouse pointer
+ setMousePoint( dXdYToRaDec( dx, dy, Options::useAltAz(), data->LST, data->geo()->lat(), Options::useRefraction() ) );
+ }
+
+ computeSkymap = true;
+ if ( now ) repaint();
+ else update();
+}
+
+float SkyMap::fov() {
+ if ( width() >= height() )
+ return 28.65*width()/Options::zoomFactor();
+ else
+ return 28.65*height()/Options::zoomFactor();
+}
+
+bool SkyMap::checkVisibility( SkyPoint *p, float FOV, double XMax ) {
+ double dX, dY;
+ bool useAltAz = Options::useAltAz();
+
+ //Skip objects below the horizon if:
+ // + using Horizontal coords,
+ // + the ground is drawn,
+ // + and either of the following is true:
+ // - focus is above the horizon
+ // - field of view is larger than 50 degrees
+ if ( useAltAz && Options::showGround() && p->alt()->Degrees() < -2.0
+ && ( focus()->alt()->Degrees() > 0. || FOV > 50. ) ) return false;
+
+ if ( useAltAz ) {
+ dY = fabs( p->alt()->Degrees() - focus()->alt()->Degrees() );
+ } else {
+ dY = fabs( p->dec()->Degrees() - focus()->dec()->Degrees() );
+ }
+ if ( isPoleVisible ) dY *= 0.75; //increase effective FOV when pole visible.
+ if ( dY > FOV ) return false;
+ if ( isPoleVisible ) return true;
+
+ if ( useAltAz ) {
+ dX = fabs( p->az()->Degrees() - focus()->az()->Degrees() );
+ } else {
+ dX = fabs( p->ra()->Degrees() - focus()->ra()->Degrees() );
+ }
+ if ( dX > 180.0 ) dX = 360.0 - dX; // take shorter distance around sky
+
+ if ( dX < XMax ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool SkyMap::unusablePoint (double dx, double dy)
+{
+ if (dx >= 1.41 || dx <= -1.41 || dy >= 1.41 || dy <= -1.41)
+ return true;
+ else
+ return false;
+}
+
+void SkyMap::setZoomMouseCursor()
+{
+ mouseMoveCursor = false; // no mousemove cursor
+
+ QPainter p;
+ QPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
+// the center of the pixmap
+ int mx = cursorPix. width() / 2;
+ int my = cursorPix. height() / 2;
+
+ cursorPix.fill (white); // white background
+ p.begin (&cursorPix);
+ p.setPen (QPen (black, 2)); // black lines
+
+ p.drawEllipse( mx - 7, my - 7, 14, 14 );
+ p.drawLine( mx + 5, my + 5, mx + 11, my + 11 );
+ p.end();
+
+// create a mask to make parts of the pixmap invisible
+ QBitmap mask (32, 32);
+ mask.fill (color0); // all is invisible
+
+ p.begin (&mask);
+// paint over the parts which should be visible
+ p.setPen (QPen (color1, 3));
+ p.drawEllipse( mx - 7, my - 7, 14, 14 );
+ p.drawLine( mx + 5, my + 5, mx + 12, my + 12 );
+ p.end();
+
+ cursorPix.setMask (mask); // set the mask
+ QCursor cursor (cursorPix);
+ setCursor (cursor);
+}
+
+void SkyMap::setDefaultMouseCursor()
+{
+ mouseMoveCursor = false; // no mousemove cursor
+
+ QPainter p;
+ QPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
+// the center of the pixmap
+ int mx = cursorPix. width() / 2;
+ int my = cursorPix. height() / 2;
+
+ cursorPix.fill (white); // white background
+ p.begin (&cursorPix);
+ p.setPen (QPen (black, 2)); // black lines
+// 1. diagonal
+ p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
+ p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
+// 2. diagonal
+ p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
+ p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
+ p.end();
+
+// create a mask to make parts of the pixmap invisible
+ QBitmap mask (32, 32);
+ mask.fill (color0); // all is invisible
+
+ p.begin (&mask);
+// paint over the parts which should be visible
+ p.setPen (QPen (color1, 3));
+// 1. diagonal
+ p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
+ p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
+// 2. diagonal
+ p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
+ p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
+ p.end();
+
+ cursorPix.setMask (mask); // set the mask
+ QCursor cursor (cursorPix);
+ setCursor (cursor);
+}
+
+void SkyMap::setMouseMoveCursor()
+{
+ if (mouseButtonDown)
+ {
+ setCursor (9); // cursor shape defined in qt
+ mouseMoveCursor = true;
+ }
+}
+
+void SkyMap::addLink( void ) {
+ AddLinkDialog adialog( this, clickedObject()->name() );
+ QString entry;
+ QFile file;
+
+ if ( adialog.exec()==QDialog::Accepted ) {
+ if ( adialog.isImageLink() ) {
+ //Add link to object's ImageList, and descriptive text to its ImageTitle list
+ clickedObject()->ImageList.append( adialog.url() );
+ clickedObject()->ImageTitle.append( adialog.desc() );
+
+ //Also, update the user's custom image links database
+ //check for user's image-links database. If it doesn't exist, create it.
+ file.setName( locateLocal( "appdata", "image_url.dat" ) ); //determine filename in local user KDE directory tree.
+
+ if ( !file.open( IO_ReadWrite | IO_Append ) ) {
+ QString message = i18n( "Custom image-links file could not be opened.\nLink cannot be recorded for future sessions." );
+ KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
+ return;
+ } else {
+ entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
+ QTextStream stream( &file );
+ stream << entry << endl;
+ file.close();
+ emit linkAdded();
+ }
+ } else {
+ clickedObject()->InfoList.append( adialog.url() );
+ clickedObject()->InfoTitle.append( adialog.desc() );
+
+ //check for user's image-links database. If it doesn't exist, create it.
+ file.setName( locateLocal( "appdata", "info_url.dat" ) ); //determine filename in local user KDE directory tree.
+
+ if ( !file.open( IO_ReadWrite | IO_Append ) ) {
+ QString message = i18n( "Custom information-links file could not be opened.\nLink cannot be recorded for future sessions." ); KMessageBox::sorry( 0, message, i18n( "Could not Open File" ) );
+ return;
+ } else {
+ entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
+ QTextStream stream( &file );
+ stream << entry << endl;
+ file.close();
+ emit linkAdded();
+ }
+ }
+ }
+}
+
+void SkyMap::updateAngleRuler() {
+ if ( Options::useAltAz() ) PreviousClickedPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );
+ beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
+
+// endRulerPoint = QPoint(e->x(), e->y());
+ endRulerPoint = mapFromGlobal( QCursor::pos() );
+}
+
+#include "skymap.moc"