/***************************************************************************
                          detaildialog.cpp  -  description
                             -------------------
    begin                : Sun May 5 2002
    copyright            : (C) 2002 by Jason Harris and Jasem Mutlaq
    email                : kstars@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 "detaildialog.h"

#include <QLineEdit>
#include <QPixmap>
#include <QRegExp>
#include <QTextStream>
#include <QHBoxLayout>
#include <QLabel>
#include <QTreeWidgetItem>
#include <QPushButton>
#include <QTemporaryFile>
#include <QDebug>
#include <QDesktopServices>

#include <KMessageBox>
#include <KToolInvocation>
#include <KLocalizedString>
#include <QStandardPaths>

#include "kstars.h"
#include "kstarsdata.h"
#include "kstarsdatetime.h"
#include "ksnumbers.h"
#include "geolocation.h"
#include "ksutils.h"
#include "skymap.h"
#include "skyobjects/skyobject.h"
#include "skyobjects/starobject.h"
#include "skyobjects/deepskyobject.h"
#include "skyobjects/ksplanetbase.h"
#include "skyobjects/ksmoon.h"
#include "skyobjects/kscomet.h"
#include "skyobjects/ksasteroid.h"
#include "skyobjects/supernova.h"
#include "skycomponents/catalogcomponent.h"
#include "thumbnailpicker.h"
#include "Options.h"
#include "widgets/kshelplabel.h"
#include "addlinkdialog.h"
#include "observinglist.h"

#include <config-kstars.h>

#ifdef HAVE_INDI
#include <basedevice.h>
#include "indi/indilistener.h"
#include "indi/indistd.h"
#include "indi/driverinfo.h"
#endif

#include "skycomponents/constellationboundarylines.h"
#include "skycomponents/skymapcomposite.h"
#include "kspaths.h"

DetailDialog::DetailDialog(SkyObject *o, const KStarsDateTime &ut, GeoLocation *geo, QWidget *parent ) :
    KPageDialog( parent ),
    selectedObject(o),
    Data(0), DataComet(0), Pos(0), Links(0), Adv(0), Log(0)
{
#ifdef Q_OS_OSX
        setWindowFlags(Qt::Tool| Qt::WindowStaysOnTopHint);
#endif
    setFaceType( Tabbed );
    setBackgroundRole( QPalette::Base );

    titlePalette = palette();
    titlePalette.setColor( backgroundRole(), palette().color( QPalette::Active, QPalette::Highlight ) );
    titlePalette.setColor( foregroundRole(), palette().color( QPalette::Active, QPalette::HighlightedText ) );

    //Create thumbnail image
    Thumbnail = new QPixmap( 200, 200 );

    setWindowTitle( i18n( "Object Details" ) );

    // JM 2016-11-22: Do we really need a close button?
    //setStandardButtons(QDialogButtonBox::Close);
    setStandardButtons(QDialogButtonBox::NoButton);

    createGeneralTab();
    createPositionTab( ut, geo );
    createLinksTab();
    createAdvancedTab();
    createLogTab();
}

DetailDialog::~DetailDialog() {
    delete Thumbnail;
}

void DetailDialog::createGeneralTab()
{
    Data = new DataWidget(this);
    addPage(Data, i18n("General"));

    Data->Names->setPalette(titlePalette);

    //Connections
    connect(Data->ObsListButton, SIGNAL(clicked()), this, SLOT(addToObservingList()));
    connect(Data->CenterButton, SIGNAL(clicked()), this, SLOT(centerMap()));
    #ifdef HAVE_INDI
    connect(Data->ScopeButton, SIGNAL(clicked()), this, SLOT(centerTelescope()));
    #else
    Data->ScopeButton->setEnabled(false);
    #endif
    connect(Data->Image, SIGNAL(clicked()), this, SLOT(updateThumbnail()));

    // Stuff that should be visible only for specific types of objects
    Data->IllumLabel->setVisible(false); // Only shown for the moon
    Data->Illumination->setVisible(false);

    Data->BVIndex->setVisible(false); // Only shown for stars
    Data->BVLabel->setVisible(false);

    //Show object thumbnail image
    showThumbnail();

    //Fill in the data fields
    //Contents depend on type of object
    QString objecttyp, str;

    switch (selectedObject->type()) {
        case SkyObject::STAR: {
            StarObject* s = (StarObject*) selectedObject;

            if (s->getHDIndex()) {
                Data->Names->setText(QString("%1, HD %2").arg(s->longname()).arg(s->getHDIndex()));
            } else {
                Data->Names->setText(s->longname());
            }

            objecttyp = s->sptype() + ' ' + i18n("star");
            Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag",
                                           QLocale().toString(s->mag(), 'f', 2)));  //show to hundredth place

            Data->BVLabel->setVisible(true);
            Data->BVIndex->setVisible(true);
            if(s->getBVIndex() < 30.) {
                Data->BVIndex->setText(QString::number(s->getBVIndex() , 'f', 2));
            }

            //The thumbnail image is empty, and isn't clickable for stars
            //Also, don't show the border around the Image QFrame.
            Data->Image->setFrameStyle(QFrame::NoFrame);
            disconnect(Data->Image, SIGNAL(clicked()), this, SLOT(updateThumbnail()));

            //distance
            if (s->distance() > 2000. || s->distance() < 0.) { // parallax < 0.5 mas
                Data->Distance->setText(QString(i18nc("larger than 2000 parsecs", "> 2000 pc")));
            } else if (s->distance() > 50.) { //show to nearest integer
                Data->Distance->setText(i18nc("number in parsecs", "%1 pc",
                                              QLocale().toString( s->distance(), 'f', 0)));
            } else if (s->distance() > 10.0) { //show to tenths place
                Data->Distance->setText(i18nc("number in parsecs", "%1 pc",
                                              QLocale().toString( s->distance(), 'f', 1)));
            } else { //show to hundredths place
                Data->Distance->setText(i18nc("number in parsecs", "%1 pc",
                                              QLocale().toString(s->distance(), 'f', 2 )));
            }

            //Note multiplicity/variablility in angular size label
            Data->AngSizeLabel->setText(QString());
            Data->AngSize->setText(QString());
            Data->AngSizeLabel->setFont(Data->AngSize->font());
            if (s->isMultiple() && s->isVariable()) {
                Data->AngSizeLabel->setText(i18nc( "the star is a multiple star", "multiple" ) + ',');
                Data->AngSize->setText(i18nc( "the star is a variable star", "variable" ));
            } else if (s->isMultiple()) {
                Data->AngSizeLabel->setText(i18nc("the star is a multiple star", "multiple"));
            } else if (s->isVariable()) {
                Data->AngSizeLabel->setText(i18nc("the star is a variable star", "variable"));
            }

            break; //end of stars case
        }
        case SkyObject::ASTEROID:  //[fall through to planets]
        case SkyObject::COMET:     //[fall through to planets]
        case SkyObject::MOON:      //[fall through to planets]
        case SkyObject::PLANET: {
            KSPlanetBase* ps = (KSPlanetBase*) selectedObject;

            Data->Names->setText(ps->longname());

            //Type is "G5 star" for Sun
            if (ps->name() == "Sun") {
                objecttyp = i18n("G5 star");
            } else if (ps->name() == "Moon") {
                objecttyp = ps->translatedName();
            } else if (ps->name() == i18n("Pluto") || ps->name() == "Ceres" || ps->name() == "Eris") { // TODO: Check if Ceres / Eris have translations and i18n() them
                objecttyp = i18n("Dwarf planet");
            } else {
                objecttyp = ps->typeName();
            }

            //The moon displays illumination fraction and updateMag is called to calculate moon's current magnitude
            if (selectedObject->name() == "Moon") {
                Data->IllumLabel->setVisible(true);
                Data->Illumination->setVisible(true);
                Data->Illumination->setText(QString("%1 %").arg(QLocale().toString(((KSMoon*)selectedObject)->illum()*100., 'f', 0)));
                ((KSMoon *)selectedObject)->updateMag();
            }

            // JM: Shouldn't we use the calculated magnitude? Disabling the following
            /*
            if(selectedObject->type() == SkyObject::COMET){
                Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag",
                                         QLocale().toString( ((KSComet *)selectedObject)->getTotalMagnitudeParameter(), 'f', 2)));  //show to hundredth place

            }
            else{*/
                Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag",
                                               QLocale().toString(ps->mag(), 'f', 2)));  //show to hundredth place
            //}

            //Distance from Earth.  The moon requires a unit conversion
            if (ps->name() == "Moon") {
                Data->Distance->setText(i18nc("distance in kilometers", "%1 km",
                                              QLocale().toString(ps->rearth()*AU_KM, 'f', 2)));
            } else {
                Data->Distance->setText(i18nc("distance in Astronomical Units", "%1 AU",
                                              QLocale().toString( ps->rearth(), 'f', 3)));
            }

            //Angular size; moon and sun in arcmin, others in arcsec
            if (ps->angSize()) {
                if (ps->name() == "Sun" || ps->name() == "Moon") {
                    Data->AngSize->setText(i18nc("angular size in arcminutes", "%1 arcmin",
                                                 QLocale().toString(ps->angSize(), 'f', 1))); // Needn't be a plural form because sun / moon will never contract to 1 arcminute
                } else {
                    Data->AngSize->setText(i18nc("angular size in arcseconds","%1 arcsec",
                                                 QLocale().toString( ps->angSize()*60.0 , 'f', 1)));
                }
            } else {
                Data->AngSize->setText( "--" );
            }

            break; //end of planets/comets/asteroids case
        }
        case SkyObject::SUPERNOVA: {
            Supernova* sup=(Supernova*) selectedObject;

            objecttyp = i18n("Supernova");
            Data->Names->setText(sup->name());
            if (sup->mag() < 99)
                Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag", QLocale().toString(sup->mag(), 'f', 2)));
            else
                Data->Magnitude->setText("--");

            Data->DistanceLabel->setVisible(false);
            Data->Distance->setVisible(false);

            Data->AngSizeLabel->setVisible(false);
            Data->AngSize->setVisible(false);

            QLabel *discoveryDateLabel = new QLabel(i18n("Discovery Date:"), this);
            QLabel *discoveryDate      = new QLabel(sup->getDate(), this);
            Data->dataGridLayout->addWidget(discoveryDateLabel, 1, 0);
            Data->dataGridLayout->addWidget(discoveryDate, 1, 1);

            QLabel *typeLabel          = new QLabel(i18n("Type:"), this);
            QLabel *type               = new QLabel(sup->getType(), this);
            Data->dataGridLayout->addWidget(typeLabel, 2, 0);
            Data->dataGridLayout->addWidget(type, 2, 1);

            QLabel *hostGalaxyLabel    = new QLabel(i18n("Host Galaxy:"), this);
            QLabel *hostGalaxy         = new QLabel(sup->getHostGalaxy().isEmpty() ? "--" : sup->getHostGalaxy(), this);
            Data->dataGridLayout->addWidget(hostGalaxyLabel, 3, 0);
            Data->dataGridLayout->addWidget(hostGalaxy, 3, 1);

            QLabel *redShiftLabel    = new QLabel(i18n("Red Shift:"), this);
            QLabel *redShift         = new QLabel( (sup->getRedShift() < 99) ? QString::number(sup->getRedShift(), 'f', 2) : QString("--"), this);
            Data->dataGridLayout->addWidget(redShiftLabel, 4, 0);
            Data->dataGridLayout->addWidget(redShift, 4, 1);

            break;
        }
        default: { //deep-sky objects
            DeepSkyObject* dso = (DeepSkyObject*) selectedObject;

            //Show all names recorded for the object
            QStringList nameList;
            if (!dso->longname().isEmpty() && dso->longname() != dso->name()) {
                nameList.append(dso->translatedLongName());
                nameList.append(dso->translatedName());
            } else {
                nameList.append(dso->translatedName());
            }

            if (!dso->translatedName2().isEmpty()) {
                nameList.append(dso->translatedName2());
            }

            if (dso->ugc() != 0) {
                nameList.append(QString("UGC %1").arg(dso->ugc()));
            }

            if (dso->pgc() != 0) {
                nameList.append(QString("PGC %1").arg(dso->pgc()));
            }

            Data->Names->setText(nameList.join(","));

            objecttyp = dso->typeName();

            if (dso->type() == SkyObject::RADIO_SOURCE)
            {
                Q_ASSERT( dso->customCatalog() ); // the in-built catalogs don't have radio sources
                Data->MagLabel->setText(i18nc("integrated flux at a frequency", "Flux(%1):", dso->customCatalog()->fluxFrequency()));
                Data->Magnitude->setText(i18nc("integrated flux value", "%1 %2",
                                               QLocale().toString(dso->flux(), 'f', 1), dso->customCatalog()->fluxUnit()));  //show to tenths place
            } else if (dso->mag() > 90.0) {
                Data->Magnitude->setText("--");
            } else {
                Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag",
                                               QLocale().toString( dso->mag(), 'f', 1)));  //show to tenths place
            }

            //No distances at this point...
            Data->Distance->setText("--");

            //Only show decimal place for small angular sizes
            if (dso->a() > 10.0) {
                Data->AngSize->setText(i18nc("angular size in arcminutes", "%1 arcmin",
                                             QLocale().toString(dso->a(), 'f', 0)));
            } else if (dso->a()) {
                Data->AngSize->setText(i18nc("angular size in arcminutes", "%1 arcmin",
                                          QLocale().toString( dso->a(), 'f', 1 )));
            } else {
                Data->AngSize->setText("--");
            }

            break;
        }
    }

    // Add specifics data
    switch (selectedObject->type()) {
        case SkyObject::ASTEROID: {
            KSAsteroid* ast = (KSAsteroid *)selectedObject;
            DataComet = new DataCometWidget( this );    // Show same specifics data as comets
            Data->IncludeData->layout()->addWidget( DataComet );

            // Perihelion
            str.setNum( ast->getPerihelion() );
            DataComet->Perihelion->setText( str + " AU" );
            // Earth MOID
            if ( ast->getEarthMOID() == 0 )
                str = "";
            else
                str.setNum( ast->getEarthMOID() ).append( " AU" );
            DataComet->EarthMOID->setText( str );
            // Orbit ID
            DataComet->OrbitID->setText( ast->getOrbitID() );
            // Orbit Class
            DataComet->OrbitClass->setText( ast->getOrbitClass() );
            // NEO
            if ( ast->isNEO() )
                DataComet->NEO->setText( "Yes" );
            else
                DataComet->NEO->setText( "No" );
            // Albedo
            if ( ast->getAlbedo() == 0.0 )
                str = "";
            else
                str.setNum( ast->getAlbedo() );
            DataComet->Albedo->setText( str );
            // Diameter
            if( ast->getDiameter() == 0.0 )
                str = "";
            else
                str.setNum( ast->getDiameter() ).append( " km" );
            DataComet->Diameter->setText( str );
            // Dimensions
            if ( ast->getDimensions().isEmpty() )
                DataComet->Dimensions->setText( "" );
            else
                DataComet->Dimensions->setText( ast->getDimensions() + " km" );
            // Rotation period
            if ( ast->getRotationPeriod() == 0.0 )
                str = "";
            else
                str.setNum( ast->getRotationPeriod() ).append( " h" );
            DataComet->Rotation->setText( str );
            // Period
            if ( ast->getPeriod() == 0.0 )
                str = "";
            else
                str.setNum( ast->getPeriod() ).append( " y" );
            DataComet->Period->setText( str );

            break;
        }
        case SkyObject::COMET: {
            KSComet* com = (KSComet *)selectedObject;
            DataComet = new DataCometWidget( this );
            Data->IncludeData->layout()->addWidget( DataComet );

            // Perihelion
            str.setNum( com->getPerihelion() );
            DataComet->Perihelion->setText( str + " AU" );
            // Earth MOID
            if ( com->getEarthMOID() == 0 )
                str = "";
            else
                str.setNum( com->getEarthMOID() ).append( " AU" );
            DataComet->EarthMOID->setText( str );
            // Orbit ID
            DataComet->OrbitID->setText( com->getOrbitID() );
            // Orbit Class
            DataComet->OrbitClass->setText( com->getOrbitClass() );
            // NEO
            if ( com->isNEO() )
                DataComet->NEO->setText( "Yes" );
            else
                DataComet->NEO->setText( "No" );
            // Albedo
            if ( com->getAlbedo() == 0.0 )
                str = "";
            else
                str.setNum( com->getAlbedo() );
            DataComet->Albedo->setText( str );
            // Diameter
            if( com->getDiameter() == 0.0 )
                str = "";
            else
                str.setNum( com->getDiameter() ).append( " km" );
            DataComet->Diameter->setText( str );
            // Dimensions
            if ( com->getDimensions().isEmpty() )
                DataComet->Dimensions->setText( "" );
            else
                DataComet->Dimensions->setText( com->getDimensions() + " km" );
            // Rotation period
            if ( com->getRotationPeriod() == 0.0 )
                str = "";
            else
                str.setNum( com->getRotationPeriod() ).append( " h" );
            DataComet->Rotation->setText( str );
            // Period
            if ( com->getPeriod() == 0.0 )
                str = "";
            else
                str.setNum( com->getPeriod() ).append( " y" );
            DataComet->Period->setText( str );

            break;
        }
    }

    //Common to all types:
    QString cname = KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(selectedObject);
    if (selectedObject->type() != SkyObject::CONSTELLATION) {
        cname = i18nc("%1 type of sky object (planet, asteroid etc), %2 name of a constellation", "%1 in %2", objecttyp, cname);
    }
    Data->ObjectTypeInConstellation->setText(cname);
}

