skymap.cpp 40.2 KB
Newer Older
1
/**************************************************************************
Chris Howells's avatar
adding  
Chris Howells committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
                          skymap.cpp  -  K Desktop Planetarium
                             -------------------
    begin                : Sat Feb 10 2001
    copyright            : (C) 2001 by Jason Harris
    email                : jharris@30doradus.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

Patrick Spendrin's avatar
Patrick Spendrin committed
18 19 20
#ifdef _WIN32
#include <windows.h>
#endif
21

22
#include "skymap.h"
Chris Howells's avatar
adding  
Chris Howells committed
23

24
#include "ksasteroid.h"
25
#include "kstars_debug.h"
26
#include "fov.h"
Chris Howells's avatar
adding  
Chris Howells committed
27
#include "imageviewer.h"
Robert Lancaster's avatar
Robert Lancaster committed
28
#include "xplanetimageviewer.h"
29 30
#include "ksdssdownloader.h"
#include "kspaths.h"
31
#include "kspopupmenu.h"
Csaba Kertesz's avatar
Csaba Kertesz committed
32
#include "kstars.h"
33 34
#include "ksutils.h"
#include "Options.h"
Csaba Kertesz's avatar
Csaba Kertesz committed
35
#include "skymapcomposite.h"
36 37 38 39 40
#ifdef HAVE_OPENGL
#include "skymapgldraw.h"
#endif
#include "skymapqdraw.h"
#include "starhopperdialog.h"
Csaba Kertesz's avatar
Csaba Kertesz committed
41
#include "starobject.h"
42
#include "syncedcatalogcomponent.h"
43
#include "texturemanager.h"
Csaba Kertesz's avatar
Csaba Kertesz committed
44
#include "dialogs/detaildialog.h"
45
#include "printing/printingwizard.h"
46
#include "skycomponents/flagcomponent.h"
47
#include "skyobjects/deepskyobject.h"
48
#include "skyobjects/ksplanetbase.h"
49
#include "tools/flagmanager.h"
Alexey Khudyakov's avatar
Alexey Khudyakov committed
50
#include "widgets/infoboxwidget.h"
51 52
#include "projections/azimuthalequidistantprojector.h"
#include "projections/equirectangularprojector.h"
53
#include "projections/lambertprojector.h"
Henry de Valence's avatar
Henry de Valence committed
54
#include "projections/gnomonicprojector.h"
Henry de Valence's avatar
Henry de Valence committed
55
#include "projections/orthographicprojector.h"
56
#include "projections/stereographicprojector.h"
Akarsh Simha's avatar
Akarsh Simha committed
57

58 59 60
#include <KActionCollection>
#include <KToolBar>
#include <KToolInvocation>
61

62 63
#include <QBitmap>
#include <QToolTip>
64
#include <QClipboard>
65 66
#include <QInputDialog>
#include <QDesktopServices>
67

Jasem Mutlaq's avatar
Jasem Mutlaq committed
68
#include <QProcess>
69
#include <QFileDialog>
70

71 72
#include <cmath>

73 74 75 76 77 78 79 80 81 82
namespace
{
// Draw bitmap for zoom cursor. Width is size of pen to draw with.
QBitmap zoomCursorBitmap(int width)
{
    QBitmap b(32, 32);
    b.fill(Qt::color0);
    int mx = 16, my = 16;
    // Begin drawing
    QPainter p;
83 84 85 86
    p.begin(&b);
    p.setPen(QPen(Qt::color1, width));
    p.drawEllipse(mx - 7, my - 7, 14, 14);
    p.drawLine(mx + 5, my + 5, mx + 11, my + 11);
87 88 89 90 91 92 93 94 95 96 97 98
    p.end();
    return b;
}

// Draw bitmap for default cursor. Width is size of pen to draw with.
QBitmap defaultCursorBitmap(int width)
{
    QBitmap b(32, 32);
    b.fill(Qt::color0);
    int mx = 16, my = 16;
    // Begin drawing
    QPainter p;
99 100
    p.begin(&b);
    p.setPen(QPen(Qt::color1, width));
101
    // 1. diagonal
102 103
    p.drawLine(mx - 2, my - 2, mx - 8, mx - 8);
    p.drawLine(mx + 2, my + 2, mx + 8, mx + 8);
104
    // 2. diagonal
105 106
    p.drawLine(mx - 2, my + 2, mx - 8, mx + 8);
    p.drawLine(mx + 2, my - 2, mx + 8, mx - 8);
107 108 109
    p.end();
    return b;
}
Jasem Mutlaq's avatar
Jasem Mutlaq committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

QBitmap circleCursorBitmap(int width)
{
    QBitmap b(32, 32);
    b.fill(Qt::color0);
    int mx = 16, my = 16;
    // Begin drawing
    QPainter p;
    p.begin(&b);
    p.setPen(QPen(Qt::color1, width));

    // Circle
    p.drawEllipse(mx - 8, my - 8, mx, my);
    // 1. diagonal
    p.drawLine(mx - 8, my - 8, 0, 0);
    p.drawLine(mx + 8, my - 8, 32, 0);
    // 2. diagonal
    p.drawLine(mx - 8, my + 8, 0, 32);
    p.drawLine(mx + 8, my + 8, 32, 32);

    p.end();
    return b;
}

134 135
}

136
SkyMap *SkyMap::pinstance = nullptr;
137

138
SkyMap *SkyMap::Create()
139
{
Akarsh Simha's avatar
Akarsh Simha committed
140
    delete pinstance;
141
    pinstance = new SkyMap();
142 143 144
    return pinstance;
}

145
SkyMap *SkyMap::Instance()
146 147 148 149
{
    return pinstance;
}

150
SkyMap::SkyMap()
151 152
    : QGraphicsView(KStars::Instance()), computeSkymap(true), rulerMode(false), data(KStarsData::Instance()), pmenu(nullptr),
      ClickedObject(nullptr), FocusObject(nullptr), m_proj(nullptr), m_previewLegend(false), m_objPointingMode(false)
Chris Howells's avatar
adding  
Chris Howells committed
153
{
154 155 156 157
#if !defined(KSTARS_LITE)
    grabGesture(Qt::PinchGesture);
    grabGesture(Qt::TapAndHoldGesture);
#endif
Jason Harris's avatar
Jason Harris committed
158 159
    m_Scale = 1.0;

160 161
    ZoomRect = QRect();

Jasem Mutlaq's avatar
Jasem Mutlaq committed
162 163
    // set the default cursor
    setMouseCursorShape(static_cast<Cursor>(Options::defaultCursor()));
164 165

    QPalette p = palette();
166 167
    p.setColor(QPalette::Window, QColor(data->colorScheme()->colorNamed("SkyColor")));
    setPalette(p);
168

169 170 171 172 173 174
    setFocusPolicy(Qt::StrongFocus);
    setMinimumSize(380, 250);
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setStyleSheet("QGraphicsView { border-style: none; }");
175

176
    setMouseTracking(true); //Generate MouseMove events!
177
    midMouseButtonDown = false;
178 179 180
    mouseButtonDown    = false;
    slewing            = false;
    clockSlewing       = false;
181

Csaba Kertesz's avatar
Csaba Kertesz committed
182
    ClickedObject = nullptr;
183
    FocusObject   = nullptr;
184

185
    m_SkyMapDraw = nullptr;
186

187
    pmenu = new KSPopupMenu();
188

Henry de Valence's avatar
Purge.  
Henry de Valence committed
189
    setupProjector();
190

191
    //Initialize Transient label stuff
192
    m_HoverTimer.setSingleShot(true); // using this timer as a single shot timer
193

194 195 196
    connect(&m_HoverTimer, SIGNAL(timeout()), this, SLOT(slotTransientLabel()));
    connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus()));
    connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool)));
197

198
    // Time infobox
199 200 201 202 203
    m_timeBox = new InfoBoxWidget(Options::shadeTimeBox(), Options::positionTimeBox(), Options::stickyTimeBox(),
                                  QStringList(), this);
    m_timeBox->setVisible(Options::showTimeBox());
    connect(data->clock(), SIGNAL(timeChanged()), m_timeBox, SLOT(slotTimeChanged()));
    connect(data->clock(), SIGNAL(timeAdvanced()), m_timeBox, SLOT(slotTimeChanged()));
204 205

    // Geo infobox
206 207 208 209
    m_geoBox = new InfoBoxWidget(Options::shadeGeoBox(), Options::positionGeoBox(), Options::stickyGeoBox(),
                                 QStringList(), this);
    m_geoBox->setVisible(Options::showGeoBox());
    connect(data, SIGNAL(geoChanged()), m_geoBox, SLOT(slotGeoChanged()));
210 211

    // Object infobox
212 213 214
    m_objBox = new InfoBoxWidget(Options::shadeFocusBox(), Options::positionFocusBox(), Options::stickyFocusBox(),
                                 QStringList(), this);
    m_objBox->setVisible(Options::showFocusBox());
215 216
    connect(this, SIGNAL(objectChanged(SkyObject*)), m_objBox, SLOT(slotObjectChanged(SkyObject*)));
    connect(this, SIGNAL(positionChanged(SkyPoint*)), m_objBox, SLOT(slotPointChanged(SkyPoint*)));
Alexey Khudyakov's avatar
Alexey Khudyakov committed
217

218 219
    m_SkyMapDraw = new SkyMapQDraw(this);
    m_SkyMapDraw->setMouseTracking(true);
220

221
    m_SkyMapDraw->setParent(this->viewport());
222
    m_SkyMapDraw->show();
223

224
    m_iboxes = new InfoBoxes(m_SkyMapDraw);
225

226
    m_iboxes->setVisible(Options::showInfoBoxes());
227 228
    m_iboxes->addInfoBox(m_timeBox);
    m_iboxes->addInfoBox(m_geoBox);
229
    m_iboxes->addInfoBox(m_objBox);
230 231
}

232 233
void SkyMap::slotToggleGeoBox(bool flag)
{
234 235 236
    m_geoBox->setVisible(flag);
}

237 238
void SkyMap::slotToggleFocusBox(bool flag)
{
239 240 241
    m_objBox->setVisible(flag);
}

242 243
void SkyMap::slotToggleTimeBox(bool flag)
{
244 245 246
    m_timeBox->setVisible(flag);
}

247 248
void SkyMap::slotToggleInfoboxes(bool flag)
{
249
    m_iboxes->setVisible(flag);
250
    Options::setShowInfoBoxes(flag);
Chris Howells's avatar
adding  
Chris Howells committed
251 252
}

253 254
SkyMap::~SkyMap()
{
255
    /* == Save infoxes status into Options == */
