moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / kstars / kstars / skymap.cpp
blobede6d1aafb424d7701361be2dc2ca03578fd7f3c
1 /***************************************************************************
2 skymap.cpp - K Desktop Planetarium
3 -------------------
4 begin : Sat Feb 10 2001
5 copyright : (C) 2001 by Jason Harris
6 email : jharris@30doradus.org
7 ***************************************************************************/
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18 #include <kapplication.h>
19 #include <kconfig.h>
20 #include <kiconloader.h>
21 #include <kstatusbar.h>
22 #include <kmessagebox.h>
23 #include <kaction.h>
24 #include <kstandarddirs.h>
26 #include <qmemarray.h>
27 #include <qpointarray.h>
28 #include <qcursor.h>
29 #include <qbitmap.h>
30 #include <qpainter.h>
32 #include <math.h>
33 #include <stdlib.h>
34 #include <unistd.h>
36 #include "skymap.h"
37 #include "Options.h"
38 #include "kstars.h"
39 #include "kstarsdata.h"
40 #include "imageviewer.h"
41 #include "infoboxes.h"
42 #include "detaildialog.h"
43 #include "addlinkdialog.h"
44 #include "kspopupmenu.h"
45 #include "simclock.h"
46 #include "skyobject.h"
47 #include "deepskyobject.h"
48 #include "ksmoon.h"
49 #include "ksasteroid.h"
50 #include "kscomet.h"
51 #include "starobject.h"
53 SkyMap::SkyMap(KStarsData *d, QWidget *parent, const char *name )
54 : QWidget (parent,name), computeSkymap(true), angularDistanceMode(false),
55 ksw(0), data(d), pmenu(0), sky(0), IBoxes(0),
56 ClickedObject(0), FocusObject(0), TransientObject(0),
57 starpix(0), pts(0), sp(0)
59 if ( parent ) ksw = (KStars*) parent->parent();
60 else ksw = 0;
62 pts = new QPointArray( 2000 ); // points for milkyway and horizon
63 sp = new SkyPoint(); // needed by coordinate grid
65 ZoomRect = QRect();
67 setDefaultMouseCursor(); // set the cross cursor
69 // load the pixmaps of stars
70 starpix = new StarPixmap( data->colorScheme()->starColorMode(), data->colorScheme()->starColorIntensity() );
72 setBackgroundColor( QColor( data->colorScheme()->colorNamed( "SkyColor" ) ) );
73 setBackgroundMode( QWidget::NoBackground );
74 setFocusPolicy( QWidget::StrongFocus );
75 setMinimumSize( 380, 250 );
76 setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
78 setMouseTracking (true); //Generate MouseMove events!
79 midMouseButtonDown = false;
80 mouseButtonDown = false;
81 slewing = false;
82 clockSlewing = false;
84 ClickedObject = NULL;
85 FocusObject = NULL;
87 sky = new QPixmap();
88 pmenu = new KSPopupMenu( ksw );
90 //Initialize Transient label stuff
91 TransientTimeout = 100; //fade label color every 0.2 sec
92 connect( &HoverTimer, SIGNAL( timeout() ), this, SLOT( slotTransientLabel() ) );
93 connect( &TransientTimer, SIGNAL( timeout() ), this, SLOT( slotTransientTimeout() ) );
95 IBoxes = new InfoBoxes( Options::windowWidth(), Options::windowHeight(),
96 Options::positionTimeBox(), Options::shadeTimeBox(),
97 Options::positionGeoBox(), Options::shadeGeoBox(),
98 Options::positionFocusBox(), Options::shadeFocusBox(),
99 data->colorScheme()->colorNamed( "BoxTextColor" ),
100 data->colorScheme()->colorNamed( "BoxGrabColor" ),
101 data->colorScheme()->colorNamed( "BoxBGColor" ) );
103 IBoxes->showTimeBox( Options::showTimeBox() );
104 IBoxes->showFocusBox( Options::showFocusBox() );
105 IBoxes->showGeoBox( Options::showGeoBox() );
106 IBoxes->timeBox()->setAnchorFlag( Options::stickyTimeBox() );
107 IBoxes->geoBox()->setAnchorFlag( Options::stickyGeoBox() );
108 IBoxes->focusBox()->setAnchorFlag( Options::stickyFocusBox() );
110 IBoxes->geoChanged( data->geo() );
112 connect( IBoxes->timeBox(), SIGNAL( shaded(bool) ), data, SLOT( saveTimeBoxShaded(bool) ) );
113 connect( IBoxes->geoBox(), SIGNAL( shaded(bool) ), data, SLOT( saveGeoBoxShaded(bool) ) );
114 connect( IBoxes->focusBox(), SIGNAL( shaded(bool) ), data, SLOT( saveFocusBoxShaded(bool) ) );
115 connect( IBoxes->timeBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveTimeBoxPos(QPoint) ) );
116 connect( IBoxes->geoBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveGeoBoxPos(QPoint) ) );
117 connect( IBoxes->focusBox(), SIGNAL( moved(QPoint) ), data, SLOT( saveFocusBoxPos(QPoint) ) );
119 connect( this, SIGNAL( destinationChanged() ), this, SLOT( slewFocus() ) );
121 //Initialize Refraction correction lookup table arrays. RefractCorr1 is for calculating
122 //the apparent altitude from the true altitude, and RefractCorr2 is for the reverse.
123 for ( unsigned int index = 0; index <184; ++index ) {
124 double alt = -1.75 + index*0.5; //start at -1.75 degrees to get midpoint value for each interval.
126 RefractCorr1[index] = 1.02 / tan( dms::PI*( alt + 10.3/(alt + 5.11) )/180.0 ) / 60.0; //correction in degrees.
127 RefractCorr2[index] = -1.0 / tan( dms::PI*( alt + 7.31/(alt + 4.4) )/180.0 ) / 60.0;
131 SkyMap::~SkyMap() {
132 delete starpix;
133 delete pts;
134 delete sp;
135 delete sky;
136 delete pmenu;
137 delete IBoxes;
139 //Deprecated...DeepSkyObject dtor now handles this itself.
140 /*//delete any remaining object Image pointers
141 for ( DeepSkyObject *obj = data->deepSkyListMessier.first(); obj; obj = data->deepSkyListMessier.next() ) {
142 if ( obj->image() ) obj->deleteImage();
144 for ( DeepSkyObject *obj = data->deepSkyListNGC.first(); obj; obj = data->deepSkyListNGC.next() ) {
145 if ( obj->image() ) obj->deleteImage();
147 for ( DeepSkyObject *obj = data->deepSkyListIC.first(); obj; obj = data->deepSkyListIC.next() ) {
148 if ( obj->image() ) obj->deleteImage();
150 for ( DeepSkyObject *obj = data->deepSkyListOther.first(); obj; obj = data->deepSkyListOther.next() ) {
151 if ( obj->image() ) obj->deleteImage();
155 void SkyMap::setGeometry( int x, int y, int w, int h ) {
156 QWidget::setGeometry( x, y, w, h );
157 sky->resize( w, h );
160 void SkyMap::setGeometry( const QRect &r ) {
161 QWidget::setGeometry( r );
162 sky->resize( r.width(), r.height() );
166 void SkyMap::showFocusCoords( void ) {
167 //display object info in infoBoxes
168 QString oname;
169 oname = i18n( "nothing" );
170 if ( focusObject() != NULL && Options::isTracking() )
171 oname = focusObject()->translatedLongName();
173 infoBoxes()->focusObjChanged(oname);
175 if ( Options::useAltAz() && Options::useRefraction() ) {
176 SkyPoint corrFocus( *(focus()) );
177 corrFocus.setAlt( refract( focus()->alt(), false ) );
178 corrFocus.HorizontalToEquatorial( data->LST, data->geo()->lat() );
179 infoBoxes()->focusCoordChanged( &corrFocus );
180 } else {
181 infoBoxes()->focusCoordChanged( focus() );
185 SkyObject* SkyMap::objectNearest( SkyPoint *p ) {
186 double r0 = 200.0/Options::zoomFactor(); //the maximum search radius
187 double rmin = r0;
189 //Search stars database for nearby object.
190 double rstar_min = r0;
191 double starmag_min = 20.0; //absurd initial value
192 int istar_min = -1;
194 if ( Options::showStars() ) { //Can only click on a star if it's being drawn!
196 //test RA and dec to see if this star is roughly nearby
198 for ( register unsigned int i=0; i<data->starList.count(); ++i ) {
199 SkyObject *test = (SkyObject *)data->starList.at(i);
201 double dRA = test->ra()->Hours() - p->ra()->Hours();
202 double dDec = test->dec()->Degrees() - p->dec()->Degrees();
203 //determine angular distance between this object and mouse cursor
204 double f = 15.0*cos( test->dec()->radians() );
205 double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
206 if (r < r0 && test->mag() < starmag_min ) {
207 istar_min = i;
208 rstar_min = r;
209 starmag_min = test->mag();
214 //Next, find the nearest solar system body within r0
215 double r = 0.0;
216 double rsolar_min = r0;
217 SkyObject *solarminobj = NULL;
219 if ( Options::showPlanets() )
220 solarminobj = data->PCat->findClosest( p, r );
222 if ( r < r0 ) {
223 rsolar_min = r;
224 } else {
225 solarminobj = NULL;
228 //Moon
229 if ( Options::showMoon() ) {
230 double dRA = data->Moon->ra()->Hours() - p->ra()->Hours();
231 double dDec = data->Moon->dec()->Degrees() - p->dec()->Degrees();
232 double f = 15.0*cos( data->Moon->dec()->radians() );
233 r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
234 if (r < rsolar_min) {
235 solarminobj= data->Moon;
236 rsolar_min = r;
240 //Asteroids
241 if ( Options::showAsteroids() ) {
242 for ( KSAsteroid *ast = data->asteroidList.first(); ast; ast = data->asteroidList.next() ) {
243 //test RA and dec to see if this object is roughly nearby
244 double dRA = ast->ra()->Hours() - p->ra()->Hours();
245 double dDec = ast->dec()->Degrees() - p->dec()->Degrees();
246 double f = 15.0*cos( ast->dec()->radians() );
247 double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
248 if ( r < rsolar_min && ast->mag() < Options::magLimitAsteroid() ) {
249 solarminobj = ast;
250 rsolar_min = r;
255 //Comets
256 if ( Options::showComets() ) {
257 for ( KSComet *com = data->cometList.first(); com; com = data->cometList.next() ) {
258 //test RA and dec to see if this object is roughly nearby
259 double dRA = com->ra()->Hours() - p->ra()->Hours();
260 double dDec = com->dec()->Degrees() - p->dec()->Degrees();
261 double f = 15.0*cos( com->dec()->radians() );
262 double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
263 if ( r < rsolar_min ) {
264 solarminobj = com;
265 rsolar_min = r;
270 //Next, search for nearest deep-sky object within r0
271 double rmess_min = r0;
272 double rngc_min = r0;
273 double ric_min = r0;
274 double rother_min = r0;
275 int imess_min = -1;
276 int ingc_min = -1;
277 int iic_min = -1;
278 int iother_min = -1;
280 for ( DeepSkyObject *o = data->deepSkyList.first(); o; o = data->deepSkyList.next() ) {
281 bool checkObject = false;
282 if ( o->isCatalogM() &&
283 ( Options::showMessier() || Options::showMessierImages() ) ) checkObject = true;
284 if ( o->isCatalogNGC() && Options::showNGC() ) checkObject = true;
285 if ( o->isCatalogIC() && Options::showIC() ) checkObject = true;
286 if ( o->catalog().isEmpty() && Options::showOther() ) checkObject = true;
288 if ( checkObject ) {
289 //test RA and dec to see if this object is roughly nearby
290 double dRA = o->ra()->Hours() - p->ra()->Hours();
291 double dDec = o->dec()->Degrees() - p->dec()->Degrees();
292 double f = 15.0*cos( o->dec()->radians() );
293 double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
294 if ( o->isCatalogM() && r < rmess_min) {
295 imess_min = data->deepSkyList.at();
296 rmess_min = r;
298 if ( o->isCatalogNGC() && r < rngc_min) {
299 ingc_min = data->deepSkyList.at();
300 rngc_min = r;
302 if ( o->isCatalogIC() && r < ric_min) {
303 iic_min = data->deepSkyList.at();
304 ric_min = r;
306 if ( o->catalog().isEmpty() && r < rother_min) {
307 iother_min = data->deepSkyList.at();
308 rother_min = r;
313 //Next, search for nearest object within r0 among the custom catalogs
314 double rcust_min = r0;
315 int icust_min = -1;
316 int icust_cat = -1;
318 for ( register unsigned int j=0; j<Options::catalogCount(); ++j ) {
319 if ( Options::showCatalog()[j] ) {
320 QPtrList<DeepSkyObject> cat = data->CustomCatalogs[ Options::catalogName()[j] ];
322 for ( register unsigned int i=0; i<cat.count(); ++i ) {
323 //test RA and dec to see if this object is roughly nearby
324 SkyObject *test = (SkyObject *)cat.at(i);
325 double dRA = test->ra()->Hours()-p->ra()->Hours();
326 double dDec = test->dec()->Degrees()-p->dec()->Degrees();
327 double f = 15.0*cos( test->dec()->radians() );
328 double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
329 if (r < rcust_min) {
330 icust_cat = j;
331 icust_min = i;
332 rcust_min = r;
338 int jmin(-1);
339 int icat(-1);
341 //Among the objects selected within r0, prioritize the selection by catalog:
342 //Planets, Messier, NGC, IC, stars
343 if ( istar_min >= 0 && rstar_min < r0 ) {
344 rmin = rstar_min;
345 icat = 0; //set catalog to star
348 //IC object overrides star, unless star is twice as close as IC object
349 if ( iic_min >= 0 && ric_min < r0 && rmin > 0.5*ric_min ) {
350 rmin = ric_min;
351 icat = 1; //set catalog to Deep Sky
352 jmin = iic_min;
355 //NGC object overrides previous selection, unless previous is twice as close
356 if ( ingc_min >= 0 && rngc_min < r0 && rmin > 0.5*rngc_min ) {
357 rmin = rngc_min;
358 icat = 1; //set catalog to Deep Sky
359 jmin = ingc_min;
362 //"other" object overrides previous selection, unless previous is twice as close
363 if ( iother_min >= 0 && rother_min < r0 && rmin > 0.5*rother_min ) {
364 rmin = rother_min;
365 icat = 1; //set catalog to Deep Sky
366 jmin = iother_min;
369 //Messier object overrides previous selection, unless previous is twice as close
370 if ( imess_min >= 0 && rmess_min < r0 && rmin > 0.5*rmess_min ) {
371 rmin = rmess_min;
372 icat = 1; //set catalog to Deep Sky
373 jmin = imess_min;
376 //Custom object overrides previous selection, unless previous is twice as close
377 if ( icust_min >= 0 && rcust_min < r0 && rmin > 0.5*rcust_min ) {
378 rmin = rcust_min;
379 icat = 2; //set catalog to Custom
382 //Solar system body overrides previous selection, unless previous selection is twice as close
383 if ( solarminobj != NULL && rmin > 0.5*rsolar_min ) {
384 rmin = rsolar_min;
385 icat = 3; //set catalog to solar system
388 QPtrList<DeepSkyObject> cat;
390 switch (icat) {
391 case 0: //star
392 return data->starList.at(istar_min);
393 break;
395 case 1: //Deep-Sky Objects
396 return data->deepSkyList.at(jmin);
397 break;
399 case 2: //Custom Catalog Object
400 cat = data->CustomCatalogs[ Options::catalogName()[icust_cat] ];
401 return cat.at(icust_min);
402 break;
404 case 3: //solar system object
405 return solarminobj;
406 break;
408 default: //no object found
409 return NULL;
410 break;
414 void SkyMap::slotTransientLabel( void ) {
415 //This function is only called if the HoverTimer manages to timeout.
416 //(HoverTimer is restarted with every mouseMoveEvent; so if it times
417 //out, that means there was no mouse movement for HOVER_INTERVAL msec.)
418 //Identify the object nearest to the mouse cursor as the
419 //TransientObject. The TransientObject is automatically labeled
420 //in SkyMap::paintEvent().
421 //Note that when the TransientObject pointer is not NULL, the next
422 //mouseMoveEvent calls fadeTransientLabel(), which will fade out the
423 //TransientLabel and then set TransientObject to NULL.
425 //Do not show a transient label if the map is in motion, or if the mouse
426 //pointer is below the opaque horizon, or if the object has a permanent label
427 if ( ! slewing && ! ( Options::useAltAz() && Options::showGround() &&
428 mousePoint()->alt()->Degrees() < 0.0 ) ) {
429 SkyObject *so = objectNearest( mousePoint() );
431 if ( so && ! isObjectLabeled( so ) ) {
432 setTransientObject( so );
434 TransientColor = data->colorScheme()->colorNamed( "UserLabelColor" );
435 if ( TransientTimer.isActive() ) TransientTimer.stop();
436 update();
442 //Slots
444 void SkyMap::slotTransientTimeout( void ) {
445 //to fade the labels, we will need to smoothly transition from UserLabelColor to SkyColor.
446 QColor c1 = data->colorScheme()->colorNamed( "UserLabelColor" );
447 QColor c2 = data->colorScheme()->colorNamed( "SkyColor" );
449 int dRed = ( c2.red() - c1.red() )/20;
450 int dGreen = ( c2.green() - c1.green() )/20;
451 int dBlue = ( c2.blue() - c1.blue() )/20;
452 int newRed = TransientColor.red() + dRed;
453 int newGreen = TransientColor.green() + dGreen;
454 int newBlue = TransientColor.blue() + dBlue;
456 //Check to see if we have arrived at the target color (SkyColor).
457 //If so, point TransientObject to NULL.
458 if ( abs(newRed-c2.red()) < abs(dRed) || abs(newGreen-c2.green()) < abs(dGreen) || abs(newBlue-c2.blue()) < abs(dBlue) ) {
459 setTransientObject( NULL );
460 TransientTimer.stop();
461 } else {
462 TransientColor.setRgb( newRed, newGreen, newBlue );
465 update();
468 void SkyMap::setFocusObject( SkyObject *o ) {
469 FocusObject = o;
471 if ( FocusObject )
472 Options::setFocusObject( FocusObject->name() );
473 else
474 Options::setFocusObject( i18n( "nothing" ) );
477 void SkyMap::slotCenter( void ) {
478 setFocusPoint( clickedPoint() );
479 if ( Options::useAltAz() )
480 focusPoint()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
482 //clear the planet trail of old focusObject, if it was temporary
483 if ( focusObject() && focusObject()->isSolarSystem() && data->temporaryTrail ) {
484 ((KSPlanetBase*)focusObject())->clearTrail();
485 data->temporaryTrail = false;
488 //If the requested object is below the opaque horizon, issue a warning message
489 //(unless user is already pointed below the horizon)
490 if ( Options::useAltAz() && Options::showGround() &&
491 focus()->alt()->Degrees() > -1.0 && focusPoint()->alt()->Degrees() < -1.0 ) {
493 QString caption = i18n( "Requested Position Below Horizon" );
494 QString message = i18n( "The requested position is below the horizon.\nWould you like to go there anyway?" );
495 if ( KMessageBox::warningYesNo( this, message, caption,
496 KStdGuiItem::yes(), KStdGuiItem::no(), "dag_focus_below_horiz" )==KMessageBox::No ) {
497 setClickedObject( NULL );
498 setFocusObject( NULL );
499 Options::setIsTracking( false );
501 return;
505 //set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset
506 //destination to previous object...
507 setFocusObject( ClickedObject );
508 Options::setIsTracking( true );
509 if ( ksw ) {
510 ksw->actionCollection()->action("track_object")->setIconSet( BarIcon( "encrypted" ) );
511 ksw->toolBar( "mainToolBar" )->setButtonIconSet( 4, BarIcon( "encrypted" ) );
512 ksw->actionCollection()->action("track_object")->setText( i18n( "Stop &Tracking" ) );
515 //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail
516 if ( focusObject() && focusObject()->isSolarSystem()
517 && Options::useAutoTrail()
518 && ! ((KSPlanetBase*)focusObject())->hasTrail() ) {
519 ((KSPlanetBase*)focusObject())->addToTrail();
520 data->temporaryTrail = true;
523 //update the destination to the selected coordinates
524 if ( Options::useAltAz() ) {
525 if ( Options::useRefraction() )
526 setDestinationAltAz( refract( focusPoint()->alt(), true ).Degrees(), focusPoint()->az()->Degrees() );
527 else
528 setDestinationAltAz( focusPoint()->alt()->Degrees(), focusPoint()->az()->Degrees() );
529 } else {
530 setDestination( focusPoint() );
533 //display coordinates in statusBar
534 if ( ksw ) {
535 QString s = focusPoint()->az()->toDMSString() + ", " + focusPoint()->alt()->toDMSString(true);
536 ksw->statusBar()->changeItem( s, 1 );
537 s = focusPoint()->ra()->toHMSString() + ", " + focusPoint()->dec()->toDMSString(true);
538 ksw->statusBar()->changeItem( s, 2 );
541 showFocusCoords(); //update FocusBox
544 void SkyMap::slotDSS( void ) {
545 QString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=1" );
546 QString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
547 dms ra(0.0), dec(0.0);
548 QString RAString, DecString;
549 char decsgn;
551 //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
552 //if we clicked on empty sky, we need to precess to J2000.
553 if ( clickedObject() ) {
554 ra.setH( clickedObject()->ra0()->Hours() );
555 dec.setD( clickedObject()->dec0()->Degrees() );
556 } else {
557 //move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
558 clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
559 clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
560 clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
561 ra.setH( clickedPoint()->ra()->Hours() );
562 dec.setD( clickedPoint()->dec()->Degrees() );
564 //restore coords from present epoch
565 clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
566 clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
569 RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
571 decsgn = '+';
572 if ( dec.Degrees() < 0.0 ) decsgn = '-';
573 int dd = abs( dec.degree() );
574 int dm = abs( dec.arcmin() );
575 int ds = abs( dec.arcsec() );
576 DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
578 //concat all the segments into the kview command line:
579 KURL url (URLprefix + RAString + DecString + URLsuffix);
581 QString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
582 new ImageViewer (&url, message, this);
585 void SkyMap::slotDSS2( void ) {
586 QString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=2r" );
587 QString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
588 dms ra(0.0), dec(0.0);
589 QString RAString, DecString;
590 char decsgn;
592 //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
593 //if we clicked on empty sky, we need to precess to J2000.
594 if ( clickedObject() ) {
595 ra.setH( clickedObject()->ra0()->Hours() );
596 dec.setD( clickedObject()->dec0()->Degrees() );
597 } else {
598 //move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
599 clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
600 clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
601 clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
602 ra.setH( clickedPoint()->ra()->Hours() );
603 dec.setD( clickedPoint()->dec()->Degrees() );
605 //restore coords from present epoch
606 clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
607 clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
610 RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
612 decsgn = '+';
613 if ( dec.Degrees() < 0.0 ) decsgn = '-';
614 int dd = abs( dec.degree() );
615 int dm = abs( dec.arcmin() );
616 int ds = abs( dec.arcsec() );
618 DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
620 //concat all the segments into the kview command line:
621 KURL url (URLprefix + RAString + DecString + URLsuffix);
623 QString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
624 new ImageViewer (&url, message, this);
627 void SkyMap::slotInfo( int id ) {
628 QStringList::Iterator it = clickedObject()->InfoList.at(id-200);
629 QString sURL = (*it);
630 KURL url ( sURL );
631 if (!url.isEmpty())
632 kapp->invokeBrowser(sURL);
635 void SkyMap::slotBeginAngularDistance(void) {
636 setPreviousClickedPoint( mousePoint() );
637 angularDistanceMode = true;
638 beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
639 endRulerPoint = QPoint( beginRulerPoint.x(),beginRulerPoint.y() );
642 void SkyMap::slotEndAngularDistance(void) {
643 dms angularDistance;
644 if(angularDistanceMode) {
645 if ( SkyObject *so = objectNearest( mousePoint() ) ) {
646 angularDistance = so->angularDistanceTo( previousClickedPoint() );
647 ksw->statusBar()->changeItem( so->translatedLongName() +
648 " " +
649 i18n("Angular distance: " ) +
650 angularDistance.toDMSString(), 0 );
651 } else {
652 angularDistance = mousePoint()->angularDistanceTo( previousClickedPoint() );
653 ksw->statusBar()->changeItem( i18n("Angular distance: " ) +
654 angularDistance.toDMSString(), 0 );
656 angularDistanceMode=false;
660 void SkyMap::slotCancelAngularDistance(void) {
661 angularDistanceMode=false;
664 void SkyMap::slotImage( int id ) {
665 QStringList::Iterator it = clickedObject()->ImageList.at(id-100);
666 QStringList::Iterator it2 = clickedObject()->ImageTitle.at(id-100);
667 QString sURL = (*it);
668 QString message = (*it2);
669 KURL url ( sURL );
670 if (!url.isEmpty())
671 new ImageViewer (&url, clickedObject()->messageFromTitle(message), this);
674 bool SkyMap::isObjectLabeled( SkyObject *object ) {
675 for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
676 if ( o == object ) return true;
679 return false;
682 void SkyMap::slotRemoveObjectLabel( void ) {
683 for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
684 if ( o == clickedObject() ) {
685 //remove object from list
686 data->ObjLabelList.remove();
687 break;
691 forceUpdate();
694 void SkyMap::slotAddObjectLabel( void ) {
695 data->ObjLabelList.append( clickedObject() );
696 //Since we just added a permanent label, we don't want it to fade away!
697 if ( transientObject() == clickedObject() ) setTransientObject( NULL );
698 forceUpdate();
701 void SkyMap::slotRemovePlanetTrail( void ) {
702 //probably don't need this if-statement, but just to be sure...
703 if ( clickedObject() && clickedObject()->isSolarSystem() ) {
704 ((KSPlanetBase*)clickedObject())->clearTrail();
705 forceUpdate();
709 void SkyMap::slotAddPlanetTrail( void ) {
710 //probably don't need this if-statement, but just to be sure...
711 if ( clickedObject() && clickedObject()->isSolarSystem() ) {
712 ((KSPlanetBase*)clickedObject())->addToTrail();
713 forceUpdate();
717 void SkyMap::slotDetail( void ) {
718 // check if object is selected
719 if ( !clickedObject() ) {
720 KMessageBox::sorry( this, i18n("No object selected."), i18n("Object Details") );
721 return;
723 DetailDialog detail( clickedObject(), data->ut(), data->geo(), ksw );
724 detail.exec();
727 void SkyMap::slotClockSlewing() {
728 //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock.
729 if ( fabs( data->clock()->scale() ) > Options::slewTimeScale() ) {
730 if ( ! clockSlewing ) {
731 clockSlewing = true;
732 data->clock()->setManualMode( true );
734 // don't change automatically the DST status
735 if ( ksw ) ksw->updateTime( false );
737 } else {
738 if ( clockSlewing ) {
739 clockSlewing = false;
740 data->clock()->setManualMode( false );
742 // don't change automatically the DST status
743 if ( ksw ) ksw->updateTime( false );
748 void SkyMap::setFocus( SkyPoint *p ) {
749 setFocus( p->ra()->Hours(), p->dec()->Degrees() );
752 void SkyMap::setFocus( const dms &ra, const dms &dec ) {
753 setFocus( ra.Hours(), dec.Degrees() );
756 void SkyMap::setFocus( double ra, double dec ) {
757 Focus.set( ra, dec );
758 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
761 void SkyMap::setFocusAltAz( const dms &alt, const dms &az) {
762 setFocusAltAz( alt.Degrees(), az.Degrees() );
765 void SkyMap::setFocusAltAz(double alt, double az) {
766 focus()->setAlt(alt);
767 focus()->setAz(az);
768 focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
769 slewing = false;
771 oldfocus()->set( focus()->ra(), focus()->dec() );
772 oldfocus()->setAz( focus()->az()->Degrees() );
773 oldfocus()->setAlt( focus()->alt()->Degrees() );
775 double dHA = data->LST->Hours() - focus()->ra()->Hours();
776 while ( dHA < 0.0 ) dHA += 24.0;
777 data->HourAngle->setH( dHA );
779 forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work.
782 void SkyMap::setDestination( SkyPoint *p ) {
783 Destination.set( p->ra(), p->dec() );
784 destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
785 emit destinationChanged();
788 void SkyMap::setDestination( const dms &ra, const dms &dec ) {
789 Destination.set( ra, dec );
790 destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
791 emit destinationChanged();
794 void SkyMap::setDestination( double ra, double dec ) {
795 Destination.set( ra, dec );
796 destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
797 emit destinationChanged();
800 void SkyMap::setDestinationAltAz( const dms &alt, const dms &az) {
801 destination()->setAlt(alt);
802 destination()->setAz(az);
803 destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
804 emit destinationChanged();
807 void SkyMap::setDestinationAltAz(double alt, double az) {
808 destination()->setAlt(alt);
809 destination()->setAz(az);
810 destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
811 emit destinationChanged();
814 void SkyMap::updateFocus() {
815 if ( Options::isTracking() && focusObject() != NULL ) {
816 if ( Options::useAltAz() ) {
817 //Tracking any object in Alt/Az mode requires focus updates
818 double dAlt = focusObject()->alt()->Degrees();
819 if ( Options::useRefraction() )
820 dAlt = refract( focusObject()->alt(), true ).Degrees();
821 setFocusAltAz( dAlt, focusObject()->az()->Degrees() );
822 focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
823 setDestination( focus() );
824 } else {
825 //Tracking in equatorial coords
826 setFocus( focusObject() );
827 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
828 setDestination( focus() );
830 } else if ( Options::isTracking() && focusPoint() != NULL ) {
831 if ( Options::useAltAz() ) {
832 //Tracking on empty sky in Alt/Az mode
833 setFocus( focusPoint() );
834 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
835 setDestination( focus() );
837 } else if ( ! slewing ) {
838 //Not tracking and not slewing, let sky drift by
839 if ( Options::useAltAz() ) {
840 focus()->setAlt( destination()->alt()->Degrees() );
841 focus()->setAz( destination()->az()->Degrees() );
842 focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
843 //destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
844 } else {
845 focus()->setRA( data->LST->Hours() - data->HourAngle->Hours() );
846 setDestination( focus() );
847 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
848 destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
852 //Update the Hour Angle
853 data->setHourAngle( data->LST->Hours() - focus()->ra()->Hours() );
855 setOldFocus( focus() );
856 oldfocus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
859 void SkyMap::slewFocus( void ) {
860 double dX, dY, fX, fY, r;
861 double step = 1.0;
862 SkyPoint newFocus;
864 //Don't slew if the mouse button is pressed
865 //Also, no animated slews if the Manual Clock is active
866 //08/2002: added possibility for one-time skipping of slew with snapNextFocus
867 if ( !mouseButtonDown ) {
868 bool goSlew = ( Options::useAnimatedSlewing() &&
869 ! data->snapNextFocus() ) &&
870 !( data->clock()->isManualMode() && data->clock()->isActive() );
871 if ( goSlew ) {
872 if ( Options::useAltAz() ) {
873 dX = destination()->az()->Degrees() - focus()->az()->Degrees();
874 dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
875 } else {
876 dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
877 dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
880 //switch directions to go the short way around the celestial sphere, if necessary.
881 if ( dX < -180.0 ) dX = 360.0 + dX;
882 else if ( dX > 180.0 ) dX = -360.0 + dX;
884 r = sqrt( dX*dX + dY*dY );
886 while ( r > step ) {
887 fX = dX / r;
888 fY = dY / r;
890 if ( Options::useAltAz() ) {
891 focus()->setAlt( focus()->alt()->Degrees() + fY*step );
892 focus()->setAz( dms( focus()->az()->Degrees() + fX*step ).reduce() );
893 focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
894 } else {
895 fX = fX/15.; //convert RA degrees to hours
896 newFocus.set( focus()->ra()->Hours() + fX*step, focus()->dec()->Degrees() + fY*step );
897 setFocus( &newFocus );
898 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
901 slewing = true;
902 //since we are slewing, fade out the transient label
903 if ( transientObject() && ! TransientTimer.isActive() )
904 fadeTransientLabel();
906 forceUpdate();
907 kapp->processEvents(10); //keep up with other stuff
909 if ( Options::useAltAz() ) {
910 dX = destination()->az()->Degrees() - focus()->az()->Degrees();
911 dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
912 } else {
913 dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
914 dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
917 //switch directions to go the short way around the celestial sphere, if necessary.
918 if ( dX < -180.0 ) dX = 360.0 + dX;
919 else if ( dX > 180.0 ) dX = -360.0 + dX;
921 r = sqrt( dX*dX + dY*dY );
925 //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination
926 //set focus=destination.
927 if ( Options::useAltAz() ) {
928 setFocusAltAz( destination()->alt()->Degrees(), destination()->az()->Degrees() );
929 focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
930 } else {
931 setFocus( destination() );
932 focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
935 data->HourAngle->setH( data->LST->Hours() - focus()->ra()->Hours() );
936 slewing = false;
938 //Turn off snapNextFocus, we only want it to happen once
939 if ( data->snapNextFocus() ) {
940 data->setSnapNextFocus(false);
943 //Start the HoverTimer. if the user leaves the mouse in place after a slew,
944 //we want to attach a label to the nearest object.
945 if ( Options::useHoverLabel() )
946 HoverTimer.start( HOVER_INTERVAL, true );
948 forceUpdate();
952 void SkyMap::invokeKey( int key ) {
953 QKeyEvent *e = new QKeyEvent( QEvent::KeyPress, key, 0, 0 );
954 keyPressEvent( e );
955 delete e;
958 double SkyMap::findPA( SkyObject *o, int x, int y, double scale ) {
959 //Find position angle of North using a test point displaced to the north
960 //displace by 100/zoomFactor radians (so distance is always 100 pixels)
961 //this is 5730/zoomFactor degrees
962 double newDec = o->dec()->Degrees() + 5730.0/Options::zoomFactor();
963 if ( newDec > 90.0 ) newDec = 90.0;
964 SkyPoint test( o->ra()->Hours(), newDec );
965 if ( Options::useAltAz() ) test.EquatorialToHorizontal( data->LST, data->geo()->lat() );
966 QPoint t = getXY( &test, Options::useAltAz(), Options::useRefraction(), scale );
967 double dx = double( t.x() - x );
968 double dy = double( y - t.y() ); //backwards because QWidget Y-axis increases to the bottom
969 double north;
970 if ( dy ) {
971 north = atan( dx/dy )*180.0/dms::PI;
972 //resolve atan ambiguity:
973 if ( dy < 0.0 ) north += 180.0;
974 if ( north >= 360.0 ) north -= 360.;
975 } else {
976 north = 90.0;
977 if ( dx > 0 ) north = -90.0;
980 return ( north + o->pa() );
983 QPoint SkyMap::getXY( SkyPoint *o, bool Horiz, bool doRefraction, double scale ) {
984 QPoint p;
986 double Y, dX;
987 double sindX, cosdX, sinY, cosY, sinY0, cosY0;
989 int Width = int( width() * scale );
990 int Height = int( height() * scale );
992 double pscale = Options::zoomFactor() * scale;
994 if ( Horiz ) {
995 if ( doRefraction ) Y = refract( o->alt(), true ).radians(); //account for atmospheric refraction
996 else Y = o->alt()->radians();
998 if ( focus()->az()->Degrees() > 270.0 && o->az()->Degrees() < 90.0 ) {
999 dX = 2*dms::PI + focus()->az()->radians() - o->az()->radians();
1000 } else {
1001 dX = focus()->az()->radians() - o->az()->radians();
1004 focus()->alt()->SinCos( sinY0, cosY0 );
1006 } else {
1007 if (focus()->ra()->Hours() > 18.0 && o->ra()->Hours() < 6.0) {
1008 dX = 2*dms::PI + o->ra()->radians() - focus()->ra()->radians();
1009 } else {
1010 dX = o->ra()->radians() - focus()->ra()->radians();
1012 Y = o->dec()->radians();
1013 focus()->dec()->SinCos( sinY0, cosY0 );
1016 //Convert dX, Y coords to screen pixel coords.
1017 #if ( __GLIBC__ >= 2 && __GLIBC_MINOR__ >=1 )
1018 //GNU version
1019 sincos( dX, &sindX, &cosdX );
1020 sincos( Y, &sinY, &cosY );
1021 #else
1022 //ANSI version
1023 sindX = sin(dX);
1024 cosdX = cos(dX);
1025 sinY = sin(Y);
1026 cosY = cos(Y);
1027 #endif
1029 double c = sinY0*sinY + cosY0*cosY*cosdX;
1031 if ( c < 0.0 ) { //Object is on "back side" of the celestial sphere; don't plot it.
1032 p.setX( -10000000 );
1033 p.setY( -10000000 );
1034 return p;
1037 double k = sqrt( 2.0/( 1 + c ) );
1039 p.setX( int( 0.5*Width - pscale*k*cosY*sindX ) );
1040 p.setY( int( 0.5*Height - pscale*k*( cosY0*sinY - sinY0*cosY*cosdX ) ) );
1042 return p;
1045 SkyPoint SkyMap::dXdYToRaDec( double dx, double dy, bool useAltAz, dms *LST, const dms *lat, bool doRefract ) {
1046 //Determine RA and Dec of a point, given (dx, dy): it's pixel
1047 //coordinates in the SkyMap with the center of the map as the origin.
1049 SkyPoint result;
1050 double sinDec, cosDec, sinDec0, cosDec0, sinc, cosc, sinlat, coslat;
1051 double xx, yy;
1053 double r = sqrt( dx*dx + dy*dy );
1054 dms centerAngle;
1055 centerAngle.setRadians( 2.0*asin(0.5*r) );
1057 focus()->dec()->SinCos( sinDec0, cosDec0 );
1058 centerAngle.SinCos( sinc, cosc );
1060 if ( useAltAz ) {
1061 dms HA;
1062 dms Dec, alt, az, alt0, az0;
1063 double A;
1064 double sinAlt, cosAlt, sinAlt0, cosAlt0, sinAz, cosAz;
1065 // double HA0 = LST - focus.ra();
1066 az0 = focus()->az()->Degrees();
1067 alt0 = focus()->alt()->Degrees();
1068 alt0.SinCos( sinAlt0, cosAlt0 );
1070 dx = -dx; //Flip East-west (Az goes in opposite direction of RA)
1071 yy = dx*sinc;
1072 xx = r*cosAlt0*cosc - dy*sinAlt0*sinc;
1074 A = atan( yy/xx );
1075 //resolve ambiguity of atan():
1076 if ( xx<0 ) A = A + dms::PI;
1077 // if ( xx>0 && yy<0 ) A = A + 2.0*dms::PI;
1079 dms deltaAz;
1080 deltaAz.setRadians( A );
1081 az = focus()->az()->Degrees() + deltaAz.Degrees();
1082 alt.setRadians( asin( cosc*sinAlt0 + ( dy*sinc*cosAlt0 )/r ) );
1084 if ( doRefract ) alt.setD( refract( &alt, false ).Degrees() ); //find true altitude from apparent altitude
1086 az.SinCos( sinAz, cosAz );
1087 alt.SinCos( sinAlt, cosAlt );
1088 lat->SinCos( sinlat, coslat );
1090 Dec.setRadians( asin( sinAlt*sinlat + cosAlt*coslat*cosAz ) );
1091 Dec.SinCos( sinDec, cosDec );
1093 HA.setRadians( acos( ( sinAlt - sinlat*sinDec )/( coslat*cosDec ) ) );
1094 if ( sinAz > 0.0 ) HA.setH( 24.0 - HA.Hours() );
1096 result.setRA( LST->Hours() - HA.Hours() );
1097 result.setRA( result.ra()->reduce() );
1098 result.setDec( Dec.Degrees() );
1100 return result;
1102 } else {
1103 yy = dx*sinc;
1104 xx = r*cosDec0*cosc - dy*sinDec0*sinc;
1106 double RARad = ( atan( yy / xx ) );
1107 //resolve ambiguity of atan():
1108 if ( xx<0 ) RARad = RARad + dms::PI;
1109 // if ( xx>0 && yy<0 ) RARad = RARad + 2.0*dms::PI;
1111 dms deltaRA, Dec;
1112 deltaRA.setRadians( RARad );
1113 Dec.setRadians( asin( cosc*sinDec0 + (dy*sinc*cosDec0)/r ) );
1115 result.setRA( focus()->ra()->Hours() + deltaRA.Hours() );
1116 result.setRA( result.ra()->reduce() );
1117 result.setDec( Dec.Degrees() );
1119 return result;
1123 dms SkyMap::refract( const dms *alt, bool findApparent ) {
1124 if ( alt->Degrees() <= -2.000 ) return dms( alt->Degrees() );
1126 int index = int( ( alt->Degrees() + 2.0 )*2. ); //RefractCorr arrays start at alt=-2.0 degrees.
1127 dms result;
1129 if ( findApparent ) {
1130 result.setD( alt->Degrees() + RefractCorr1[index] );
1131 } else {
1132 result.setD( alt->Degrees() + RefractCorr2[index] );
1135 return result;
1138 //---------------------------------------------------------------------------
1141 // force a new calculation of the skymap (used instead of update(), which may skip the redraw)
1142 // if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue
1143 // also, determine new coordinates of mouse cursor.
1144 void SkyMap::forceUpdate( bool now )
1146 QPoint mp( mapFromGlobal( QCursor::pos() ) );
1147 double dx = ( 0.5*width() - mp.x() )/Options::zoomFactor();
1148 double dy = ( 0.5*height() - mp.y() )/Options::zoomFactor();
1150 if (! unusablePoint (dx, dy)) {
1151 //determine RA, Dec of mouse pointer
1152 setMousePoint( dXdYToRaDec( dx, dy, Options::useAltAz(), data->LST, data->geo()->lat(), Options::useRefraction() ) );
1155 computeSkymap = true;
1156 if ( now ) repaint();
1157 else update();
1160 float SkyMap::fov() {
1161 if ( width() >= height() )
1162 return 28.65*width()/Options::zoomFactor();
1163 else
1164 return 28.65*height()/Options::zoomFactor();
1167 bool SkyMap::checkVisibility( SkyPoint *p, float FOV, double XMax ) {
1168 double dX, dY;
1169 bool useAltAz = Options::useAltAz();
1171 //Skip objects below the horizon if:
1172 // + using Horizontal coords,
1173 // + the ground is drawn,
1174 // + and either of the following is true:
1175 // - focus is above the horizon
1176 // - field of view is larger than 50 degrees
1177 if ( useAltAz && Options::showGround() && p->alt()->Degrees() < -2.0
1178 && ( focus()->alt()->Degrees() > 0. || FOV > 50. ) ) return false;
1180 if ( useAltAz ) {
1181 dY = fabs( p->alt()->Degrees() - focus()->alt()->Degrees() );
1182 } else {
1183 dY = fabs( p->dec()->Degrees() - focus()->dec()->Degrees() );
1185 if ( isPoleVisible ) dY *= 0.75; //increase effective FOV when pole visible.
1186 if ( dY > FOV ) return false;
1187 if ( isPoleVisible ) return true;
1189 if ( useAltAz ) {
1190 dX = fabs( p->az()->Degrees() - focus()->az()->Degrees() );
1191 } else {
1192 dX = fabs( p->ra()->Degrees() - focus()->ra()->Degrees() );
1194 if ( dX > 180.0 ) dX = 360.0 - dX; // take shorter distance around sky
1196 if ( dX < XMax ) {
1197 return true;
1198 } else {
1199 return false;
1203 bool SkyMap::unusablePoint (double dx, double dy)
1205 if (dx >= 1.41 || dx <= -1.41 || dy >= 1.41 || dy <= -1.41)
1206 return true;
1207 else
1208 return false;
1211 void SkyMap::setZoomMouseCursor()
1213 mouseMoveCursor = false; // no mousemove cursor
1215 QPainter p;
1216 QPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
1217 // the center of the pixmap
1218 int mx = cursorPix. width() / 2;
1219 int my = cursorPix. height() / 2;
1221 cursorPix.fill (white); // white background
1222 p.begin (&cursorPix);
1223 p.setPen (QPen (black, 2)); // black lines
1225 p.drawEllipse( mx - 7, my - 7, 14, 14 );
1226 p.drawLine( mx + 5, my + 5, mx + 11, my + 11 );
1227 p.end();
1229 // create a mask to make parts of the pixmap invisible
1230 QBitmap mask (32, 32);
1231 mask.fill (color0); // all is invisible
1233 p.begin (&mask);
1234 // paint over the parts which should be visible
1235 p.setPen (QPen (color1, 3));
1236 p.drawEllipse( mx - 7, my - 7, 14, 14 );
1237 p.drawLine( mx + 5, my + 5, mx + 12, my + 12 );
1238 p.end();
1240 cursorPix.setMask (mask); // set the mask
1241 QCursor cursor (cursorPix);
1242 setCursor (cursor);
1245 void SkyMap::setDefaultMouseCursor()
1247 mouseMoveCursor = false; // no mousemove cursor
1249 QPainter p;
1250 QPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
1251 // the center of the pixmap
1252 int mx = cursorPix. width() / 2;
1253 int my = cursorPix. height() / 2;
1255 cursorPix.fill (white); // white background
1256 p.begin (&cursorPix);
1257 p.setPen (QPen (black, 2)); // black lines
1258 // 1. diagonal
1259 p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
1260 p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
1261 // 2. diagonal
1262 p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
1263 p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
1264 p.end();
1266 // create a mask to make parts of the pixmap invisible
1267 QBitmap mask (32, 32);
1268 mask.fill (color0); // all is invisible
1270 p.begin (&mask);
1271 // paint over the parts which should be visible
1272 p.setPen (QPen (color1, 3));
1273 // 1. diagonal
1274 p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
1275 p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
1276 // 2. diagonal
1277 p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
1278 p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
1279 p.end();
1281 cursorPix.setMask (mask); // set the mask
1282 QCursor cursor (cursorPix);
1283 setCursor (cursor);
1286 void SkyMap::setMouseMoveCursor()
1288 if (mouseButtonDown)
1290 setCursor (9); // cursor shape defined in qt
1291 mouseMoveCursor = true;
1295 void SkyMap::addLink( void ) {
1296 AddLinkDialog adialog( this, clickedObject()->name() );
1297 QString entry;
1298 QFile file;
1300 if ( adialog.exec()==QDialog::Accepted ) {
1301 if ( adialog.isImageLink() ) {
1302 //Add link to object's ImageList, and descriptive text to its ImageTitle list
1303 clickedObject()->ImageList.append( adialog.url() );
1304 clickedObject()->ImageTitle.append( adialog.desc() );
1306 //Also, update the user's custom image links database
1307 //check for user's image-links database. If it doesn't exist, create it.
1308 file.setName( locateLocal( "appdata", "image_url.dat" ) ); //determine filename in local user KDE directory tree.
1310 if ( !file.open( IO_ReadWrite | IO_Append ) ) {
1311 QString message = i18n( "Custom image-links file could not be opened.\nLink cannot be recorded for future sessions." );
1312 KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
1313 return;
1314 } else {
1315 entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
1316 QTextStream stream( &file );
1317 stream << entry << endl;
1318 file.close();
1319 emit linkAdded();
1321 } else {
1322 clickedObject()->InfoList.append( adialog.url() );
1323 clickedObject()->InfoTitle.append( adialog.desc() );
1325 //check for user's image-links database. If it doesn't exist, create it.
1326 file.setName( locateLocal( "appdata", "info_url.dat" ) ); //determine filename in local user KDE directory tree.
1328 if ( !file.open( IO_ReadWrite | IO_Append ) ) {
1329 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" ) );
1330 return;
1331 } else {
1332 entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
1333 QTextStream stream( &file );
1334 stream << entry << endl;
1335 file.close();
1336 emit linkAdded();
1342 void SkyMap::updateAngleRuler() {
1343 if ( Options::useAltAz() ) PreviousClickedPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );
1344 beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
1346 // endRulerPoint = QPoint(e->x(), e->y());
1347 endRulerPoint = mapFromGlobal( QCursor::pos() );
1350 #include "skymap.moc"