File indexing completed on 2024-04-21 14:46:52

0001 /*
0002     SPDX-FileCopyrightText: 2016 Akarsh Simha <akarsh@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "exporteyepieceview.h"
0008 
0009 #include "dms.h"
0010 #include "eyepiecefield.h"
0011 #include "kstarsdata.h"
0012 #include "Options.h"
0013 #include "skypoint.h"
0014 
0015 #include <QComboBox>
0016 #include <QDialogButtonBox>
0017 #include <QFileDialog>
0018 #include <QHBoxLayout>
0019 #include <QLabel>
0020 #include <QPainter>
0021 #include <QPixmap>
0022 #include <QVBoxLayout>
0023 #include <QWidget>
0024 
0025 ExportEyepieceView::ExportEyepieceView(const SkyPoint *_sp, const KStarsDateTime &dt, const QPixmap *renderImage,
0026                                        const QPixmap *renderChart, QWidget *parent)
0027     : QDialog(parent), m_dt(dt)
0028 {
0029 #ifdef Q_OS_OSX
0030     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0031 #endif
0032     m_sp.reset(new SkyPoint(*_sp)); // Work on a copy.
0033 
0034     Q_ASSERT(renderChart);
0035     m_renderChart.reset(new QPixmap(*renderChart));
0036 
0037     if (renderImage != nullptr)
0038         m_renderImage.reset(new QPixmap(*renderImage));
0039 
0040     setWindowTitle(i18nc("@title:window", "Export eyepiece view"));
0041 
0042     QWidget *mainWidget     = new QWidget(this);
0043     QVBoxLayout *mainLayout = new QVBoxLayout;
0044     mainLayout->addWidget(mainWidget);
0045     setLayout(mainLayout);
0046 
0047     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
0048     mainLayout->addWidget(buttonBox);
0049     connect(buttonBox, SIGNAL(rejected()), this, SLOT(slotCloseDialog()));
0050     connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotSaveImage()));
0051 
0052     QVBoxLayout *rows = new QVBoxLayout;
0053     mainWidget->setLayout(rows);
0054 
0055     QLabel *tickInfoLabel = new QLabel(i18n("Overlay orientation vs. time ticks: "), this);
0056     m_tickConfigCombo     = new QComboBox(this);
0057     m_tickConfigCombo->addItem(i18n("None"));
0058     m_tickConfigCombo->addItem(i18n("Towards Zenith"));
0059     m_tickConfigCombo->addItem(i18n("Dobsonian View"));
0060 
0061     QHBoxLayout *optionsLayout = new QHBoxLayout;
0062     optionsLayout->addWidget(tickInfoLabel);
0063     optionsLayout->addWidget(m_tickConfigCombo);
0064     optionsLayout->addStretch();
0065     rows->addLayout(optionsLayout);
0066 
0067     m_tickWarningLabel = new QLabel(this);
0068     rows->addWidget(m_tickWarningLabel);
0069 
0070     m_outputDisplay = new QLabel;
0071     m_outputDisplay->setBackgroundRole(QPalette::Base);
0072     m_outputDisplay->setScaledContents(false);
0073     m_outputDisplay->setMinimumWidth(400);
0074     m_outputDisplay->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0075     rows->addWidget(m_outputDisplay);
0076 
0077     connect(m_tickConfigCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotOverlayTicks(int)));
0078     connect(m_tickConfigCombo, SIGNAL(activated(int)), this, SLOT(slotOverlayTicks(int)));
0079 
0080     render();
0081     show();
0082 }
0083 
0084 void ExportEyepieceView::slotOverlayTicks(int tickConfig)
0085 {
0086     m_tickConfig = tickConfig;
0087     if (tickConfig == 0)
0088         m_tickWarningLabel->setText(QString());
0089     else if (tickConfig == 1)
0090         m_tickWarningLabel->setText(i18n("Note: This overlay makes sense only if the view was generated in alt/az mode "
0091                                          "with a preset such as Refractor or Vanilla"));
0092     else if (tickConfig == 2)
0093         m_tickWarningLabel->setText(i18n("Note: This overlay  makes sense only if the view was generated in alt/az "
0094                                          "mode with a preset such as Dobsonian"));
0095     render();
0096 }
0097 
0098 void ExportEyepieceView::render()
0099 {
0100     float baseWidth  = m_renderChart->width();
0101     float baseHeight = m_renderChart->height();
0102 
0103     if (m_renderImage.get() != nullptr)
0104         m_output = QImage(int(baseWidth * 2.25), int(baseHeight),
0105                           QImage::Format_ARGB32); // 0.25 * baseWidth gap between the images
0106     else
0107         m_output = QImage(int(baseWidth), int(baseHeight), QImage::Format_ARGB32);
0108 
0109     m_output.fill(Qt::white);
0110     QPainter op(&m_output);
0111     op.drawPixmap(QPointF(0, 0), *m_renderChart);
0112     if (m_renderImage.get() != nullptr)
0113         op.drawPixmap(QPointF(baseWidth * 1.25, 0), *m_renderImage);
0114 
0115     if (m_tickConfig != 0 && Options::useAltAz()) // FIXME: this is very skymap-state-heavy for my happiness --asimha
0116     {
0117         // we must draw ticks
0118         QImage tickOverlay(baseWidth, baseHeight, QImage::Format_ARGB32);
0119         tickOverlay.fill(Qt::transparent);
0120 
0121         QPainter p(&tickOverlay);
0122         p.setPen(Qt::red); // FIXME: Determine color depending on situation, or make it configurable
0123         double rEnd   = 0.85 * (baseWidth / 2.);
0124         double rStart = 0.8 * (baseWidth / 2.);
0125         QFont font;
0126         font.setPixelSize((rEnd - rStart));
0127         p.setFont(font);
0128 
0129         GeoLocation *geo = KStarsData::Instance()->geo();
0130         double alt0      = m_sp->alt().Degrees(); // altitude when hour = 0, i.e. at m_dt (see below).
0131         dms northAngle0  = EyepieceField::findNorthAngle(m_sp.get(), geo->lat());
0132 
0133         for (float hour = -3.5; hour <= 3.5; hour += 0.5)
0134         {
0135             dms rotation; // rotation
0136 
0137             // FIXME: Code duplication : code duplicated from EyepieceField. This should really be a member of SkyPoint or something.
0138             SkyPoint sp    = SkyPoint::timeTransformed(m_sp.get(), m_dt, geo, hour);
0139             double alt     = sp.alt().Degrees();
0140             dms northAngle = EyepieceField::findNorthAngle(&sp, geo->lat());
0141 
0142             rotation = (northAngle - northAngle0);
0143             if (m_tickConfig == 2)
0144             {
0145                 // Dobsonian: add additional CW rotation by altitude, but compensate for the fact that we've already rotated by alt0
0146                 rotation = rotation - dms((alt - alt0));
0147             }
0148             rotation = rotation.reduce();
0149             p.save();
0150             p.translate(baseWidth / 2.0, baseHeight / 2.0);
0151             p.rotate(-(rotation.Degrees() + 90.0));
0152             p.drawLine(QPointF(rStart, 0), QPointF(rEnd, 0));
0153             QTime ct = geo->UTtoLT(m_dt.addSecs(3600.0 * hour)).time();
0154             p.drawText(QPointF(rEnd + 0.01 * baseWidth, 0), QString::asprintf("%02d:%02d", ct.hour(), ct.minute()));
0155             p.restore();
0156         }
0157         p.end();
0158         op.drawImage(QPointF(0, 0), tickOverlay);
0159         if (m_renderImage.get() != nullptr)
0160         {
0161             op.drawImage(QPointF(baseWidth * 1.25, 0), tickOverlay);
0162         }
0163     }
0164     op.end();
0165 
0166     m_outputDisplay->setPixmap((QPixmap::fromImage(m_output))
0167                                    .scaled(m_outputDisplay->width(), m_outputDisplay->height(), Qt::KeepAspectRatio,
0168                                            Qt::SmoothTransformation));
0169 }
0170 
0171 void ExportEyepieceView::slotSaveImage()
0172 {
0173     // does nothing at the moment. TODO: Implement.
0174     QString fileName = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save Image as"), QString(),
0175                                                     i18n("Image files (*.png *.jpg *.xpm *.bmp *.gif)"));
0176     if (!fileName.isEmpty())
0177     {
0178         m_output.save(fileName);
0179         slotCloseDialog();
0180     }
0181 }
0182 
0183 void ExportEyepieceView::slotCloseDialog()
0184 {
0185     hide();
0186     deleteLater();
0187 }