256
    //Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget()));
257
    // Time box
258 259 260 261
    Options::setPositionTimeBox(m_timeBox->pos());
    Options::setShadeTimeBox(m_timeBox->shaded());
    Options::setStickyTimeBox(m_timeBox->sticky());
    Options::setShowTimeBox(m_timeBox->isVisibleTo(m_iboxes));
262
    // Geo box
263 264 265 266
    Options::setPositionGeoBox(m_geoBox->pos());
    Options::setShadeGeoBox(m_geoBox->shaded());
    Options::setStickyGeoBox(m_geoBox->sticky());
    Options::setShowGeoBox(m_geoBox->isVisibleTo(m_iboxes));
267
    // Obj box
268 269 270 271
    Options::setPositionFocusBox(m_objBox->pos());
    Options::setShadeFocusBox(m_objBox->shaded());
    Options::setStickyFocusBox(m_objBox->sticky());
    Options::setShowFocusBox(m_objBox->isVisibleTo(m_iboxes));
272

273
    //store focus values in Options
Alexey Khudyakov's avatar
Alexey Khudyakov committed
274
    //If not tracking and using Alt/Az coords, stor the Alt/Az coordinates
275
    if (Options::useAltAz() && !Options::isTracking())
276
    {
277 278
        Options::setFocusRA(focus()->az().Degrees());
        Options::setFocusDec(focus()->alt().Degrees());
279 280 281
    }
    else
    {
282 283
        Options::setFocusRA(focus()->ra().Hours());
        Options::setFocusDec(focus()->dec().Degrees());
284 285
    }