void DetailDialog::createPositionTab( const KStarsDateTime &ut, GeoLocation *geo ) {
    Pos = new PositionWidget(this);
    addPage( Pos,  i18n("Position") );

    Pos->CoordTitle->setPalette( titlePalette );
    Pos->RSTTitle->setPalette( titlePalette );
    KStarsData *data = KStarsData::Instance();

    //Coordinates Section:
    //Don't use KLocale::formatNumber() for the epoch string,
    //because we don't want a thousands-place separator!
    selectedObject->updateCoords( data->updateNum(), true, data->geo()->lat(), data->lst(), false );
    QString sEpoch = QString::number( KStarsDateTime::jdToEpoch( selectedObject->getLastPrecessJD() ), 'f', 1 );
    //Replace the decimal point with localized decimal symbol
    sEpoch.replace( '.', QLocale().decimalPoint() ); // Is this necessary? -- asimha Oct 2016

    qDebug() << (selectedObject->deprecess(data->updateNum())).ra0().toHMSString() << (selectedObject->deprecess(data->updateNum())).dec0().toDMSString() << endl;
    //qDebug() << selectedObject->ra().toHMSString() << selectedObject->dec().toDMSString() << endl;
    Pos->RALabel->setText( i18n( "RA (%1):", sEpoch ) );
    Pos->DecLabel->setText( i18n( "DE (%1):", sEpoch ) );
    Pos->RA->setText( selectedObject->ra().toHMSString() );
    Pos->Dec->setText( selectedObject->dec().toDMSString() );

    selectedObject->EquatorialToHorizontal(data->lst(), data->geo()->lat());

    Pos->Az->setText( selectedObject->az().toDMSString() );
    dms a;

    if( Options::useAltAz() )
        a = selectedObject->alt();
    else
        a = selectedObject->altRefracted();
    Pos->Alt->setText( a.toDMSString() );

    // Display the RA0 and Dec0 for objects that are outside the solar system
    if( !selectedObject->isSolarSystem() ) {
        Pos->RA0->setText( selectedObject->ra0().toHMSString() );
        Pos->Dec0->setText( selectedObject->dec0().toDMSString() );
    }
    else {
        Pos->RA0->setText( "--" );
        Pos->Dec0->setText( "--" );
    }

    //Hour Angle can be negative, but dms HMS expressions cannot.
    //Here's a kludgy workaround:
    dms lst = geo->GSTtoLST( ut.gst() );
    dms ha( lst.Degrees() - selectedObject->ra().Degrees() );
    QChar sgn('+');
    if ( ha.Hours() > 12.0 ) {
        ha.setH( 24.0 - ha.Hours() );
        sgn = '-';
    }
    Pos->HA->setText( QString("%1%2").arg(sgn).arg( ha.toHMSString() ) );

    //Airmass is approximated as the secant of the zenith distance,
    //equivalent to 1./sin(Alt).  Beware of Inf at Alt=0!
    if ( selectedObject->alt().Degrees() > 0.0 )
        Pos->Airmass->setText( QLocale().toString(
                                                  selectedObject->airmass() , 'f', 2 ) );
    else
        Pos->Airmass->setText( "--" );

    //Rise/Set/Transit Section:

    //Prepare time/position variables
    QTime rt = selectedObject->riseSetTime( ut, geo, true ); //true = use rise time
    dms raz = selectedObject->riseSetTimeAz( ut, geo, true ); //true = use rise time

    //If transit time is before rise time, use transit time for tomorrow
    QTime tt = selectedObject->transitTime( ut, geo );
    dms talt = selectedObject->transitAltitude( ut, geo );
    if ( tt < rt ) {
        tt = selectedObject->transitTime( ut.addDays( 1 ), geo );
        talt = selectedObject->transitAltitude( ut.addDays( 1 ), geo );
    }

    //If set time is before rise time, use set time for tomorrow
    QTime st = selectedObject->riseSetTime(  ut, geo, false ); //false = use set time
    dms saz = selectedObject->riseSetTimeAz( ut, geo, false ); //false = use set time
    if ( st < rt ) {
        st = selectedObject->riseSetTime( ut.addDays( 1 ), geo, false ); //false = use set time
        saz = selectedObject->riseSetTimeAz( ut.addDays( 1 ), geo, false ); //false = use set time
    }

    if ( rt.isValid() ) {
        Pos->TimeRise->setText( QString().sprintf( "%02d:%02d", rt.hour(), rt.minute() ) );
        Pos->TimeSet->setText( QString().sprintf( "%02d:%02d", st.hour(), st.minute() ) );
        Pos->AzRise->setText( raz.toDMSString() );
        Pos->AzSet->setText( saz.toDMSString() );
    } else {
        if ( selectedObject->alt().Degrees() > 0.0 ) {
            Pos->TimeRise->setText( i18n( "Circumpolar" ) );
            Pos->TimeSet->setText( i18n( "Circumpolar" ) );
        } else {
            Pos->TimeRise->setText( i18n( "Never rises" ) );
            Pos->TimeSet->setText( i18n( "Never rises" ) );
        }

        Pos->AzRise->setText( i18nc( "Not Applicable", "N/A" ) );
        Pos->AzSet->setText( i18nc( "Not Applicable", "N/A" ) );
    }

    Pos->TimeTransit->setText( QString().sprintf( "%02d:%02d", tt.hour(), tt.minute() ) );
    Pos->AltTransit->setText( talt.toDMSString() );

    // Restore the position and other time-dependent parameters
    selectedObject->recomputeCoords( ut, geo );

}

