/*************************************************************************** observinglist.cpp - Trinity Desktop Planetarium ------------------- begin : 29 Nov 2004 copyright : (C) 2004 by Jeff Woods, Jason Harris email : jcwoods@bellsouth.net, 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 #include #include #include #include "observinglist.h" #include "observinglistui.h" #include "obslistwizard.h" #include "kstars.h" #include "kstarsdata.h" #include "skyobject.h" #include "starobject.h" #include "skymap.h" #include "detaildialog.h" #include "tools/altvstime.h" #include "indimenu.h" #include "indielement.h" #include "indiproperty.h" #include "indidevice.h" #include "devicemanager.h" #include "indistd.h" ObservingList::ObservingList( KStars *_ks, TQWidget* parent ) : KDialogBase( KDialogBase::Plain, i18n( "Observing List" ), Close, Close, parent, "observinglist", false ), ks( _ks ), LogObject(0), oCurrent(0), noNameStars(0), isModified(false), bIsLarge(true) { TQFrame *page = plainPage(); TQVBoxLayout *vlay = new TQVBoxLayout( page, 0, 0 ); ui = new ObservingListUI( page ); vlay->addWidget( ui ); //FIXME: enable MiniButton when I figure out how to resize the window correctly ui->MiniButton->hide(); //Connections connect( this, TQT_SIGNAL( closeClicked() ), this, TQT_SLOT( slotClose() ) ); connect( ui->TableStack, TQT_SIGNAL( aboutToShow( TQWidget* ) ), this, TQT_SLOT( slotPrepTable( TQWidget* ) ) ); connect( ui->FullTable, TQT_SIGNAL( selectionChanged() ), this, TQT_SLOT( slotNewSelection() ) ); connect( ui->TinyTable, TQT_SIGNAL( selectionChanged() ), this, TQT_SLOT( slotNewSelection() ) ); connect( ui->FullTable, TQT_SIGNAL( doubleClicked( TQListViewItem*, const TQPoint&, int) ), this, TQT_SLOT( slotCenterObject() ) ); connect( ui->TinyTable, TQT_SIGNAL( doubleClicked( TQListBoxItem* ) ), this, TQT_SLOT( slotCenterObject() ) ); connect( ui->RemoveButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotRemoveObjects() ) ); connect( ui->CenterButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotCenterObject() ) ); connect( ui->ScopeButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotSlewToObject() ) ); connect( ui->DetailsButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotDetails() ) ); connect( ui->AVTButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotAVT() ) ); connect( ui->OpenButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotOpenList() ) ); connect( ui->SaveButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotSaveList() ) ); connect( ui->SaveAsButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotSaveListAs() ) ); connect( ui->WizardButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotWizard() ) ); //FIXME: enable MiniButton // connect( ui->MiniButton, TQT_SIGNAL( clicked() ), // this, TQT_SLOT( slotToggleSize() ) ); obsList.setAutoDelete( false ); //do NOT delete removed pointers! //Add icons to Push Buttons KIconLoader *icons = KGlobal::iconLoader(); ui->OpenButton->setPixmap( icons->loadIcon( "fileopen", KIcon::Toolbar ) ); ui->SaveButton->setPixmap( icons->loadIcon( "filesave", KIcon::Toolbar ) ); ui->SaveAsButton->setPixmap( icons->loadIcon( "filesaveas", KIcon::Toolbar ) ); ui->WizardButton->setPixmap( icons->loadIcon( "wizard", KIcon::Toolbar ) ); ui->MiniButton->setPixmap( icons->loadIcon( "window_nofullscreen", KIcon::Toolbar ) ); ui->CenterButton->setEnabled( false ); ui->ScopeButton->setEnabled( false ); ui->DetailsButton->setEnabled( false ); ui->AVTButton->setEnabled( false ); ui->RemoveButton->setEnabled( false ); ui->NotesLabel->setEnabled( false ); ui->NotesEdit->setEnabled( false ); } bool ObservingList::contains( const SkyObject *q ) { for ( SkyObject* o = obsList.first(); o; o = obsList.next() ) { if ( o == q ) return true; } return false; } //SLOTS void ObservingList::slotPrepTable( TQWidget *tab ) { if ( tab == ui->FullTable ) { } else { } } void ObservingList::slotAddObject( SkyObject *obj ) { if ( ! obj ) obj = ks->map()->clickedObject(); //First, make sure object is not already in the list for ( SkyObject *o = obsList.first(); o; o = obsList.next() ) { if ( obj == o ) { //FIXME STRINGS FREEZE //ks->statusBar()->changeItem( i18n( "%1 is already in the observing list." ).arg( obj->name() ), 0 ); return; } } //Insert object in obsList obsList.append( obj ); if ( ! isModified ) isModified = true; //Insert object entry in FullTable and TinyTable TQString smag("--"); if ( obj->mag() < 90.0 ) smag = TQString::number( obj->mag(), 'g', 2 ); new KListViewItem( ui->FullTable, obj->translatedName(), obj->ra()->toHMSString(), obj->dec()->toDMSString(), smag, obj->typeName() ); ui->TinyTable->insertItem( obj->translatedName() ); //Note addition in statusbar ks->statusBar()->changeItem( i18n( "Added %1 to observing list." ).arg( obj->name() ), 0 ); } void ObservingList::slotRemoveObject( SkyObject *o ) { if ( !o ) o = ks->map()->clickedObject(); obsList.remove(o); if ( ! isModified ) isModified = true; if ( o == LogObject ) saveCurrentUserLog(); //Remove entry from FullTable bool objectFound = false; TQListViewItemIterator it( ui->FullTable ); while ( it.current() ) { TQListViewItem *item = it.current(); //If the object is named "star" then match coordinates instead of name if ( o->translatedName() == i18n( "star" ) ) { if ( item->text(1) == o->ra()->toHMSString() && item->text(2) == o->dec()->toDMSString() ) { delete item; objectFound = true; break; } } else if ( item->text( 0 ) == o->translatedName() ) { delete item; objectFound = true; break; } ++it; } if ( ! objectFound ) { kdDebug() << i18n( "Cannot remove Object %1; not found in table." ).arg(o->translatedName()) << endl; } else { //Remove object from TinyTable for ( uint i=0; i < ui->TinyTable->count(); i++ ) { if ( o->translatedName() == ui->TinyTable->text(i) ) { ui->TinyTable->removeItem(i); break; } } } } void ObservingList::slotRemoveObjects() { if ( SelectedObjects.count() == 0) return; for ( SkyObject *o = SelectedObjects.first(); o; o = SelectedObjects.next() ) slotRemoveObject( o ); slotNewSelection(); } void ObservingList::slotNewSelection() { //If the TinyTable is visible, we need to sync the selection in the FullTable if ( sender() == ui->TinyTable ) syncTableSelection(); //Construct list of selected objects SelectedObjects.clear(); TQListViewItemIterator it( ui->FullTable, TQListViewItemIterator::Selected ); //loop over selected items while ( it.current() ) { for ( SkyObject *o = obsList.first(); o; o = obsList.next() ) { if ( it.current()->text(0) == i18n("star") ) { if ( it.current()->text(1) == o->ra()->toHMSString() && it.current()->text(2) == o->dec()->toDMSString() ) { SelectedObjects.append(o); break; } } else if ( o->translatedName() == it.current()->text(0) ) { SelectedObjects.append( o ); break; } } it++; } //Enable widgets when one object selected if ( SelectedObjects.count() == 1 ) { TQString newName( SelectedObjects.first()->translatedName() ); TQString oldName( obsList.current()->translatedName() ); //Enable buttons ui->CenterButton->setEnabled( true ); ui->ScopeButton->setEnabled( true ); ui->DetailsButton->setEnabled( true ); ui->AVTButton->setEnabled( true ); ui->RemoveButton->setEnabled( true ); //Find the selected object in the obsList, //then break the loop. Now obsList.current() //points to the new selected object (until now it was the previous object) bool found( false ); for ( SkyObject* o = obsList.first(); o; o = obsList.next() ) { if ( o->translatedName() == newName ) { found = true; break; } } if ( ! found ) { kdDebug() << i18n( "Object %1 not found in obsList." ).arg( newName ) << endl; } else if ( newName != i18n( "star" ) ) { //Display the object's current user notes in the NotesEdit //First, save the last object's user log to disk, if necessary saveCurrentUserLog(); //set LogObject to the new selected object LogObject = obsList.current(); ui->NotesLabel->setEnabled( true ); ui->NotesEdit->setEnabled( true ); ui->NotesLabel->setText( i18n( "observing notes for %1:" ).arg( LogObject->translatedName() ) ); if ( LogObject->userLog.isEmpty() ) { ui->NotesEdit->setText( i18n("Record here observation logs and/or data on %1.").arg( LogObject->translatedName() ) ); } else { ui->NotesEdit->setText( LogObject->userLog ); } } else { //selected object is named "star" //clear the log text box saveCurrentUserLog(); ui->NotesLabel->setEnabled( false ); ui->NotesEdit->setEnabled( false ); } //This shouldn't be necessary. For some reason, obsList.current() //is valid here, but in subsequent functions (such as slotCenterObject) //called *right after* this one, obsList.current()==NULL. No idea why. oCurrent = obsList.current(); } else if ( SelectedObjects.count() == 0 ) { //Disable buttons ui->CenterButton->setEnabled( false ); ui->ScopeButton->setEnabled( false ); ui->DetailsButton->setEnabled( false ); ui->AVTButton->setEnabled( false ); ui->RemoveButton->setEnabled( false ); ui->NotesLabel->setEnabled( false ); ui->NotesEdit->setEnabled( false ); oCurrent = 0; //Clear the user log text box. saveCurrentUserLog(); } else { //more than one object selected. ui->CenterButton->setEnabled( false ); ui->ScopeButton->setEnabled( false ); ui->DetailsButton->setEnabled( false ); ui->AVTButton->setEnabled( true ); ui->RemoveButton->setEnabled( true ); ui->NotesLabel->setEnabled( false ); ui->NotesEdit->setEnabled( false ); oCurrent = 0; //Clear the user log text box. saveCurrentUserLog(); } } void ObservingList::slotCenterObject() { if ( oCurrent ) { ks->map()->setClickedObject( oCurrent ); ks->map()->setClickedPoint( oCurrent ); ks->map()->slotCenter(); } } void ObservingList::slotSlewToObject() { INDI_D *indidev(NULL); INDI_P *prop(NULL), *onset(NULL); INDI_E *RAEle(NULL), *DecEle(NULL), *AzEle(NULL), *AltEle(NULL), *ConnectEle(NULL), *nameEle(NULL); bool useJ2000( false); int selectedCoord(0); SkyPoint sp; // Find the first device with EQUATORIAL_EOD_COORD or EQUATORIAL_COORD and with SLEW element // i.e. the first telescope we find! INDIMenu *imenu = ks->getINDIMenu(); for (unsigned int i=0; i < imenu->mgr.count() ; i++) { for (unsigned int j=0; j < imenu->mgr.at(i)->indi_dev.count(); j++) { indidev = imenu->mgr.at(i)->indi_dev.at(j); indidev->stdDev->currentObject = NULL; prop = indidev->findProp("EQUATORIAL_EOD_COORD"); if (prop == NULL) { prop = indidev->findProp("EQUATORIAL_COORD"); if (prop == NULL) { prop = indidev->findProp("HORIZONTAL_COORD"); if (prop == NULL) continue; else selectedCoord = 1; /* Select horizontal */ } else useJ2000 = true; } ConnectEle = indidev->findElem("CONNECT"); if (!ConnectEle) continue; if (ConnectEle->state == PS_OFF) { KMessageBox::error(0, i18n("Telescope %1 is offline. Please connect and retry again.").arg(indidev->label)); return; } switch (selectedCoord) { // Equatorial case 0: if (prop->perm == PP_RO) continue; RAEle = prop->findElement("RA"); if (!RAEle) continue; DecEle = prop->findElement("DEC"); if (!DecEle) continue; break; //Qt::Horizontal case 1: if (prop->perm == PP_RO) continue; AzEle = prop->findElement("AZ"); if (!AzEle) continue; AltEle = prop->findElement("ALT"); if (!AltEle) continue; break; } onset = indidev->findProp("ON_COORD_SET"); if (!onset) continue; onset->activateSwitch("SLEW"); indidev->stdDev->currentObject = oCurrent; /* Send object name if available */ if (indidev->stdDev->currentObject) { nameEle = indidev->findElem("OBJECT_NAME"); if (nameEle && nameEle->pp->perm != PP_RO) { nameEle->write_w->setText(indidev->stdDev->currentObject->name()); nameEle->pp->newText(); } } switch (selectedCoord) { case 0: if (indidev->stdDev->currentObject) sp.set (indidev->stdDev->currentObject->ra(), indidev->stdDev->currentObject->dec()); else sp.set (ks->map()->clickedPoint()->ra(), ks->map()->clickedPoint()->dec()); if (useJ2000) sp.apparentCoord(ks->data()->ut().djd(), (long double) J2000); RAEle->write_w->setText(TQString("%1:%2:%3").arg(sp.ra()->hour()).arg(sp.ra()->minute()).arg(sp.ra()->second())); DecEle->write_w->setText(TQString("%1:%2:%3").arg(sp.dec()->degree()).arg(sp.dec()->arcmin()).arg(sp.dec()->arcsec())); break; case 1: if (indidev->stdDev->currentObject) { sp.setAz(*indidev->stdDev->currentObject->az()); sp.setAlt(*indidev->stdDev->currentObject->alt()); } else { sp.setAz(*ks->map()->clickedPoint()->az()); sp.setAlt(*ks->map()->clickedPoint()->alt()); } AzEle->write_w->setText(TQString("%1:%2:%3").arg(sp.az()->degree()).arg(sp.az()->arcmin()).arg(sp.az()->arcsec())); AltEle->write_w->setText(TQString("%1:%2:%3").arg(sp.alt()->degree()).arg(sp.alt()->arcmin()).arg(sp.alt()->arcsec())); break; } prop->newText(); return; } } // We didn't find any telescopes KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); } //FIXME: This will open multiple Detail windows for each object; //Should have one window whose target object changes with selection void ObservingList::slotDetails() { if ( oCurrent ) { DetailDialog dd( oCurrent, ks->data()->lt(), ks->geo(), ks ); dd.exec(); } } void ObservingList::slotAVT() { if ( SelectedObjects.count() ) { AltVsTime avt( ks ); for ( SkyObject *o = SelectedObjects.first(); o; o = SelectedObjects.next() ) { avt.processObject( o ); } avt.exec(); } } //FIXME: On close, we will need to close any open Details/AVT windows void ObservingList::slotClose() { //Save the current User log text if ( oCurrent && ! ui->NotesEdit->text().isEmpty() && ui->NotesEdit->text() != i18n("Record here observation logs and/or data on %1.").arg( oCurrent->name()) ) { oCurrent->saveUserLog( ui->NotesEdit->text() ); } hide(); } void ObservingList::saveCurrentUserLog() { if ( ! ui->NotesEdit->text().isEmpty() && ui->NotesEdit->text() != i18n("Record here observation logs and/or data on %1.").arg( LogObject->translatedName() ) ) { LogObject->saveUserLog( ui->NotesEdit->text() ); ui->NotesEdit->clear(); ui->NotesLabel->setText( i18n( "Observing notes for object:" ) ); LogObject = NULL; } } void ObservingList::slotOpenList() { KURL fileURL = KFileDialog::getOpenURL( TQDir::homeDirPath(), "*.obslist|KStars Observing List (*.obslist)" ); TQFile f; if ( fileURL.isValid() ) { if ( ! fileURL.isLocalFile() ) { //Save remote list to a temporary local file KTempFile tmpfile; tmpfile.setAutoDelete(true); FileName = tmpfile.name(); if( KIO::NetAccess::download( fileURL, FileName, this ) ) f.setName( FileName ); } else { FileName = fileURL.path(); f.setName( FileName ); } if ( !f.open( IO_ReadOnly) ) { TQString message = i18n( "Could not open file %1" ).arg( f.name() ); KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) ); return; } saveCurrentList(); //First line is the name of the list. The rest of the file should //be object names, one per line. TQTextStream istream(&f); TQString line; ListName = istream.readLine(); while ( ! istream.eof() ) { line = istream.readLine(); //DEBUG kdDebug() << line << endl; //If the object is named "star", add it by coordinates SkyObject *o = 0; if ( line.startsWith( "star" ) ) { TQStringList fields = TQStringList::split( " ", line ); //DEBUG kdDebug() << fields << endl; double ra = dms::fromString( fields[1], false ).Degrees(); //false = hours double dc = dms::fromString( fields[2], true ).Degrees(); //true = degrees //Identify the star with these coordinates double rmax = 1.; for ( uint i=0; i < ks->data()->starList.count(); ++i ) { SkyObject *s = (SkyObject*)(ks->data()->starList.at(i)); double dra = fabs( ra - s->ra()->Degrees() ); double ddc = fabs( dc - s->dec()->Degrees() ); if ( dra < rmax && ddc < rmax ) { o = s; rmax = sqrt( dra*dra + ddc*ddc ); } } } else { o = ks->data()->objectNamed( line ); } if ( o ) slotAddObject( o ); } //Newly-opened list should not trigger isModified flag isModified = false; f.close(); } else if ( fileURL.path() != "" ) { TQString message = i18n( "The specified file is invalid. Try another file?" ); if ( KMessageBox::warningYesNo( this, message, i18n("Invalid File"), i18n("Try Another"), i18n("Do Not Try") ) == KMessageBox::Yes ) { slotOpenList(); } } } void ObservingList::saveCurrentList() { //Before loading a new list, do we need to save the current one? //Assume that if the list is empty, then there's no need to save if ( obsList.count() ) { if ( isModified ) { TQString message = i18n( "Do you want to save the current list before opening a new list?" ); if ( KMessageBox::questionYesNo( this, message, i18n( "Save Current List?" ), KStdGuiItem::save(), KStdGuiItem::discard() ) == KMessageBox::Yes ) slotSaveList(); } //If we ever allow merging the loaded list with //the existing one, that code would go here obsList.clear(); ui->FullTable->clear(); } } void ObservingList::slotSaveListAs() { bool ok(false); ListName = KInputDialog::getText( i18n( "Enter List Name" ), i18n( "List name:" ), "", &ok ); if ( ok ) { KURL fileURL = KFileDialog::getSaveURL( TQDir::homeDirPath(), "*.obslist|KStars Observing List (*.obslist)" ); if ( fileURL.isValid() ) FileName = fileURL.path(); slotSaveList(); } } void ObservingList::slotSaveList() { if ( ListName.isEmpty() || FileName.isEmpty() ) { slotSaveListAs(); return; } TQFile f( FileName ); if ( !f.open( IO_WriteOnly) ) { TQString message = i18n( "Could not open file %1. Try a different filename?" ).arg( f.name() ); if ( KMessageBox::warningYesNo( 0, message, i18n( "Could Not Open File" ), i18n("Try Different"), i18n("Do Not Try") ) == KMessageBox::Yes ) { FileName == ""; slotSaveList(); } return; } TQTextStream ostream(&f); ostream << ListName << endl; //Save objects to the list using their name. If it's a star with a genetive name //(i.e., "sigma orionis"), save the name with ascii characters, not greek letters. //If it's an unnamed star, save "star" and the star's coordinates. for ( SkyObject* o = obsList.first(); o; o = obsList.next() ) { if ( o->name() == "star" ) { ostream << o->name() << " " << o->ra()->Hours() << " " << o->dec()->Degrees() << endl; } else if ( o->type() == SkyObject::STAR ) { StarObject *s = (StarObject*)o; if ( s->name() == s->gname() ) { ostream << s->gname( false ) << endl; } else { ostream << o->name() << endl; } } else { ostream << o->name() << endl; } } f.close(); isModified = false; } void ObservingList::slotWizard() { ObsListWizard wizard( ks ); if ( wizard.exec() == TQDialog::Accepted ) { //Make sure current list is saved saveCurrentList(); for ( SkyObject *o = wizard.obsList().first(); o; o = wizard.obsList().next() ) { slotAddObject( o ); } } } void ObservingList::slotToggleSize() { if ( isLarge() ) { ui->MiniButton->setPixmap( KGlobal::iconLoader()->loadIcon( "window_fullscreen", KIcon::Toolbar ) ); //switch widget stack to show TinyTable ui->TableStack->raiseWidget( ui->TinyTable ); //Abbreviate text on each button ui->CenterButton->setText( i18n( "First letter in 'Center'", "C" ) ); ui->ScopeButton->setText( i18n( "First letter in 'Scope'", "S" ) ); ui->DetailsButton->setText( i18n( "First letter in 'Details'", "D" ) ); ui->AVTButton->setText( i18n( "First letter in 'Alt vs Time'", "A" ) ); ui->RemoveButton->setText( i18n( "First letter in 'Remove'", "R" ) ); //Hide Observing notes ui->NotesLabel->hide(); ui->NotesEdit->hide(); syncTableSelection( false ); //sync TinyTable with FullTable adjustSize(); bIsLarge = false; } else { ui->MiniButton->setPixmap( KGlobal::iconLoader()->loadIcon( "window_nofullscreen", KIcon::Toolbar ) ); //switch widget stack to show FullTable ui->TableStack->raiseWidget( ui->FullTable ); //Expand text on each button ui->CenterButton->setText( i18n( "Center" ) ); ui->ScopeButton->setText( i18n( "Scope" ) ); ui->DetailsButton->setText( i18n( "Details" ) ); ui->AVTButton->setText( i18n( "Alt vs Time" ) ); ui->RemoveButton->setText( i18n( "Remove" ) ); //Show Observing notes ui->NotesLabel->show(); ui->NotesEdit->show(); syncTableSelection( true ); //sync FullTable with TinyTable adjustSize(); bIsLarge = true; } } void ObservingList::syncTableSelection( bool syncFullTable ) { if ( syncFullTable ) { int i=0; TQListViewItem *it = ui->FullTable->firstChild(); while ( it ) { it->setSelected( ui->TinyTable->isSelected( i++ ) ); it->nextSibling(); } } else { int i=0; TQListViewItem *it = ui->FullTable->firstChild(); while ( it ) { ui->TinyTable->setSelected( i++, it->isSelected() ); it->nextSibling(); } } } #include "observinglist.moc"