286 287 288 289 290 291 292
#ifdef HAVE_OPENGL
    delete m_SkyMapGLDraw;
    delete m_SkyMapQDraw;
    m_SkyMapDraw = 0; // Just a formality
#else
    delete m_SkyMapDraw;
#endif
293

294
    delete pmenu;
295 296

    delete m_proj;
297

298
    pinstance = nullptr;
Chris Howells's avatar
adding  
Chris Howells committed
299 300
}

301 302
void SkyMap::showFocusCoords()
{
303 304
    if (focusObject() && Options::isTracking())
        emit objectChanged(focusObject());
305
    else
306
        emit positionChanged(focus());
307 308
}

309 310
void SkyMap::slotTransientLabel()
{
311 312 313
    //This function is only called if the HoverTimer manages to timeout.
    //(HoverTimer is restarted with every mouseMoveEvent; so if it times
    //out, that means there was no mouse movement for HOVER_INTERVAL msec.)
314
    if (hasFocus() && !slewing &&
315
            !(Options::useAltAz() && Options::showGround() && SkyPoint::refract(m_MousePoint.alt()).Degrees() < 0.0))
316
    {
317 318
        double maxrad = 1000.0 / Options::zoomFactor();
        SkyObject *so = data->skyComposite()->objectNearest(&m_MousePoint, maxrad);
319

320
        if (so && !isObjectLabeled(so))
321
        {
322 323 324
            QToolTip::showText(QCursor::pos(),
                               i18n("%1: %2<sup>m</sup>", so->translatedLongName(), QString::number(so->mag(), 'f', 1)),
                               this);
325 326
        }
    }
327 328
}