void DetailDialog::createLinksTab()
{
    // don't create a link tab for an unnamed star
    if (selectedObject->name() == QString("star"))
        return;

    Links = new LinksWidget( this );
    addPage( Links, i18n( "Links" ) );

    Links->InfoTitle->setPalette( titlePalette );
    Links->ImagesTitle->setPalette( titlePalette );

    foreach ( const QString &s, selectedObject->InfoTitle() )
        Links->InfoTitleList->addItem( i18nc( "Image/info menu item (should be translated)", s.toLocal8Bit() ) );

    //Links->InfoTitleList->setCurrentRow(0);

    foreach ( const QString &s, selectedObject->ImageTitle() )
        Links->ImageTitleList->addItem( i18nc( "Image/info menu item (should be translated)", s.toLocal8Bit() ) );

    // Signals/Slots
    connect( Links->ViewButton, SIGNAL(clicked()), this, SLOT( viewLink() ) );
    connect( Links->AddLinkButton, SIGNAL(clicked()), this, SLOT( addLink() ) );
    connect( Links->EditLinkButton, SIGNAL(clicked()), this, SLOT( editLinkDialog() ) );
    connect( Links->RemoveLinkButton, SIGNAL(clicked()), this, SLOT( removeLinkDialog() ) );

    // When an item is selected in info list, selected items are cleared image list.
    connect( Links->InfoTitleList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT( setCurrentLink(QListWidgetItem*) ) );
    connect( Links->InfoTitleList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), Links->ImageTitleList, SLOT( clearSelection() ) );

    // vice versa
    connect( Links->ImageTitleList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT( setCurrentLink(QListWidgetItem*) ) );
    connect( Links->ImageTitleList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), Links->InfoTitleList, SLOT( clearSelection() ));

    connect( Links->InfoTitleList, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT( viewLink() ) );
    connect( Links->ImageTitleList, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT( viewLink() ) );

    connect( Links->InfoTitleList, SIGNAL(itemSelectionChanged ()), this, SLOT( updateButtons() ) );
    connect( Links->ImageTitleList, SIGNAL(itemSelectionChanged ()), this, SLOT( updateButtons() ));

    updateLists();
}

