File indexing completed on 2024-04-14 14:08:55

0001 /*
0002     SPDX-FileCopyrightText: 2001 Thomas Kabelmann <tk78@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "imageviewer.h"
0008 #include "Options.h"
0009 
0010 #ifndef KSTARS_LITE
0011 #include "kstars.h"
0012 #include <KMessageBox>
0013 #include <KFormat>
0014 #endif
0015 
0016 #include "ksnotification.h"
0017 #include <QFileDialog>
0018 #include <QJsonObject>
0019 #include <QPainter>
0020 #include <QResizeEvent>
0021 #include <QScreen>
0022 #include <QStatusBar>
0023 #include <QTemporaryFile>
0024 #include <QVBoxLayout>
0025 #include <QPushButton>
0026 #include <QApplication>
0027 
0028 QUrl ImageViewer::lastURL = QUrl::fromLocalFile(QDir::homePath());
0029 
0030 ImageLabel::ImageLabel(QWidget *parent) : QFrame(parent)
0031 {
0032 #ifndef KSTARS_LITE
0033     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0034     setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
0035     setLineWidth(2);
0036 #endif
0037 }
0038 
0039 void ImageLabel::setImage(const QImage &img)
0040 {
0041 #ifndef KSTARS_LITE
0042     m_Image = img;
0043     pix     = QPixmap::fromImage(m_Image);
0044 #endif
0045 }
0046 
0047 void ImageLabel::invertPixels()
0048 {
0049 #ifndef KSTARS_LITE
0050     m_Image.invertPixels();
0051     pix = QPixmap::fromImage(m_Image.scaled(width(), height(), Qt::KeepAspectRatio));
0052 #endif
0053 }
0054 
0055 void ImageLabel::paintEvent(QPaintEvent *)
0056 {
0057 #ifndef KSTARS_LITE
0058     QPainter p;
0059     p.begin(this);
0060     int x = 0;
0061     if (pix.width() < width())
0062         x = (width() - pix.width()) / 2;
0063     p.drawPixmap(x, 0, pix);
0064     p.end();
0065 #endif
0066 }
0067 
0068 void ImageLabel::resizeEvent(QResizeEvent *event)
0069 {
0070     int w = pix.width();
0071     int h = pix.height();
0072 
0073     if (event->size().width() == w && event->size().height() == h)
0074         return;
0075 
0076     pix = QPixmap::fromImage(m_Image.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
0077 }
0078 
0079 ImageViewer::ImageViewer(const QString &caption, QWidget *parent) : QDialog(parent), fileIsImage(false), downloadJob(nullptr)
0080 {
0081 #ifndef KSTARS_LITE
0082     init(caption, QString());
0083 #endif
0084 }
0085 
0086 ImageViewer::ImageViewer(const QUrl &url, const QString &capText, QWidget *parent)
0087     : QDialog(parent), m_ImageUrl(url)
0088 {
0089 #ifndef KSTARS_LITE
0090     init(url.fileName(), capText);
0091 
0092     // check URL
0093     if (!m_ImageUrl.isValid())
0094         qDebug() << Q_FUNC_INFO << "URL is malformed: " << m_ImageUrl;
0095 
0096     if (m_ImageUrl.isLocalFile())
0097     {
0098         loadImage(m_ImageUrl.toLocalFile());
0099         return;
0100     }
0101 
0102     {
0103         QTemporaryFile tempfile;
0104         tempfile.open();
0105         file.setFileName(tempfile.fileName());
0106     } // we just need the name and delete the tempfile from disc; if we don't do it, a dialog will be show
0107 
0108     loadImageFromURL();
0109 #endif
0110 }
0111 
0112 void ImageViewer::init(QString caption, QString capText)
0113 {
0114 #ifndef KSTARS_LITE
0115     setAttribute(Qt::WA_DeleteOnClose, true);
0116     setModal(false);
0117     setWindowTitle(i18nc("@title:window", "KStars image viewer: %1", caption));
0118 
0119     // Create widget
0120     QFrame *page = new QFrame(this);
0121 
0122     //setMainWidget( page );
0123     QVBoxLayout *mainLayout = new QVBoxLayout;
0124     mainLayout->addWidget(page);
0125     setLayout(mainLayout);
0126 
0127     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0128     mainLayout->addWidget(buttonBox);
0129     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0130 
0131     QPushButton *invertB = new QPushButton(i18n("Invert colors"));
0132     invertB->setToolTip(i18n("Reverse colors of the image. This is useful to enhance contrast at times. This affects "
0133                              "only the display and not the saving."));
0134     QPushButton *saveB = new QPushButton(QIcon::fromTheme("document-save"), i18n("Save"));
0135     saveB->setToolTip(i18n("Save the image to disk"));
0136 
0137     buttonBox->addButton(invertB, QDialogButtonBox::ActionRole);
0138     buttonBox->addButton(saveB, QDialogButtonBox::ActionRole);
0139 
0140     connect(invertB, SIGNAL(clicked()), this, SLOT(invertColors()));
0141     connect(saveB, SIGNAL(clicked()), this, SLOT(saveFileToDisc()));
0142 
0143     m_View = new ImageLabel(page);
0144     m_View->setAutoFillBackground(true);
0145     m_Caption = new QLabel(page);
0146     m_Caption->setAutoFillBackground(true);
0147     m_Caption->setFrameShape(QFrame::StyledPanel);
0148     m_Caption->setText(capText);
0149     // Add layout
0150     QVBoxLayout *vlay = new QVBoxLayout(page);
0151     vlay->setSpacing(0);
0152     vlay->setContentsMargins(0, 0, 0, 0);
0153     vlay->addWidget(m_View);
0154     vlay->addWidget(m_Caption);
0155 
0156     //Reverse colors
0157     QPalette p = palette();
0158     p.setColor(QPalette::Window, palette().color(QPalette::WindowText));
0159     p.setColor(QPalette::WindowText, palette().color(QPalette::Window));
0160     m_Caption->setPalette(p);
0161     m_View->setPalette(p);
0162 
0163     //If the caption is wider than the image, try to shrink the font a bit
0164     QFont capFont = m_Caption->font();
0165     capFont.setPointSize(capFont.pointSize() - 2);
0166     m_Caption->setFont(capFont);
0167 #endif
0168 }
0169 
0170 ImageViewer::~ImageViewer()
0171 {
0172     QString filename = file.fileName();
0173     if (filename.startsWith(QLatin1String("/tmp/")) || filename.contains("/Temp"))
0174     {
0175         if (m_ImageUrl.isEmpty() == false ||
0176                 KMessageBox::questionYesNo(nullptr, i18n("Remove temporary file %1 from disk?", filename),
0177                                            i18n("Confirm Removal"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
0178                                            "imageviewer_temporary_file_removal") == KMessageBox::Yes)
0179             QFile::remove(filename);
0180     }
0181 
0182     QApplication::restoreOverrideCursor();
0183 }
0184 
0185 void ImageViewer::loadImageFromURL()
0186 {
0187 #ifndef KSTARS_LITE
0188     QUrl saveURL = QUrl::fromLocalFile(file.fileName());
0189 
0190     if (!saveURL.isValid())
0191         qWarning() << "tempfile-URL is malformed";
0192 
0193     QApplication::setOverrideCursor(Qt::WaitCursor);
0194 
0195     downloadJob.setProgressDialogEnabled(true, i18n("Download"),
0196                                          i18n("Please wait while image is being downloaded..."));
0197     connect(&downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
0198     connect(&downloadJob, SIGNAL(canceled()), this, SLOT(close()));
0199     connect(&downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
0200 
0201     downloadJob.get(m_ImageUrl);
0202 #endif
0203 }
0204 
0205 void ImageViewer::downloadReady()
0206 {
0207 #ifndef KSTARS_LITE
0208     QApplication::restoreOverrideCursor();
0209 
0210     if (file.open(QFile::WriteOnly))
0211     {
0212         file.write(downloadJob.downloadedData());
0213         file.close(); // to get the newest information from the file and not any information from opening of the file
0214 
0215         if (file.exists())
0216         {
0217             showImage();
0218             return;
0219         }
0220 
0221         close();
0222     }
0223     else
0224         KSNotification::error(file.errorString(), i18n("Image Viewer"));
0225 #endif
0226 }
0227 
0228 void ImageViewer::downloadError(const QString &errorString)
0229 {
0230 #ifndef KSTARS_LITE
0231     QApplication::restoreOverrideCursor();
0232     KSNotification::error(errorString);
0233 #endif
0234 }
0235 
0236 bool ImageViewer::loadImage(const QString &filename)
0237 {
0238 #ifndef KSTARS_LITE
0239     // If current file is temporary, remove from disk.
0240     if (file.fileName().startsWith(QLatin1String("/tmp/")) || file.fileName().contains("/Temp"))
0241         QFile::remove(file.fileName());
0242 
0243     file.setFileName(filename);
0244     return showImage();
0245 #else
0246     return false;
0247 #endif
0248 }
0249 
0250 bool ImageViewer::showImage()
0251 {
0252 #ifndef KSTARS_LITE
0253     QImage image;
0254 
0255     if (!image.load(file.fileName()))
0256     {
0257         KSNotification::error(i18n("Loading of the image %1 failed.", m_ImageUrl.url()));
0258         close();
0259         return false;
0260     }
0261 
0262     fileIsImage = true; // we loaded the file and know now, that it is an image
0263 
0264     //If the image is larger than screen width and/or screen height,
0265     //shrink it to fit the screen
0266     QRect deskRect = QGuiApplication::primaryScreen()->geometry();
0267     int w          = deskRect.width();  // screen width
0268     int h          = deskRect.height(); // screen height
0269 
0270     if (image.width() <= w && image.height() > h) //Window is taller than desktop
0271         image = image.scaled(int(image.width() * h / image.height()), h);
0272     else if (image.height() <= h && image.width() > w) //window is wider than desktop
0273         image = image.scaled(w, int(image.height() * w / image.width()));
0274     else if (image.width() > w && image.height() > h) //window is too tall and too wide
0275     {
0276         //which needs to be shrunk least, width or height?
0277         float fx = float(w) / float(image.width());
0278         float fy = float(h) / float(image.height());
0279         if (fx > fy) //width needs to be shrunk less, so shrink to fit in height
0280             image = image.scaled(int(image.width() * fy), h);
0281         else //vice versa
0282             image = image.scaled(w, int(image.height() * fx));
0283     }
0284 
0285     show(); // hide is default
0286 
0287     m_View->setImage(image);
0288     w = image.width();
0289 
0290     //If the caption is wider than the image, set the window size
0291     //to fit the caption
0292     if (m_Caption->width() > w)
0293         w = m_Caption->width();
0294     //setFixedSize( w, image.height() + m_Caption->height() );
0295 
0296     resize(w, image.height());
0297     update();
0298     show();
0299 
0300     return true;
0301 #else
0302     return false;
0303 #endif
0304 }
0305 
0306 void ImageViewer::saveFileToDisc()
0307 {
0308 #ifndef KSTARS_LITE
0309     QFileDialog dialog;
0310 
0311     QUrl newURL =
0312         dialog.getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save Image"), lastURL); // save-dialog with default filename
0313     if (!newURL.isEmpty())
0314     {
0315         //QFile f (newURL.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).toLocalFile() + '/' +  newURL.fileName());
0316         QFile f(newURL.toLocalFile());
0317         if (f.exists())
0318         {
0319             if ((KMessageBox::warningContinueCancel(static_cast<QWidget *>(parent()),
0320                                                     i18n("A file named \"%1\" already exists. "
0321                                                             "Overwrite it?",
0322                                                             newURL.fileName()),
0323                                                     i18n("Overwrite File?"), KStandardGuiItem::overwrite()) == KMessageBox::Cancel))
0324                 return;
0325 
0326             f.remove();
0327         }
0328 
0329         lastURL = QUrl(newURL.toString(QUrl::RemoveFilename));
0330 
0331         saveFile(newURL);
0332     }
0333 #endif
0334 }
0335 
0336 void ImageViewer::saveFile(QUrl &url)
0337 {
0338     // synchronous access to prevent segfaults
0339 
0340     //if (!KIO::NetAccess::file_copy (QUrl (file.fileName()), url, (QWidget*) 0))
0341     //QUrl tmpURL((file.fileName()));
0342     //tmpURL.setScheme("file");
0343 
0344     if (file.copy(url.toLocalFile()) == false)
0345         //if (KIO::file_copy(tmpURL, url)->exec() == false)
0346     {
0347         QString text = i18n("Saving of the image %1 failed.", url.toString());
0348 #ifndef KSTARS_LITE
0349         KSNotification::error(text);
0350 #else
0351         qDebug() << Q_FUNC_INFO << text;
0352 #endif
0353     }
0354 #ifndef KSTARS_LITE
0355     else
0356         KStars::Instance()->statusBar()->showMessage(i18n("Saved image to %1", url.toString()));
0357 #endif
0358 }
0359 
0360 void ImageViewer::invertColors()
0361 {
0362 #ifndef KSTARS_LITE
0363     // Invert colors
0364     m_View->invertPixels();
0365     m_View->update();
0366 #endif
0367 }
0368 
0369 QJsonObject ImageViewer::metadata()
0370 {
0371 #ifndef KSTARS_LITE
0372     QJsonObject md;
0373     if (m_View && !m_View->m_Image.isNull())
0374     {
0375         md.insert("resolution", QString("%1x%2").arg(m_View->m_Image.width()).arg(m_View->m_Image.height()));
0376         // sizeInBytes is only available in 5.10+
0377         md.insert("size", KFormat().formatByteSize(m_View->m_Image.bytesPerLine() * m_View->m_Image.height()));
0378         md.insert("bin", "1x1");
0379         md.insert("bpp", QString::number(m_View->m_Image.bytesPerLine() / m_View->m_Image.width() * 8));
0380     }
0381     return md;
0382 #endif
0383 }