329
//Slots
330

331
void SkyMap::setClickedObject(SkyObject *o)
332 333
{
    ClickedObject = o;
334 335
}

336
void SkyMap::setFocusObject(SkyObject *o)
337
{
338
    FocusObject = o;
339 340
    if (FocusObject)
        Options::setFocusObject(FocusObject->name());
341
    else
342
        Options::setFocusObject(i18n("nothing"));
343 344
}

345 346
void SkyMap::slotCenter()
{
347
    KStars *kstars        = KStars::Instance();
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    TrailObject *trailObj = dynamic_cast<TrailObject *>(focusObject());

    SkyPoint *foc;
    if(ClickedObject != nullptr)
        foc = ClickedObject;
    else
        foc = &ClickedPoint;

    if (Options::useAltAz())
    {
        // JM 2016-09-12: Following call has problems when ra0/dec0 of an object are not valid for example
        // because they're solar system bodies. So it creates a lot of issues. It is disabled and centering
        // works correctly for all different body types as I tested.
        //DeepSkyObject *dso = dynamic_cast<DeepSkyObject *>(focusObject());
        //if (dso)
        //    foc->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);

        // JM 2018-05-06: No need to do the above
        foc->EquatorialToHorizontal(data->lst(), data->geo()->lat());
    }
    else
        foc->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);

    qCDebug(KSTARS) << "Centering on " << foc->ra().toHMSString() << foc->dec().toDMSString();

    //clear the planet trail of old focusObject, if it was temporary
    if (trailObj && data->temporaryTrail)
    {
        trailObj->clearTrail();
        data->temporaryTrail = false;
    }

    //If the requested object is below the opaque horizon, issue a warning message
    //(unless user is already pointed below the horizon)
    if (Options::useAltAz() && Options::showGround() && focus()->alt().Degrees() > -1.0 &&
            foc->alt().Degrees() < -1.0)
    {
        QString caption = i18n("Requested Position Below Horizon");
        QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?");
387 388
        if (KMessageBox::warningYesNo(this, message, caption, KGuiItem(i18n("Go Anyway")),
                                      KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz") == KMessageBox::No)
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
        {
            setClickedObject(nullptr);
            setFocusObject(nullptr);
            Options::setIsTracking(false);

            return;
        }
    }

    //set FocusObject before slewing.  Otherwise, KStarsData::updateTime() can reset
    //destination to previous object...
    setFocusObject(ClickedObject);
    if(ClickedObject == nullptr)
        setFocusPoint(&ClickedPoint);

    Options::setIsTracking(true);

    if (kstars)
    {
        kstars->actionCollection()
Jasem Mutlaq's avatar
Jasem Mutlaq committed
409 410
        ->action("track_object")
        ->setIcon(QIcon::fromTheme("document-encrypt"));
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
        kstars->actionCollection()->action("track_object")->setText(i18n("Stop &Tracking"));
    }

    //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail

    if (Options::useAutoTrail() && trailObj && trailObj->hasTrail())
    {
        trailObj->addToTrail();
        data->temporaryTrail = true;
    }

    //update the destination to the selected coordinates
    if (Options::useAltAz())
    {
        setDestinationAltAz(foc->altRefracted(), foc->az());
    }
    else
    {
        setDestination(*foc);
    }

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

    //display coordinates in statusBar
    emit mousePointChanged(foc);
    showFocusCoords(); //update FocusBox
Chris Howells's avatar
adding  
Chris Howells committed
437 438
}

439
void SkyMap::slotUpdateSky(bool now)
440
{
441 442 443 444
    // Code moved from KStarsData::updateTime()
    //Update focus
    updateFocus();

445 446
    if (now)
        QTimer::singleShot(
Jasem Mutlaq's avatar
Jasem Mutlaq committed
447 448
            0, this,
            SLOT(forceUpdateNow())); // Why is it done this way rather than just calling forceUpdateNow()? -- asimha // --> Opening a neww thread? -- Valentin
449 450 451 452
    else
        forceUpdate();
}

453 454
void SkyMap::slotDSS()
{
455
    dms ra(0.0), dec(0.0);
456
    QString urlstring;
457 458 459

    //ra and dec must be the coordinates at J2000.  If we clicked on an object, just use the object's ra0, dec0 coords
    //if we clicked on empty sky, we need to precess to J2000.
460
    if (clickedObject())
461
    {
462
        urlstring = KSDssDownloader::getDSSURL(clickedObject());
463 464 465
    }
    else
    {
466 467 468 469
        SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum());
        ra                        = deprecessedPoint.ra();
        dec                       = deprecessedPoint.dec();
        urlstring                 = KSDssDownloader::getDSSURL(ra, dec); // Use default size for non-objects
470 471
    }

472
    QUrl url(urlstring);
473

474 475
    KStars *kstars = KStars::Instance();
    if (kstars)
476
    {
477
        new ImageViewer(
Jasem Mutlaq's avatar
Jasem Mutlaq committed
478 479
            url, i18n("Digitized Sky Survey image provided by the Space Telescope Science Institute [public domain]."),
            this);
480
        //iv->show();
481
    }
Chris Howells's avatar
adding  
Chris Howells committed
482 483
}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
void SkyMap::slotCopyCoordinates()
{
    dms J2000RA(0.0), J2000DE(0.0), JNowRA(0.0), JNowDE(0.0), Az, Alt;
    if (clickedObject())
    {
        J2000RA  = clickedObject()->ra0();
        J2000DE = clickedObject()->dec0();
        JNowRA = clickedObject()->ra();
        JNowDE = clickedObject()->dec();
        Az = clickedObject()->az();
        Alt = clickedObject()->alt();
    }
    else
    {
        SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum());
        deprecessedPoint.EquatorialToHorizontal(data->lst(), data->geo()->lat());

        J2000RA = deprecessedPoint.ra0();
        J2000DE = deprecessedPoint.dec0();
        JNowRA = deprecessedPoint.ra();
        JNowDE = deprecessedPoint.dec();
        Az = deprecessedPoint.az();
        Alt = deprecessedPoint.alt();
    }

    QApplication::clipboard()->setText(i18nc("Equatorial & Horizontal Coordinates", "JNow:\t%1\t%2\nJ2000:\t%3\t%4\nAzAlt:\t%5\t%6",
                                       JNowRA.toHMSString(),
Jasem Mutlaq's avatar
Jasem Mutlaq committed
511
                                       JNowDE.toDMSString(),
512 513 514 515 516 517
                                       J2000RA.toHMSString(),
                                       J2000DE.toDMSString(),
                                       Az.toDMSString(),
                                       Alt.toDMSString()));
}