void DetailDialog::addLink() {
    if( !selectedObject )
        return;
    QPointer<AddLinkDialog> adialog = new AddLinkDialog( this, selectedObject->name() );
    QString entry;
    QFile file;

    if ( adialog->exec()==QDialog::Accepted ) {
        if ( adialog->isImageLink() ) {
            //Add link to object's ImageList, and descriptive text to its ImageTitle list
            selectedObject->ImageList().append( adialog->url() );
            selectedObject->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.setFileName( KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "image_url.dat" ) ; //determine filename in local user KDE directory tree.

            if ( !file.open( QIODevice::ReadWrite | QIODevice::Append ) ) {
                QString message = i18n( "Custom image-links file could not be opened.\nLink cannot be recorded for future sessions." );
                KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
        delete adialog;
                return;
            } else {
                entry = selectedObject->name() + ':' + adialog->desc() + ':' + adialog->url();
                QTextStream stream( &file );
                stream << entry << endl;
                file.close();
                emit updateLists();
            }
        } else {
            selectedObject->InfoList().append( adialog->url() );
            selectedObject->InfoTitle().append( adialog->desc() );

            //check for user's image-links database.  If it doesn't exist, create it.
            file.setFileName( KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "info_url.dat" ) ; //determine filename in local user KDE directory tree.

            if ( !file.open( QIODevice::ReadWrite | QIODevice::Append ) ) {
                QString message = i18n( "Custom information-links file could not be opened.\nLink cannot be recorded for future sessions." );
                KMessageBox::sorry( 0, message, i18n( "Could not Open File" ) );
        delete adialog;
                return;
            } else {
                entry = selectedObject->name() + ':' + adialog->desc() + ':' + adialog->url();
                QTextStream stream( &file );
                stream << entry << endl;
                file.close();
                emit updateLists();
            }
        }
    }
    delete adialog;
}

