/*************************************************************************** skymap.cpp - Trinity 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, TQWidget *parent, const char *name ) : TQWidget (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 TQPointArray( 2000 ); // points for milkyway and horizon sp = new SkyPoint(); // needed by coordinate grid ZoomRect = TQRect(); setDefaultMouseCursor(); // set the cross cursor // load the pixmaps of stars starpix = new StarPixmap( data->colorScheme()->starColorMode(), data->colorScheme()->starColorIntensity() ); setBackgroundColor( TQColor( data->colorScheme()->colorNamed( "SkyColor" ) ) ); setBackgroundMode( TQWidget::NoBackground ); setFocusPolicy( TQWidget::StrongFocus ); setMinimumSize( 380, 250 ); setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding ) ); setMouseTracking (true); //Generate MouseMove events! midMouseButtonDown = false; mouseButtonDown = false; slewing = false; clockSlewing = false; ClickedObject = NULL; FocusObject = NULL; sky = new TQPixmap(); sky2 = new TQPixmap(); pmenu = new KSPopupMenu( ksw ); //Initialize Transient label stuff TransientTimeout = 100; //fade label color every 0.2 sec connect( &HoverTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( slotTransientLabel() ) ); connect( &TransientTimer, TQ_SIGNAL( timeout() ), this, TQ_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(), TQ_SIGNAL( shaded(bool) ), data, TQ_SLOT( saveTimeBoxShaded(bool) ) ); connect( IBoxes->geoBox(), TQ_SIGNAL( shaded(bool) ), data, TQ_SLOT( saveGeoBoxShaded(bool) ) ); connect( IBoxes->focusBox(), TQ_SIGNAL( shaded(bool) ), data, TQ_SLOT( saveFocusBoxShaded(bool) ) ); connect( IBoxes->timeBox(), TQ_SIGNAL( moved(TQPoint) ), data, TQ_SLOT( saveTimeBoxPos(TQPoint) ) ); connect( IBoxes->geoBox(), TQ_SIGNAL( moved(TQPoint) ), data, TQ_SLOT( saveGeoBoxPos(TQPoint) ) ); connect( IBoxes->focusBox(), TQ_SIGNAL( moved(TQPoint) ), data, TQ_SLOT( saveFocusBoxPos(TQPoint) ) ); connect( this, TQ_SIGNAL( destinationChanged() ), this, TQ_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 ) { TQWidget::setGeometry( x, y, w, h ); sky->resize( w, h ); sky2->resize( w, h ); } void SkyMap::setGeometry( const TQRect &r ) { TQWidget::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 TQString 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 ( unsigned int i=0; istarList.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 ( unsigned int j=0; j< data->CustomCatalogs.count(); ++j ) { if ( Options::showCatalog()[j] ) { TQPtrList catList = data->CustomCatalogs.at(j)->objList(); for ( unsigned int i=0; ira()->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 } TQPtrList 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. TQColor c1 = data->colorScheme()->colorNamed( "UserLabelColor" ); TQColor 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 ) { TQString caption = i18n( "Requested Position Below Horizon" ); TQString 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 ) { TQString sX = focusPoint()->az()->toDMSString(); TQString sY = focusPoint()->alt()->toDMSString(true); if ( Options::useAltAz() && Options::useRefraction() ) sY = refract( focusPoint()->alt(), true ).toDMSString(true); TQString 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 ) { TQString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=1" ); TQString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" ); dms ra(0.0), dec(0.0); TQString 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); TQString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." ); new ImageViewer (&url, message, this); } void SkyMap::slotDSS2( void ) { TQString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=2r" ); TQString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" ); dms ra(0.0), dec(0.0); TQString 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); TQString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." ); new ImageViewer (&url, message, this); } void SkyMap::slotInfo( int id ) { TQStringList::Iterator it = clickedObject()->InfoList.at(id-200); TQString 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 = TQPoint( 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 ) { TQStringList::Iterator it = clickedObject()->ImageList.at(id-100); TQStringList::Iterator it2 = clickedObject()->ImageTitle.at(id-100); TQString sURL = (*it); TQString 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 ) { TQKeyEvent *e = new TQKeyEvent( TQEvent::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() ); TQPoint t = getXY( &test, Options::useAltAz(), Options::useRefraction(), scale ); double dx = double( t.x() - x ); double dy = double( y - t.y() ); //backwards because TQWidget 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() ); } TQPoint SkyMap::getXY( SkyPoint *o, bool Horiz, bool doRefraction, double scale ) { TQPoint 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 ) { TQPoint mp( mapFromGlobal( TQCursor::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 TQPainter p; TQPixmap 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 (TQPen (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 TQBitmap mask (32, 32); mask.fill (color0); // all is invisible p.begin (&mask); // paint over the parts which should be visible p.setPen (TQPen (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 TQCursor cursor (cursorPix); setCursor (cursor); } void SkyMap::setDefaultMouseCursor() { mouseMoveCursor = false; // no mousemove cursor TQPainter p; TQPixmap 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 (TQPen (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 TQBitmap mask (32, 32); mask.fill (color0); // all is invisible p.begin (&mask); // paint over the parts which should be visible p.setPen (TQPen (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 TQCursor 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() ); TQString entry; TQFile file; if ( adialog.exec()==TQDialog::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 ) ) { TQString 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(); TQTextStream 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 ) ) { TQString 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(); TQTextStream 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 = TQPoint(e->x(), e->y()); endRulerPoint = mapFromGlobal( TQCursor::pos() ); } #include "skymap.moc"