518 519
void SkyMap::slotSDSS()
{
Akarsh Simha's avatar
Akarsh Simha committed
520 521 522 523
    // TODO: Remove code duplication -- we have the same stuff
    // implemented in ObservingList::setCurrentImage() etc. in
    // tools/observinglist.cpp; must try to de-duplicate as much as
    // possible.
524 525
    QString URLprefix("http://casjobs.sdss.org/ImgCutoutDR6/getjpeg.aspx?");
    QString URLsuffix("&scale=1.0&width=600&height=600&opt=GST&query=SR(10,20)");
Akarsh Simha's avatar
Akarsh Simha committed
526 527 528 529 530
    dms ra(0.0), dec(0.0);
    QString RAString, DecString;

    //ra and dec must be the coordinates at J2000.  If we clicked on an object, just use the object's ra0, dec0 coords
    //if we clicked on empty sky, we need to precess to J2000.
531
    if (clickedObject())
532
    {
Akarsh Simha's avatar
Akarsh Simha committed
533 534
        ra  = clickedObject()->ra0();
        dec = clickedObject()->dec0();
535 536 537
    }
    else
    {
538 539 540
        SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum());
        ra                        = deprecessedPoint.ra();
        dec                       = deprecessedPoint.dec();
Akarsh Simha's avatar
Akarsh Simha committed
541 542
    }