void DetailDialog::createAdvancedTab()
{
    // Don't create an adv tab for an unnamed star or if advinterface file failed loading
    // We also don't need adv dialog for solar system objects.
    if (selectedObject->name() == QString("star") ||
            KStarsData::Instance()->avdTree().isEmpty() ||
            selectedObject->type() == SkyObject::PLANET ||
            selectedObject->type() == SkyObject::COMET ||
            selectedObject->type() == SkyObject::ASTEROID )
        return;

    Adv = new DatabaseWidget( this );
    addPage( Adv,  i18n( "Advanced" ) );

    connect( Adv->ADVTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(viewADVData()));

    populateADVTree();
}

void DetailDialog::createLogTab()
{
    //Don't create a log tab for an unnamed star
    if ( selectedObject->name() == QString("star") )
        return;

    // Log Tab
    Log = new LogWidget( this );
    addPage( Log,  i18n( "Log" ) );

    Log->LogTitle->setPalette( titlePalette );

    if ( selectedObject->userLog().isEmpty() )
        Log->UserLog->setText(i18n("Record here observation logs and/or data on %1.", selectedObject->translatedName()));
    else
        Log->UserLog->setText(selectedObject->userLog());

    //Automatically save the log contents when the widget loses focus
    connect( Log->UserLog, SIGNAL( focusOut() ), this, SLOT( saveLogData() ) );
}


