1 /***************************************************************************
2 altvstime.cpp - description
4 begin : wed nov 17 08:05:11 CET 2002
5 copyright : (C) 2002-2003 by Pablo de Vicente
7 ***************************************************************************/
9 /***************************************************************************
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. *
16 ***************************************************************************/
21 #include <klineedit.h>
23 #include <kpushbutton.h>
24 #include <kdialogbase.h>
26 #include "altvstime.h"
27 #include "altvstimeui.h"
31 #include "kstarsdata.h"
33 #include "skyobject.h"
34 #include "skyobjectname.h"
35 #include "ksnumbers.h"
36 #include "objectnamelist.h"
38 #include "finddialog.h"
39 #include "locationdialog.h"
41 #include "kstarsdatetime.h"
42 #include "libkdeedu/extdate/extdatetimeedit.h"
44 AltVsTime::AltVsTime( QWidget
* parent
) :
45 KDialogBase( KDialogBase::Plain
, i18n( "Altitude vs. Time" ), Close
, Close
, parent
)
47 ks
= (KStars
*) parent
;
49 QFrame
*page
= plainPage();
52 topLayout
= new QVBoxLayout( page
, 0, spacingHint() );
54 View
= new AVTPlotWidget( -12.0, 12.0, -90.0, 90.0, page
);
55 View
->setMinimumSize( 400, 400 );
56 View
->setSizePolicy( QSizePolicy::MinimumExpanding
, QSizePolicy::MinimumExpanding
);
57 View
->setXAxisType( KStarsPlotWidget::TIME
);
58 View
->setYAxisType( KStarsPlotWidget::ANGLE
);
59 View
->setShowGrid( false );
60 View
->setXAxisLabel( i18n( "Local Time" ) );
61 View
->setXAxisLabel2( i18n( "Local Sidereal Time" ) );
62 View
->setYAxisLabel( i18n( "the angle of an object above (or below) the horizon", "Altitude" ) );
64 avtUI
= new AltVsTimeUI( page
);
65 avtUI
->raBox
->setDegType( false );
66 avtUI
->decBox
->setDegType( true );
69 //Doesn't make sense to manually adjust long/lat unless we can modify TZ also
70 avtUI
->longBox
->setReadOnly( true );
71 avtUI
->latBox
->setReadOnly( true );
73 topLayout
->addWidget( View
);
74 topLayout
->addWidget( avtUI
);
80 if ( getDate().time().hour() > 12 ) DayOffset
= 1;
82 avtUI
->longBox
->show( geo
->lng() );
83 avtUI
->latBox
->show( geo
->lat() );
85 computeSunRiseSetTimes();
88 View
->updateTickmarks();
90 connect( avtUI
->browseButton
, SIGNAL( clicked() ), this, SLOT( slotBrowseObject() ) );
91 connect( avtUI
->cityButton
, SIGNAL( clicked() ), this, SLOT( slotChooseCity() ) );
92 connect( avtUI
->updateButton
, SIGNAL( clicked() ), this, SLOT( slotUpdateDateLoc() ) );
93 connect( avtUI
->clearButton
, SIGNAL( clicked() ), this, SLOT( slotClear() ) );
94 connect( avtUI
->addButton
, SIGNAL( clicked() ), this, SLOT( slotAddSource() ) );
95 connect( avtUI
->nameBox
, SIGNAL( returnPressed() ), this, SLOT( slotAddSource() ) );
96 connect( avtUI
->raBox
, SIGNAL( returnPressed() ), this, SLOT( slotAddSource() ) );
97 connect( avtUI
->decBox
, SIGNAL( returnPressed() ), this, SLOT( slotAddSource() ) );
98 connect( avtUI
->clearFieldsButton
, SIGNAL( clicked() ), this, SLOT( slotClearBoxes() ) );
99 connect( avtUI
->longBox
, SIGNAL( returnPressed() ), this, SLOT( slotAdvanceFocus() ) );
100 connect( avtUI
->latBox
, SIGNAL( returnPressed() ), this, SLOT( slotAdvanceFocus() ) );
101 connect( avtUI
->PlotList
, SIGNAL( highlighted(int) ), this, SLOT( slotHighlight() ) );
103 pList
.setAutoDelete(FALSE
);
104 deleteList
.setAutoDelete(TRUE
); //needed for skypoints which may be created in this class
106 //ther edit boxes should not pass on the return key!
107 avtUI
->nameBox
->setTrapReturnKey( true );
108 avtUI
->raBox
->setTrapReturnKey( true );
109 avtUI
->decBox
->setTrapReturnKey( true );
111 setMouseTracking( true );
114 AltVsTime::~AltVsTime()
116 // no need to delete child widgets, Qt does it all for us
119 void AltVsTime::slotAddSource(void) {
120 bool objFound( false );
122 //First, attempt to find the object name in the list of known objects
123 if ( ! avtUI
->nameBox
->text().isEmpty() ) {
124 ObjectNameList
&ObjNames
= ks
->data()->ObjNames
;
125 QString text
= avtUI
->nameBox
->text().lower();
127 for( SkyObjectName
*name
= ObjNames
.first( text
); name
; name
= ObjNames
.next() ) {
128 if ( name
->text().lower() == text
) {
130 SkyObject
*o
= name
->skyObject();
138 if ( !objFound
) kdDebug() << "No object named " << avtUI
->nameBox
->text() << " found." << endl
;
141 //Next, if the name, RA, and Dec fields are all filled, construct a new skyobject
142 //with these parameters
143 if ( !objFound
&& ! avtUI
->nameBox
->text().isEmpty() && ! avtUI
->raBox
->text().isEmpty() && ! avtUI
->decBox
->text().isEmpty() ) {
145 dms
newRA( 0.0 ), newDec( 0.0 );
146 newRA
= avtUI
->raBox
->createDms( false, &ok
);
147 if ( ok
) newDec
= avtUI
->decBox
->createDms( true, &ok
);
149 //If the epochName is blank (or any non-double), we assume J2000
150 //Otherwise, precess to J2000.
152 dt
.setFromEpoch( getEpoch( avtUI
->epochName
->text() ) );
153 long double jd
= dt
.djd();
155 SkyPoint
ptest( newRA
, newDec
);
156 ptest
.precessFromAnyEpoch( jd
, J2000
);
157 newRA
.setH( ptest
.ra()->Hours() );
158 newDec
.setD( ptest
.dec()->Degrees() );
161 //make sure the coords do not already exist from another object
163 for ( SkyPoint
*p
= pList
.first(); p
; p
= pList
.next() ) {
164 //within an arcsecond?
165 if ( fabs( newRA
.Degrees() - p
->ra()->Degrees() ) < 0.0003 && fabs( newDec
.Degrees() - p
->dec()->Degrees() ) < 0.0003 ) {
171 kdDebug() << "This point is already displayed; I will not duplicate it." << endl
;
176 SkyObject
*obj
= new SkyObject( 8, newRA
, newDec
, 1.0, avtUI
->nameBox
->text() );
177 deleteList
.append( obj
); //this object will be deleted when window is destroyed
178 processObject( obj
);
181 //If the Ra and Dec boxes are filled, but the name field is empty,
182 //move input focus to nameBox`
183 } else if ( avtUI
->nameBox
->text().isEmpty() && ! avtUI
->raBox
->text().isEmpty() && ! avtUI
->decBox
->text().isEmpty() ) {
184 avtUI
->nameBox
->QWidget::setFocus();
186 //nameBox is empty, and one of the ra or dec fields is empty. Move input focus to empty coord box
187 } else if ( avtUI
->raBox
->text().isEmpty() ) {
188 avtUI
->raBox
->QWidget::setFocus();
189 } else if ( avtUI
->decBox
->text().isEmpty() ) {
190 avtUI
->decBox
->QWidget::setFocus();
193 View
->repaint(false);
196 //Use find dialog to choose an object
197 void AltVsTime::slotBrowseObject(void) {
199 if ( fd
.exec() == QDialog::Accepted
) {
200 SkyObject
*o
= fd
.currentItem()->objName()->skyObject();
207 void AltVsTime::processObject( SkyObject
*o
, bool forceAdd
) {
208 KSNumbers
*num
= new KSNumbers( getDate().djd() );
209 KSNumbers
*oldNum
= 0;
211 //If the object is in the solar system, recompute its position for the given epochLabel
212 if ( o
&& o
->isSolarSystem() ) {
213 oldNum
= new KSNumbers( ks
->data()->ut().djd() );
214 o
->updateCoords( num
, true, geo
->lat(), ks
->LST() );
217 //precess coords to target epoch
218 o
->updateCoords( num
);
220 //If this point is not in list already, add it to list
222 for ( SkyPoint
*p
= pList
.first(); p
; p
= pList
.next() ) {
223 if ( o
->ra()->Degrees() == p
->ra()->Degrees() && o
->dec()->Degrees() == p
->dec()->Degrees() ) {
228 if ( found
&& !forceAdd
) kdDebug() << "This point is already displayed; I will not duplicate it." << endl
;
230 pList
.append( (SkyPoint
*)o
);
232 //make sure existing curves are thin and red
233 for ( int i
=0; i
< View
->objectCount(); ++i
) {
234 KPlotObject
*obj
= View
->object( i
);
235 if ( obj
->size() == 2 ) {
236 obj
->setColor( "red" );
241 //add new curve with width=2, and color=white
242 KPlotObject
*po
= new KPlotObject( "", "white", KPlotObject::CURVE
, 2, KPlotObject::SOLID
);
243 for ( double h
=-12.0; h
<=12.0; h
+=0.5 ) {
244 po
->addPoint( new DPoint( h
, findAltitude( o
, h
) ) );
246 View
->addObject( po
);
248 avtUI
->PlotList
->insertItem( o
->translatedName() );
249 avtUI
->PlotList
->setCurrentItem( avtUI
->PlotList
->count() - 1 );
250 avtUI
->raBox
->showInHours(o
->ra() );
251 avtUI
->decBox
->showInDegrees(o
->dec() );
252 avtUI
->nameBox
->setText(o
->translatedName() );
254 //Set epochName to epoch shown in date tab
255 avtUI
->epochName
->setText( QString().setNum( getDate().epoch() ) );
257 kdDebug() << "Currently, there are " << View
->objectCount() << " objects displayed." << endl
;
259 //restore original position
260 if ( o
->isSolarSystem() ) {
261 o
->updateCoords( oldNum
, true, ks
->geo()->lat(), ks
->LST() );
267 double AltVsTime::findAltitude( SkyPoint
*p
, double hour
) {
268 hour
+= 24.0*(double)DayOffset
;
270 //getDate converts the user-entered local time to UT
271 KStarsDateTime ut
= getDate().addSecs( hour
*3600.0 );
273 dms LST
= geo
->GSTtoLST( ut
.gst() );
274 p
->EquatorialToHorizontal( &LST
, geo
->lat() );
275 return p
->alt()->Degrees();
278 void AltVsTime::slotHighlight(void) {
279 int iPlotList
= avtUI
->PlotList
->currentItem();
281 //highlight the curve of the selected object
282 for ( int i
=0; i
<View
->objectCount(); ++i
) {
283 KPlotObject
*obj
= View
->object( i
);
285 if ( i
== iPlotList
) {
287 obj
->setColor( "white" );
290 obj
->setColor( "red" );
297 for ( SkyPoint
*p
= pList
.first(); p
; p
= pList
.next() ) {
298 if ( index
== iPlotList
) {
299 avtUI
->raBox
->showInHours(p
->ra() );
300 avtUI
->decBox
->showInDegrees(p
->dec() );
301 avtUI
->nameBox
->setText(avtUI
->PlotList
->currentText() );
307 //move input focus to the next logical widget
308 void AltVsTime::slotAdvanceFocus(void) {
309 if ( sender()->name() == QString( "nameBox" ) ) avtUI
->addButton
->setFocus();
310 if ( sender()->name() == QString( "raBox" ) ) avtUI
->decBox
->setFocus();
311 if ( sender()->name() == QString( "decbox" ) ) avtUI
->addButton
->setFocus();
312 if ( sender()->name() == QString( "longBox" ) ) avtUI
->latBox
->setFocus();
313 if ( sender()->name() == QString( "latBox" ) ) avtUI
->updateButton
->setFocus();
316 void AltVsTime::slotClear(void) {
317 if ( pList
.count() ) pList
.clear();
318 if ( deleteList
.count() ) deleteList
.clear();
319 avtUI
->PlotList
->clear();
320 avtUI
->nameBox
->clear();
321 avtUI
->raBox
->clear();
322 avtUI
->decBox
->clear();
323 avtUI
->epochName
->clear();
324 View
->clearObjectList();
328 void AltVsTime::slotClearBoxes(void) {
329 avtUI
->nameBox
->clear();
330 avtUI
->raBox
->clear() ;
331 avtUI
->decBox
->clear();
332 avtUI
->epochName
->setText( QString().setNum( getDate().epoch() ) );
335 void AltVsTime::computeSunRiseSetTimes() {
336 //Determine the time of sunset and sunrise for the desired date and location
337 //expressed as doubles, the fraction of a full day.
338 KStarsDateTime today
= getDate();
340 SkyObject
*oSun
= (SkyObject
*) ks
->data()->PCat
->planetSun();
341 double sunRise
= -1.0 * oSun
->riseSetTime( today
.djd() + 1.0, geo
, true ).secsTo(QTime()) / 86400.0;
342 double sunSet
= -1.0 * oSun
->riseSetTime( today
.djd(), geo
, false ).secsTo(QTime()) / 86400.0;
344 //check to see if Sun is circumpolar
345 //requires temporary repositioning of Sun to target date
346 KSNumbers
*num
= new KSNumbers( today
.djd() );
347 KSNumbers
*oldNum
= new KSNumbers( ks
->data()->ut().djd() );
348 dms LST
= geo
->GSTtoLST( getDate().gst() );
349 oSun
->updateCoords( num
, true, geo
->lat(), &LST
);
350 if ( oSun
->checkCircumpolar( geo
->lat() ) ) {
351 if ( oSun
->alt()->Degrees() > 0.0 ) {
352 //Circumpolar, signal it this way:
356 //never rises, signal it this way:
362 //Notify the View about new sun rise/set times:
363 View
->setSunRiseSetTimes( sunRise
, sunSet
);
365 //Restore Sun coordinates:
366 oSun
->updateCoords( oldNum
, true, ks
->geo()->lat(), ks
->LST() );
372 void AltVsTime::slotUpdateDateLoc(void) {
373 KStarsDateTime today
= getDate();
374 KSNumbers
*num
= new KSNumbers( today
.djd() );
375 KSNumbers
*oldNum
= 0;
376 dms LST
= geo
->GSTtoLST( today
.gst() );
378 //First determine time of sunset and sunrise
379 computeSunRiseSetTimes();
381 for ( unsigned int i
= 0; i
< avtUI
->PlotList
->count(); ++i
) {
382 QString oName
= avtUI
->PlotList
->text( i
).lower();
383 ObjectNameList
&ObjNames
= ks
->data()->ObjNames
;
384 bool objFound(false);
386 for( SkyObjectName
*name
= ObjNames
.first( oName
); name
; name
= ObjNames
.next() ) {
387 if ( name
->text().lower() == oName
) {
389 SkyObject
*o
= name
->skyObject();
391 //If the object is in the solar system, recompute its position for the given date
392 if ( o
->isSolarSystem() ) {
393 oldNum
= new KSNumbers( ks
->data()->ut().djd() );
394 o
->updateCoords( num
, true, geo
->lat(), &LST
);
397 //precess coords to target epoch
398 o
->updateCoords( num
);
401 pList
.replace( i
, (SkyPoint
*)o
);
403 KPlotObject
*po
= new KPlotObject( "", "white", KPlotObject::CURVE
, 1, KPlotObject::SOLID
);
404 for ( double h
=-12.0; h
<=12.0; h
+=0.5 ) {
405 po
->addPoint( new DPoint( h
, findAltitude( o
, h
) ) );
407 View
->replaceObject( i
, po
);
409 //restore original position
410 if ( o
->isSolarSystem() ) {
411 o
->updateCoords( oldNum
, true, ks
->data()->geo()->lat(), ks
->LST() );
421 if ( ! objFound
) { //assume unfound object is a custom object
422 pList
.at(i
)->updateCoords( num
); //precess to desired epoch
424 KPlotObject
*po
= new KPlotObject( "", "white", KPlotObject::CURVE
, 1, KPlotObject::SOLID
);
425 for ( double h
=-12.0; h
<=12.0; h
+=0.5 ) {
426 po
->addPoint( new DPoint( h
, findAltitude( pList
.at(i
), h
) ) );
428 View
->replaceObject( i
, po
);
432 if ( getDate().time().hour() > 12 ) DayOffset
= 1;
442 void AltVsTime::slotChooseCity(void) {
443 LocationDialog
ld(ks
);
444 if ( ld
.exec() == QDialog::Accepted
) {
445 GeoLocation
*newGeo
= ld
.selectedCity();
448 avtUI
->latBox
->showInDegrees( geo
->lat() );
449 avtUI
->longBox
->showInDegrees( geo
->lng() );
454 int AltVsTime::currentPlotListItem() const {
455 return avtUI
->PlotList
->currentItem();
458 void AltVsTime::setLSTLimits(void) {
459 //UT at noon on target date
460 KStarsDateTime ut
= getDate().addSecs( ((double)DayOffset
+ 0.5)*86400. );
462 dms lst
= geo
->GSTtoLST( ut
.gst() );
463 View
->setSecondaryLimits( lst
.Hours(), lst
.Hours() + 24.0, -90.0, 90.0 );
466 void AltVsTime::showCurrentDate (void)
468 KStarsDateTime dt
= KStarsDateTime::currentDateTime();
469 if ( dt
.time() > QTime( 12, 0, 0 ) ) dt
= dt
.addDays( 1 );
470 avtUI
->dateBox
->setDate( dt
.date() );
473 KStarsDateTime
AltVsTime::getDate (void)
475 //convert midnight local time to UT:
476 KStarsDateTime
dt( avtUI
->dateBox
->date(), QTime() );
477 dt
= geo
->LTtoUT( dt
);
481 double AltVsTime::getEpoch (QString eName
)
483 //If Epoch field not a double, assume J2000
485 double epoch
= eName
.toDouble(&ok
);
488 kdDebug() << "Invalid Epoch. Assuming 2000.0." << endl
;
495 AVTPlotWidget::AVTPlotWidget( double x1
, double x2
, double y1
, double y2
, QWidget
*parent
, const char* name
)
496 : KStarsPlotWidget( x1
, x2
, y1
, y2
, parent
, name
)
498 //Default SunRise/SunSet values
503 void AVTPlotWidget::mousePressEvent( QMouseEvent
*e
) {
507 void AVTPlotWidget::mouseMoveEvent( QMouseEvent
*e
) {
508 QRect
checkRect( leftPadding(), topPadding(), PixRect
.width(), PixRect
.height() );
509 int Xcursor
= e
->x();
510 int Ycursor
= e
->y();
512 if ( ! checkRect
.contains( e
->x(), e
->y() ) ) {
513 if ( e
->x() < checkRect
.left() ) Xcursor
= checkRect
.left();
514 if ( e
->x() > checkRect
.right() ) Xcursor
= checkRect
.right();
515 if ( e
->y() < checkRect
.top() ) Ycursor
= checkRect
.top();
516 if ( e
->y() > checkRect
.bottom() ) Ycursor
= checkRect
.bottom();
519 Xcursor
-= leftPadding();
520 Ycursor
-= topPadding();
522 QPixmap
buffer2( *buffer
);
525 p
.translate( leftPadding(), topPadding() );
526 p
.setPen( QPen( "grey", 1, SolidLine
) );
527 p
.drawLine( Xcursor
, 0, Xcursor
, PixRect
.height() );
528 p
.drawLine( 0, Ycursor
, PixRect
.width(), Ycursor
);
530 bitBlt( this, 0, 0, &buffer2
);
533 void AVTPlotWidget::paintEvent( QPaintEvent */
*e*/
) {
537 p
.fillRect( 0, 0, width(), height(), bgColor() );
539 p
.translate( leftPadding(), topPadding() );
541 int pW
= PixRect
.width();
542 int pH
= PixRect
.height();
544 //draw daytime sky if the Sun rises for the current date/location
545 //(when Sun does not rise, SunSet = -1.0)
546 if ( SunSet
!= -1.0 ) {
547 //If Sun does not set, then just fill the daytime sky color
548 if ( SunSet
== 1.0 ) {
549 p
.fillRect( 0, 0, pW
, int(0.5*pH
), QColor( 0, 100, 200 ) );
551 //Display centered on midnight, so need to modulate dawn/dusk by 0.5
552 int dawn
= int(pW
*(0.5 + SunRise
));
553 int dusk
= int(pW
*(SunSet
- 0.5));
554 p
.fillRect( 0, 0, dusk
, int(0.5*pH
), QColor( 0, 100, 200 ) );
555 p
.fillRect( dawn
, 0, pW
- dawn
, int(0.5*pH
), QColor( 0, 100, 200 ) );
557 //draw twilight gradients
558 unsigned short int W
= 40;
559 for ( unsigned short int i
=0; i
<W
; ++i
) {
560 p
.setPen( QColor( 0, int(100*i
/W
), 200*i
/W
) );
561 p
.drawLine( dusk
- (i
-20), 0, dusk
- (i
-20), pH
);
562 p
.drawLine( dawn
+ (i
-20), 0, dawn
+ (i
-20), pH
);
568 p
.fillRect( 0, int(0.5*pH
), pW
, int(0.5*pH
), QColor( "#002200" ) );
573 //Add vertical line indicating "now"
574 QTime t
= QTime::currentTime();
575 double x
= 12.0 + t
.hour() + t
.minute()/60.0 + t
.second()/3600.0;
576 while ( x
> 24.0 ) x
-= 24.0;
577 int ix
= int(x
*pW
/24.0); //convert to screen pixel coords
578 p
.setPen( QPen( "white", 2, DotLine
) );
579 p
.drawLine( ix
, 0, ix
, pH
);
583 bitBlt( this, 0, 0, buffer
);
586 #include "altvstime.moc"