Laurent Montel's avatar
Laurent Montel committed
543 544
    RAString  = QString::asprintf("ra=%f", ra.Degrees());
    DecString = QString::asprintf("&dec=%f", dec.Degrees());
Akarsh Simha's avatar
Akarsh Simha committed
545 546

    //concat all the segments into the kview command line:
547
    QUrl url(URLprefix + RAString + DecString + URLsuffix);
548

549 550
    KStars *kstars = KStars::Instance();
    if (kstars)
551
    {
552 553 554 555
        new ImageViewer(url,
                        i18n("Sloan Digital Sky Survey image provided by the Astrophysical Research Consortium [free "
                             "for non-commercial use]."),
                        this);
556
        //iv->show();
557
    }
Chris Howells's avatar
adding  
Chris Howells committed
558 559
}

560 561
void SkyMap::slotEyepieceView()
{
562
    KStars::Instance()->slotEyepieceView((clickedObject() ? clickedObject() : clickedPoint()));
563
}
564 565
void SkyMap::slotBeginAngularDistance()
{
566
    beginRulerMode(false);
567 568
}

569 570
void SkyMap::slotBeginStarHop()
{
571
    beginRulerMode(true);
572 573
}

574
void SkyMap::beginRulerMode(bool starHopRuler)
575
{
576
    rulerMode         = true;
577
    starHopDefineMode = starHopRuler;
578 579
    AngularRuler.clear();

580
    //If the cursor is near a SkyObject, reset the AngularRuler's
581
    //start point to the position of the SkyObject
582 583 584
    double maxrad = 1000.0 / Options::zoomFactor();
    SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad);
    if (so)
585
    {
586 587
        AngularRuler.append(so);
        AngularRuler.append(so);
588
        m_rulerStartPoint = so;
589 590 591
    }
    else
    {
592 593
        AngularRuler.append(clickedPoint());
        AngularRuler.append(clickedPoint());
594
        m_rulerStartPoint = clickedPoint();
595
    }
596

597
    AngularRuler.update(data);
598 599
}

600 601
void SkyMap::slotEndRulerMode()
{
602
    if (!rulerMode)
603
        return;
604
    if (!starHopDefineMode) // Angular Ruler
605
    {
606 607 608 609
        QString sbMessage;

        //If the cursor is near a SkyObject, reset the AngularRuler's
        //end point to the position of the SkyObject
610 611 612 613
        double maxrad = 1000.0 / Options::zoomFactor();
        SkyPoint *rulerEndPoint;
        SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad);
        if (so)
614
        {
615 616
            AngularRuler.setPoint(1, so);
            sbMessage     = so->translatedLongName() + "   ";
617
            rulerEndPoint = so;
618 619 620
        }
        else
        {
621
            AngularRuler.setPoint(1, clickedPoint());
622
            rulerEndPoint = clickedPoint();
623
        }
624

625 626
        rulerMode = false;
        AngularRuler.update(data);
Alexey Khudyakov's avatar
Alexey Khudyakov committed
627
        dms angularDistance = AngularRuler.angularSize();
628

629
        sbMessage += i18n("Angular distance: %1", angularDistance.toDMSString());
630

631 632
        const StarObject *p1 = dynamic_cast<const StarObject *>(m_rulerStartPoint);
        const StarObject *p2 = dynamic_cast<const StarObject *>(rulerEndPoint);
633 634

        qCDebug(KSTARS) << "Starobjects? " << p1 << p2;
635
        if (p1 && p2)
636
            qCDebug(KSTARS) << "Distances: " << p1->distance() << "pc; " << p2->distance() << "pc";
637
        if (p1 && p2 && std::isfinite(p1->distance()) && std::isfinite(p2->distance()) && p1->distance() > 0 &&
638
                p2->distance() > 0)
639
        {
640 641
            double dist = sqrt(p1->distance() * p1->distance() + p2->distance() * p2->distance() -
                               2 * p1->distance() * p2->distance() * cos(angularDistance.radians()));
642
            qCDebug(KSTARS) << "Could calculate physical distance: " << dist << " pc";
643
            sbMessage += i18n("; Physical distance: %1 pc", QString::number(dist));
644 645 646 647
        }

        AngularRuler.clear();

Alexey Khudyakov's avatar
Alexey Khudyakov committed
648 649
        // Create unobsructive message box with suicidal tendencies
        // to display result.
650 651 652
        InfoBoxWidget *box = new InfoBoxWidget(true, mapFromGlobal(QCursor::pos()), 0, QStringList(sbMessage), this);
        connect(box, SIGNAL(clicked()), box, SLOT(deleteLater()));
        QTimer::singleShot(5000, box, SLOT(deleteLater()));
Alexey Khudyakov's avatar
Alexey Khudyakov committed
653 654
        box->adjust();
        box->show();
655
    }