void DetailDialog::setCurrentLink(QListWidgetItem *it)
{
    m_CurrentLink = it;
}

void DetailDialog::viewLink()
{
    QString URL;

    if (m_CurrentLink == NULL) return;

    if ( m_CurrentLink->listWidget() == Links->InfoTitleList ) {
        URL = QString( selectedObject->InfoList().at( Links->InfoTitleList->row(m_CurrentLink) ) );
    }
    else if ( m_CurrentLink->listWidget() == Links->ImageTitleList ) {
        URL = QString( selectedObject->ImageList().at( Links->ImageTitleList->row(m_CurrentLink) ) );
    }

    if ( !URL.isEmpty() )
        QDesktopServices::openUrl(QUrl(URL));
}

void DetailDialog::updateLists()
{
    Links->InfoTitleList->clear();
    Links->ImageTitleList->clear();

    foreach ( const QString &s, selectedObject->InfoTitle() )
        Links->InfoTitleList->addItem( s );

    foreach ( const QString &s, selectedObject->ImageTitle() )
        Links->ImageTitleList->addItem( s );

    updateButtons();
}

void DetailDialog::updateButtons()
{
    bool anyLink=false;
    if (!Links->InfoTitleList->selectedItems().isEmpty() || !Links->ImageTitleList->selectedItems().isEmpty())
        anyLink = true;

    // Buttons could be disabled if lists are initially empty, we enable and disable them here
    // depending on the current status of the list.
    Links->ViewButton->setEnabled(anyLink);
    Links->EditLinkButton->setEnabled(anyLink);
    Links->RemoveLinkButton->setEnabled(anyLink);
}

void DetailDialog::editLinkDialog()
{
    int type=0, row=0;
    QString search_line, replace_line, currentItemTitle, currentItemURL;

    if (m_CurrentLink == NULL) return;

    QDialog editDialog( this );
    editDialog.setWindowTitle( i18n("Edit Link") );

    QVBoxLayout *mainLayout = new QVBoxLayout;

    QFrame editFrame( &editDialog );

    mainLayout->addWidget(&editFrame);

    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
    mainLayout->addWidget(buttonBox);
    connect(buttonBox, SIGNAL(accepted()), &editDialog, SLOT(accept()));
    connect(buttonBox, SIGNAL(rejected()), &editDialog, SLOT(reject()));

    editDialog.setLayout(mainLayout);

    if ( m_CurrentLink->listWidget() == Links->InfoTitleList )
    {
        row = Links->InfoTitleList->row( m_CurrentLink );

        currentItemTitle = m_CurrentLink->text();
        currentItemURL = selectedObject->InfoList().at( row );
        search_line = selectedObject->name();
        search_line += ':';
        search_line += currentItemTitle;
        search_line += ':';
        search_line += currentItemURL;
        type       = 0;
    }
    else if ( m_CurrentLink->listWidget() == Links->ImageTitleList )
    {
        row = Links->ImageTitleList->row( m_CurrentLink );

        currentItemTitle = m_CurrentLink->text();
        currentItemURL = selectedObject->ImageList().at( row );
        search_line = selectedObject->name();
        search_line += ':';
        search_line += currentItemTitle;
        search_line += ':';
        search_line += currentItemURL;
        type 	   = 1;
    }
    else return;

    QLineEdit editNameField(&editFrame);
    editNameField.setObjectName("nameedit");
    editNameField.home(false);
    editNameField.setText(currentItemTitle);
    QLabel editLinkURL(i18n("URL:"), &editFrame);
    QLineEdit editLinkField(&editFrame);
    editLinkField.setObjectName("urledit");
    editLinkField.home(false);
    editLinkField.setText(currentItemURL);
    QVBoxLayout vlay(&editFrame);
    vlay.setObjectName("vlay");
    QHBoxLayout editLinkLayout(&editFrame);
    editLinkLayout.setObjectName("editlinklayout");
    editLinkLayout.addWidget(&editLinkURL);
    editLinkLayout.addWidget(&editLinkField);
    vlay.addWidget( &editNameField );
    vlay.addLayout( &editLinkLayout );

    bool go( true );
    // If user presses cancel then skip the action
    if ( editDialog.exec() != QDialog::Accepted )
        go = false;

    // If nothing changed, skip th action
    if (editLinkField.text() == currentItemURL && editNameField.text() == currentItemTitle)
        go = false;

    if ( go ) {
        replace_line = selectedObject->name() + ':' + editNameField.text() + ':' + editLinkField.text();

        // Info Link
        if (type==0) {
            selectedObject->InfoTitle().replace(row, editNameField.text());
            selectedObject->InfoList().replace(row, editLinkField.text());

        // Image Links
        } else {
            selectedObject->ImageTitle().replace(row, editNameField.text());
            selectedObject->ImageList().replace(row, editLinkField.text());
        }

        // Update local files
        updateLocalDatabase(type, search_line, replace_line);

        // Set focus to the same item again
        if (type == 0)
            Links->InfoTitleList->setCurrentRow(row);
        else
            Links->ImageTitleList->setCurrentRow(row);
    }
}