656
    else // Star Hop
657
    {
658 659 660
        StarHopperDialog *shd    = new StarHopperDialog(this);
        const SkyPoint &startHop = *AngularRuler.point(0);
        const SkyPoint &stopHop  = *clickedPoint();
661
        double fov; // Field of view in arcminutes
662 663
        bool ok;    // true if user did not cancel the operation
        if (data->getAvailableFOVs().size() == 1)
664
        {
665
            // Exactly 1 FOV symbol visible, so use that. Also assume a circular FOV of size min{sizeX, sizeY}
666 667 668
            FOV *f = data->getAvailableFOVs().first();
            fov    = ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX());
            ok     = true;
669
        }
670
        else if (!data->getAvailableFOVs().isEmpty())
671
        {
672
            // Ask the user to choose from a list of available FOVs.
673 674 675
            FOV const *f;
            QMap<QString, double> nameToFovMap;
            foreach (f, data->getAvailableFOVs())
676
            {
677 678
                nameToFovMap.insert(f->name(),
                                    ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX()));
679
            }
680
            fov = nameToFovMap[QInputDialog::getItem(this, i18n("Star Hopper: Choose a field-of-view"),
Jasem Mutlaq's avatar
Jasem Mutlaq committed
681 682
                                                           i18n("FOV to use for star hopping:"), nameToFovMap.uniqueKeys(), 0,
                                                           false, &ok)];
683
        }
684 685
        else
        {
686
            // Ask the user to enter a field of view
687
            fov =
Jasem Mutlaq's avatar
Jasem Mutlaq committed
688 689
                QInputDialog::getDouble(this, i18n("Star Hopper: Enter field-of-view to use"),
                                        i18n("FOV to use for star hopping (in arcminutes):"), 60.0, 1.0, 600.0, 1, &ok);
690 691
        }

692
        Q_ASSERT(fov > 0.0);
693

694
        if (ok)
695
        {
696
            qCDebug(KSTARS) << "fov = " << fov;
697

698
            shd->starHop(startHop, stopHop, fov / 60.0, 9.0); //FIXME: Hardcoded maglimit value
699
            shd->show();
700
        }
701

702 703
        rulerMode = false;
    }
704 705
}

706 707
void SkyMap::slotCancelRulerMode(void)
{
708
    rulerMode = false;
709
    AngularRuler.clear();
710 711
}

712 713
void SkyMap::slotAddFlag()
{
714
    KStars *ks = KStars::Instance();
715

716
    // popup FlagManager window and update coordinates
717
    ks->slotFlagManager();
Jasem Mutlaq's avatar
Jasem Mutlaq committed
718
    ks->flagManager()->clearFields();
719 720 721 722 723 724

    //ra and dec must be the coordinates at J2000.  If we clicked on an object, just use the object's ra0, dec0 coords
    //if we clicked on empty sky, we need to precess to J2000.

    dms J2000RA, J2000DE;

725
    if (clickedObject())
726 727 728 729 730 731
    {
        J2000RA = clickedObject()->ra0();
        J2000DE = clickedObject()->dec0();
    }
    else
    {
732 733 734
        SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum());
        J2000RA                   = deprecessedPoint.ra();
        J2000DE                   = deprecessedPoint.dec();
735 736 737
    }

    ks->flagManager()->setRaDec(J2000RA, J2000DE);
738 739
}

740
void SkyMap::slotEditFlag(int flagIdx)
741
{
742
    KStars *ks = KStars::Instance();
743

744
    // popup FlagManager window and switch to selected flag
745
    ks->slotFlagManager();
746
    ks->flagManager()->showFlag(flagIdx);
747 748
}

749
void SkyMap::slotDeleteFlag(int flagIdx)
750
{
751
    KStars *ks = KStars::Instance();
752

753
    ks->data()->skyComposite()->flags()->remove(flagIdx);
754 755 756
    ks->data()->skyComposite()->flags()->saveToFile();

    // if there is FlagManager created, update its flag model
757
    if (ks->flagManager())
758
    {
759
        ks->flagManager()->deleteFlagItem(flagIdx);
760
    }
761 762
}

763 764
void SkyMap::slotImage()
{
Jasem Mutlaq's avatar
Jasem Mutlaq committed
765
    QString message = ((QAction *)sender())->text();
766
    message         = message.remove('&'); //Get rid of accelerator markers
767 768 769

    // Need to do this because we are comparing translated strings
    int index = -1;
770
    for (int i = 0; i < clickedObject()->ImageTitle().size(); ++i)
771
    {
772 773
        if (i18nc("Image/info menu item (should be translated)",
                  clickedObject()->ImageTitle().at(i).toLocal8Bit().data()) == message)
774
        {
775 776 777 778
            index = i;
            break;
        }
    }
779

780
    QString sURL;
781
    if (index >= 0 && index < clickedObject()->ImageList().size())
782
    {
783
        sURL = clickedObject()->ImageList()[index];
784 785 786
    }
    else
    {
787
        qCWarning(KSTARS) << "ImageList index out of bounds: " << index;
788
        if (index == -1)
789
        {
790 791
            qCWarning(KSTARS) << "Message string \"" << message << "\" not found in ImageTitle.";
            qCDebug(KSTARS) << clickedObject()->ImageTitle();
792 793 794
        }
    }

795 796 797
    QUrl url(sURL);
    if (!url.isEmpty())
        new ImageViewer(url, clickedObject()->messageFromTitle(message), this);
798 799
}

800 801
void SkyMap::slotInfo()
{
Jasem Mutlaq's avatar
Jasem Mutlaq committed
802
    QString message = ((QAction *)sender())->text();
803
    message         = message.remove('&'); //Get rid of accelerator markers
804 805 806

    // Need to do this because we are comparing translated strings
    int index = -1;
807
    for (int i = 0; i < clickedObject()->InfoTitle().size(); ++i)
808
    {
809 810
        if (i18nc("Image/info menu item (should be translated)",
                  clickedObject()->InfoTitle().at(i).toLocal8Bit().data()) == message)
811
        {
812 813 814 815
            index = i;
            break;
        }
    }
816

817
    QString sURL;
818
    if (index >= 0 && index < clickedObject()->InfoList().size())
819
    {
820
        sURL = clickedObject()->InfoList()[index];
821 822 823
    }
    else
    {
824
        qCWarning(KSTARS) << "InfoList index out of bounds: " << index;
825
        if (index == -1)
826
        {
827 828
            qCWarning(KSTARS) << "Message string \"" << message << "\" not found in InfoTitle.";
            qCDebug(KSTARS) << clickedObject()->InfoTitle();
829 830 831
        }
    }

832
    QUrl url(sURL);
833
    if (!url.isEmpty())
834
        QDesktopServices::openUrl(url);
Chris Howells's avatar
adding  
Chris Howells committed
835 836
}

837
bool SkyMap::isObjectLabeled(SkyObject *object)
838
{
839
    return data->skyComposite()->labelObjects().contains(object);
840 841
}

842 843 844
SkyPoint SkyMap::getCenterPoint()
{
    SkyPoint retVal;
Csaba Kertesz's avatar
Csaba Kertesz committed
845 846 847
    // FIXME: subtraction of these 0.00001 is a simple workaround, because wrong
    // SkyPoint is returned when _exact_ center of SkyMap is passed to the projector.
    retVal = projector()->fromScreen(QPointF((qreal)width() / 2 - 0.00001, (qreal)height() / 2 - 0.00001