void DetailDialog::removeLinkDialog()
{
    int type=0, row=0;
    QString currentItemURL, currentItemTitle, LineEntry, TempFileName, FileLine;
    QFile URLFile;
    QTemporaryFile TempFile;
    TempFile.setAutoRemove(false);
    TempFile.open();
    TempFileName = TempFile.fileName();

    if (m_CurrentLink == NULL) return;

    if ( m_CurrentLink->listWidget() == Links->InfoTitleList )
    {
        row = Links->InfoTitleList->row( m_CurrentLink );
        currentItemTitle = m_CurrentLink->text();
        currentItemURL   = selectedObject->InfoList()[row];
        LineEntry = selectedObject->name();
        LineEntry += ':';
        LineEntry += currentItemTitle;
        LineEntry += ':';
        LineEntry += currentItemURL;
        type       = 0;
    }
    else if ( m_CurrentLink->listWidget() == Links->ImageTitleList )
    {
        row = Links->ImageTitleList->row( m_CurrentLink );
        currentItemTitle = m_CurrentLink->text();
        currentItemURL   = selectedObject->ImageList()[row];
        LineEntry = selectedObject->name();
        LineEntry += ':';
        LineEntry += currentItemTitle;
        LineEntry += ':';
        LineEntry += currentItemURL;
        type 	   = 1;
    }
    else return;

    if (KMessageBox::warningContinueCancel( 0, i18n("Are you sure you want to remove the %1 link?", currentItemTitle), i18n("Delete Confirmation"),KStandardGuiItem::del())!=KMessageBox::Continue)
        return;

    if (type ==0)
    {
        selectedObject->InfoTitle().removeAt(row);
        selectedObject->InfoList().removeAt(row);
    }
    else
    {
        selectedObject->ImageTitle().removeAt(row);
        selectedObject->ImageList().removeAt(row);
    }

    // Remove link from file
    updateLocalDatabase(type, LineEntry);

    // Set focus to the 1st item in the list
    if (type == 0)
        Links->InfoTitleList->clearSelection();
    else
        Links->ImageTitleList->clearSelection();

}

void DetailDialog::updateLocalDatabase(int type, const QString &search_line, const QString &replace_line)
{
    QString TempFileName, file_line;
    QFile URLFile;
    QTemporaryFile TempFile;
    TempFile.setAutoRemove(false);
    TempFile.open();
    QTextStream *temp_stream=NULL, *out_stream=NULL;
    bool replace = !replace_line.isEmpty();

    if (search_line.isEmpty())
        return;

    TempFileName = TempFile.fileName();

    switch (type)
    {
        // Info Links
    case 0:
        // Get name for our local info_url file
        URLFile.setFileName( KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "info_url.dat" ) ;
        break;

        // Image Links
    case 1:
        // Get name for our local info_url file
        URLFile.setFileName( KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "image_url.dat" ) ;
        break;
    }

    // Copy URL file to temp file
    KIO::file_copy(QUrl::fromLocalFile(URLFile.fileName()), QUrl::fromLocalFile(TempFileName), -1, KIO::Overwrite | KIO::HideProgressInfo );


    if ( !URLFile.open( QIODevice::WriteOnly) )
    {
        qDebug() << "DetailDialog: Failed to open " << URLFile.fileName();
        qDebug() << "KStars cannot save to user database";
        return;
    }

    // Get streams;
    temp_stream = new QTextStream(&TempFile);
    out_stream  = new QTextStream(&URLFile);

    while (!temp_stream->atEnd())
    {
        file_line = temp_stream->readLine();
        // If we find a match, either replace, or remove (by skipping).
        if (file_line == search_line)
        {
            if (replace)
                (*out_stream) << replace_line << endl;
            else
                continue;
        }
        else
            (*out_stream) << file_line << endl;
    }

    URLFile.close();
    delete(temp_stream);
    delete(out_stream);

    updateLists();

}

void DetailDialog::populateADVTree()
{
    QTreeWidgetItem *parent = NULL, *temp = NULL;

    // We populate the tree iterativley, keeping track of parents as we go
    // This solution is more efficient than the previous recursion algorithm.
    foreach (ADVTreeData *item, KStarsData::Instance()->avdTree())
    {

        switch (item->Type)
        {
            // Top Level
        case 0:
            temp = new QTreeWidgetItem(parent, QStringList(item->Name));
            if (parent == NULL)
                Adv->ADVTree->addTopLevelItem(temp);
            parent = temp;

            break;

            // End of top level
        case 1:
            if (parent != NULL) parent = parent->parent();
            break;

            // Leaf
        case 2:
            new QTreeWidgetItem(parent, QStringList(item->Name));
            break;
        }
    }

}

void  DetailDialog::viewADVData()
{
    QString link;
    QTreeWidgetItem * current = Adv->ADVTree->currentItem();

    //If the item has children or is invalid, do nothing
    if ( !current || current->childCount()>0 )  return;

    foreach (ADVTreeData *item, KStarsData::Instance()->avdTree())
    {
        if (item->Name == current->text(0))
        {
            link = item->Link;
            link = parseADVData(link);
            QDesktopServices::openUrl(QUrl(link));
            return;
        }
    }
}

QString DetailDialog::parseADVData( const QString &inlink )
{
    QString link = inlink;
    QString subLink;
    int index;

    if ( (index = link.indexOf("KSOBJ")) != -1)
    {
        link.remove(index, 5);
        link = link.insert(index, selectedObject->name());
    }

    if ( (index = link.indexOf("KSRA")) != -1)
    {
        link.remove(index, 4);
        subLink.sprintf("%02d%02d%02d", selectedObject->ra0().hour(), selectedObject->ra0().minute(), selectedObject->ra0().second());
        subLink = subLink.insert(2, "%20");
        subLink = subLink.insert(7, "%20");

        link = link.insert(index, subLink);
    }
    if ( (index = link.indexOf("KSDEC")) != -1)
    {
        link.remove(index, 5);
        if (selectedObject->dec().degree() < 0)
        {
            subLink.sprintf("%03d%02d%02d", selectedObject->dec0().degree(), selectedObject->dec0().arcmin(), selectedObject->dec0().arcsec());
            subLink = subLink.insert(3, "%20");
            subLink = subLink.insert(8, "%20");
        }
        else
        {
            subLink.sprintf("%02d%02d%02d", selectedObject->dec0().degree(), selectedObject->dec0().arcmin(), selectedObject->dec0().arcsec());
            subLink = subLink.insert(0, "%2B");
            subLink = subLink.insert(5, "%20");
            subLink = subLink.insert(10, "%20");
        }
        link = link.insert(index, subLink);
    }

    return link;
}

void DetailDialog::saveLogData() {
    selectedObject->saveUserLog( Log->UserLog->toPlainText() );
}

void DetailDialog::addToObservingList() {
    KStarsData::Instance()->observingList()->slotAddObject( selectedObject );
}

void DetailDialog::centerMap()
{
    SkyMap::Instance()->setClickedObject( selectedObject );
    SkyMap::Instance()->slotCenter();
}

void DetailDialog::centerTelescope()
{
#ifdef HAVE_INDI

    if (INDIListener::Instance()->size() == 0)
    {
        KMessageBox::sorry(0, i18n("KStars did not find any active telescopes."));
        return;
    }

    foreach(ISD::GDInterface *gd, INDIListener::Instance()->getDevices())
    {
        INDI::BaseDevice *bd = gd->getBaseDevice();

        if (gd->getType() != KSTARS_TELESCOPE)
            continue;

        if (bd == NULL)
            continue;

        if (bd->isConnected() == false)
        {
            KMessageBox::error(0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName()));
            return;
        }


        ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this);

        gd->setProperty(&SlewCMD);
        gd->runCommand(INDI_SEND_COORDS, selectedObject);

        return;

    }

    KMessageBox::sorry(0, i18n("KStars did not find any active telescopes."));

#endif
}

void DetailDialog::showThumbnail() {
    //No image if object is a star
    if ( selectedObject->type() == SkyObject::STAR ||
         selectedObject->type() == SkyObject::CATALOG_STAR ) {
        Thumbnail->scaled( Data->Image->width(), Data->Image->height() );
        Thumbnail->fill( Data->DataFrame->palette().color( QPalette::Window ) );
        Data->Image->setPixmap( *Thumbnail );
        return;
    }

    //Try to load the object's image from disk
    //If no image found, load "no image" image
    QFile file;
    QString fname = "thumb-" + selectedObject->name().toLower().remove( ' ' ) + ".png";
    if ( KSUtils::openDataFile( file, fname ) ) {
        file.close();
        Thumbnail->load( file.fileName(), "PNG" );
    } else
        Thumbnail->load( ":/images/noimage.png" );

    *Thumbnail=Thumbnail->scaled( Data->Image->width(), Data->Image->height(),Qt::KeepAspectRatio,Qt::FastTransformation);

    Data->Image->setPixmap(*Thumbnail);
}

void DetailDialog::updateThumbnail() {
    QPointer<ThumbnailPicker> tp = new ThumbnailPicker( selectedObject, *Thumbnail, this );

    if ( tp->exec() == QDialog::Accepted ) {
        QString fname = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "thumb-" + selectedObject->name().toLower().remove( ' ' ) + ".png" ;

        Data->Image->setPixmap( *(tp->image()) );

        //If a real image was set, save it.
        //If the image was unset, delete the old image on disk.
        if ( tp->imageFound() )
        {
            bool rc = Data->Image->pixmap()->save( fname, "PNG" );
            if (rc == false)
            {
                KMessageBox::error(0, i18n("Error: Unable to save image to %1", fname), i18n("Save Thumbnail"));
            }
            else
                *Thumbnail = *(Data->Image->pixmap());
        } else {
            QFile f;
            f.setFileName( fname );
            f.remove();
        }
    }
    delete tp;
}

DataWidget::DataWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
    DataFrame->setBackgroundRole( QPalette::Base );
}

DataCometWidget::DataCometWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
}

PositionWidget::PositionWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
    CoordFrame->setBackgroundRole( QPalette::Base );
    RSTFrame->setBackgroundRole( QPalette::Base );
}

LinksWidget::LinksWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
}

DatabaseWidget::DatabaseWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
}

LogWidget::LogWidget( QWidget *p ) : QFrame( p )
{
    setupUi( this );
}


