File indexing completed on 2024-09-15 09:10:24

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 martin Kuettler <martin.kuettler@gmail.com>
0004 */
0005 
0006 #include "imageentry.h"
0007 #include "actionbar.h"
0008 #include "worksheetimageitem.h"
0009 #include "worksheetview.h"
0010 #include "lib/jupyterutils.h"
0011 
0012 #include <QDir>
0013 #include <QMenu>
0014 #include <QFileSystemWatcher>
0015 #include <QJsonValue>
0016 #include <QJsonObject>
0017 #include <QStandardPaths>
0018 
0019 #include <KLocalizedString>
0020 #include <KZip>
0021 
0022 ImageEntry::ImageEntry(Worksheet* worksheet) : WorksheetEntry(worksheet)
0023 {
0024     m_imageItem = nullptr;
0025     m_textItem = new WorksheetTextItem(this);
0026     m_imageWatcher = new QFileSystemWatcher(this);
0027     m_displaySize.width = -1;
0028     m_displaySize.height = -1;
0029     m_printSize.width = -1;
0030     m_printSize.height = -1;
0031     m_displaySize.widthUnit = ImageSize::Auto;
0032     m_displaySize.heightUnit = ImageSize::Auto;
0033     m_printSize.widthUnit = ImageSize::Auto;
0034     m_printSize.heightUnit = ImageSize::Auto;
0035     m_useDisplaySizeForPrinting = true;
0036     connect(m_imageWatcher, &QFileSystemWatcher::fileChanged, this, &ImageEntry::updateEntry);
0037 
0038     setFlag(QGraphicsItem::ItemIsFocusable);
0039     updateEntry();
0040 }
0041 
0042 void ImageEntry::populateMenu(QMenu* menu, QPointF pos)
0043 {
0044     WorksheetEntry::populateMenu(menu, pos);
0045     auto* firstAction = menu->actions().at(0);
0046 
0047     auto* action = new QAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"));
0048     menu->insertAction(firstAction, action);
0049     connect(action, &QAction::triggered, this, &ImageEntry::startConfigDialog);
0050     menu->insertSeparator(firstAction);
0051 }
0052 
0053 bool ImageEntry::isEmpty()
0054 {
0055     return false;
0056 }
0057 
0058 int ImageEntry::type() const
0059 {
0060     return Type;
0061 }
0062 
0063 bool ImageEntry::acceptRichText()
0064 {
0065     return false;
0066 }
0067 
0068 void ImageEntry::setContent(const QString& content)
0069 {
0070     Q_UNUSED(content);
0071     return;
0072 }
0073 
0074 void ImageEntry::setContent(const QDomElement& content, const KZip& file)
0075 {
0076     Q_UNUSED(file);
0077 
0078     QDomElement fileName = content.firstChildElement(QLatin1String("FileName"));
0079     if (!fileName.isNull()) {
0080         m_fileName = fileName.text();
0081 
0082         const KArchiveEntry* imageEntry = file.directory()->entry(m_fileName);
0083         if (imageEntry && imageEntry->isFile())
0084         {
0085             const KArchiveFile* imageFile = static_cast<const KArchiveFile*>(imageEntry);
0086             const QString& dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0087             imageFile->copyTo(dir);
0088         }
0089     }
0090 
0091     static QStringList unitNames;
0092     if (unitNames.isEmpty())
0093         unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%");
0094 
0095     QDomElement pathElement = content.firstChildElement(QLatin1String("Path"));
0096     QDomElement displayElement = content.firstChildElement(QLatin1String("Display"));
0097     QDomElement printElement = content.firstChildElement(QLatin1String("Print"));
0098     m_imagePath = pathElement.text();
0099     m_displaySize.width = displayElement.attribute(QLatin1String("width")).toDouble();
0100     m_displaySize.height = displayElement.attribute(QLatin1String("height")).toDouble();
0101     m_displaySize.widthUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("widthUnit")));
0102     m_displaySize.heightUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("heightUnit")));
0103     m_useDisplaySizeForPrinting = printElement.attribute(QLatin1String("useDisplaySize")).toInt();
0104     m_printSize.width = printElement.attribute(QLatin1String("width")).toDouble();
0105     m_printSize.height = printElement.attribute(QLatin1String("height")).toDouble();
0106     m_printSize.widthUnit = unitNames.indexOf(printElement.attribute(QLatin1String("widthUnit")));
0107     m_printSize.heightUnit = unitNames.indexOf(printElement.attribute(QLatin1String("heightUnit")));
0108     updateEntry();
0109 }
0110 
0111 void ImageEntry::setContentFromJupyter(const QJsonObject& cell)
0112 {
0113     // No need use ImageEntry because without file this entry type are useless
0114     Q_UNUSED(cell);
0115     return;
0116 }
0117 
0118 QJsonValue ImageEntry::toJupyterJson()
0119 {
0120     QJsonValue value;
0121 
0122     if (!m_imagePath.isEmpty() && m_imageItem)
0123     {
0124         const QImage& image = m_imageItem->pixmap().toImage();
0125         if (!image.isNull())
0126         {
0127             QJsonObject entry;
0128             entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
0129 
0130             QJsonObject metadata;
0131             QJsonObject size;
0132             size.insert(QLatin1String("width"), image.size().width());
0133             size.insert(QLatin1String("height"), image.size().height());
0134             metadata.insert(Cantor::JupyterUtils::pngMime, size);
0135             entry.insert(Cantor::JupyterUtils::metadataKey, metadata);
0136 
0137             QString text(QLatin1String("<img src='attachment:image.png'>"));
0138 
0139             QJsonObject attachments;
0140             attachments.insert(QLatin1String("image.png"), Cantor::JupyterUtils::packMimeBundle(image, Cantor::JupyterUtils::pngMime));
0141             entry.insert(QLatin1String("attachments"), attachments);
0142 
0143             Cantor::JupyterUtils::setSource(entry, text);
0144 
0145             value = entry;
0146         }
0147     }
0148 
0149     return value;
0150 }
0151 
0152 QDomElement ImageEntry::toXml(QDomDocument& doc, KZip* archive)
0153 {
0154     Q_UNUSED(archive);
0155 
0156     static QStringList unitNames;
0157     if (unitNames.isEmpty())
0158         unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%");
0159 
0160     archive->addLocalFile(m_imagePath, QUrl::fromLocalFile(m_imagePath).fileName());
0161 
0162     QDomElement image = doc.createElement(QLatin1String("Image"));
0163     QDomElement path = doc.createElement(QLatin1String("Path"));
0164     QDomElement fileName = doc.createElement(QLatin1String("FileName"));
0165     QDomText pathText = doc.createTextNode(m_imagePath);
0166     QDomText fileNameText = doc.createTextNode(QUrl::fromLocalFile(m_imagePath).fileName());
0167     path.appendChild(pathText);
0168     fileName.appendChild(fileNameText);
0169     image.appendChild(fileName);
0170     image.appendChild(path);
0171 
0172     QDomElement display = doc.createElement(QLatin1String("Display"));
0173     display.setAttribute(QLatin1String("width"), m_displaySize.width);
0174     display.setAttribute(QLatin1String("widthUnit"), unitNames[m_displaySize.widthUnit]);
0175     display.setAttribute(QLatin1String("height"), m_displaySize.height);
0176     display.setAttribute(QLatin1String("heightUnit"), unitNames[m_displaySize.heightUnit]);
0177     image.appendChild(display);
0178 
0179     QDomElement print = doc.createElement(QLatin1String("Print"));
0180     print.setAttribute(QLatin1String("useDisplaySize"), m_useDisplaySizeForPrinting);
0181     print.setAttribute(QLatin1String("width"), m_printSize.width);
0182     print.setAttribute(QLatin1String("widthUnit"), unitNames[m_printSize.widthUnit]);
0183     print.setAttribute(QLatin1String("height"), m_printSize.height);
0184     print.setAttribute(QLatin1String("heightUnit"), unitNames[m_printSize.heightUnit]);
0185     image.appendChild(print);
0186 
0187     // For the conversion to latex
0188     QDomElement latexSize = doc.createElement(QLatin1String("LatexSizeString"));
0189     QString sizeString;
0190     if (m_useDisplaySizeForPrinting)
0191         sizeString = latexSizeString(m_displaySize);
0192     else
0193         sizeString = latexSizeString(m_printSize);
0194     QDomText latexSizeString = doc.createTextNode(sizeString);
0195     latexSize.appendChild(latexSizeString);
0196     image.appendChild(latexSize);
0197 
0198     return image;
0199 }
0200 
0201 QString ImageEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
0202 {
0203     Q_UNUSED(commandSep);
0204 
0205     return commentStartingSeq + QLatin1String("image: ") + m_imagePath  + commentEndingSeq;
0206 }
0207 
0208 QString ImageEntry::latexSizeString(const ImageSize& imgSize)
0209 {
0210     // We use the transformation 1 px = 1/72 in ( = 1 pt in Latex)
0211 
0212     QString sizeString=QLatin1String("");
0213     if (imgSize.widthUnit == ImageSize::Auto &&
0214         imgSize.heightUnit == ImageSize::Auto)
0215         return QLatin1String("");
0216 
0217     if (imgSize.widthUnit == ImageSize::Percent) {
0218         if (imgSize.heightUnit == ImageSize::Auto ||
0219             (imgSize.heightUnit == ImageSize::Percent &&
0220              imgSize.width == imgSize.height))
0221             return QLatin1String("[scale=") + QString::number(imgSize.width / 100) + QLatin1String("]");
0222         // else? We could set the size based on the actual image size
0223     } else if (imgSize.widthUnit == ImageSize::Auto &&
0224                imgSize.heightUnit == ImageSize::Percent) {
0225         return QLatin1String("[scale=") + QString::number(imgSize.height / 100) + QLatin1String("]");
0226     }
0227 
0228     if (imgSize.heightUnit == ImageSize::Pixel)
0229         sizeString = QLatin1String("height=") + QString::number(imgSize.height) + QLatin1String("pt");
0230     if (imgSize.widthUnit == ImageSize::Pixel) {
0231         if (!sizeString.isEmpty())
0232             sizeString += QLatin1String(",");
0233         sizeString += QLatin1String("width=") + QString::number(imgSize.width) + QLatin1String("pt");
0234     }
0235     return QLatin1String("[") + sizeString + QLatin1String("]");
0236 }
0237 
0238 bool ImageEntry::evaluate(EvaluationOption evalOp)
0239 {
0240     evaluateNext(evalOp);
0241     return true;
0242 }
0243 
0244 qreal ImageEntry::height()
0245 {
0246     if (m_imageItem && m_imageItem->isVisible())
0247         return m_imageItem->height();
0248     else
0249         return m_textItem->height();
0250 }
0251 
0252 void ImageEntry::updateEntry()
0253 {
0254     qreal oldHeight = height();
0255     if (m_imagePath.isEmpty()) {
0256         m_textItem->setPlainText(i18n("Double click here to configure image settings"));
0257         m_textItem->setVisible(true);
0258         if (m_imageItem)
0259             m_imageItem->setVisible(false);
0260     }
0261 
0262     else {
0263         if (!m_imageItem)
0264             m_imageItem = new WorksheetImageItem(this);
0265 
0266         // This if-else block was used for backward compability for *cws files
0267         // without FileName tag. After some releases from 20.08 version, it will
0268         // be possible to remove the else part and strip the m_imagePath from the
0269         // code and Path tag from the CWS format
0270         if (!m_fileName.isNull()) {
0271             QString imagePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + m_fileName;
0272             if (imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) {
0273                 m_imageItem->setEps(QUrl::fromLocalFile(imagePath));
0274             } else {
0275                 QImage img(imagePath);
0276                 m_imageItem->setImage(img);
0277             }
0278         } else {
0279             if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) {
0280                 m_imageItem->setEps(QUrl::fromLocalFile(m_imagePath));
0281             } else {
0282                 QImage img(m_imagePath);
0283                 m_imageItem->setImage(img);
0284             }
0285         }
0286 
0287         if (!m_imageItem->imageIsValid()) {
0288             const QString msg = i18n("Cannot load image %1", m_imagePath);
0289             m_textItem->setPlainText(msg);
0290             m_textItem->setVisible(true);
0291             m_imageItem->setVisible(false);
0292         } else {
0293             QSizeF size;
0294             if (worksheet()->isPrinting() && ! m_useDisplaySizeForPrinting)
0295                 size = imageSize(m_printSize);
0296             else
0297                 size = imageSize(m_displaySize);
0298             // Hack: Eps images need to be scaled
0299             if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive))
0300                 size /= worksheet()->renderer()->scale();
0301             m_imageItem->setSize(size);
0302             m_textItem->setVisible(false);
0303             m_imageItem->setVisible(true);
0304         }
0305     }
0306 
0307     if (oldHeight != height())
0308         recalculateSize();
0309 }
0310 
0311 QSizeF ImageEntry::imageSize(const ImageSize& imgSize)
0312 {
0313     const QSize& srcSize = m_imageItem->imageSize();
0314     qreal w = 0.0;
0315     qreal h = 0.0;
0316     if (imgSize.heightUnit == ImageSize::Percent)
0317         h = srcSize.height() * imgSize.height / 100;
0318     else if (imgSize.heightUnit == ImageSize::Pixel)
0319         h = imgSize.height;
0320     if (imgSize.widthUnit == ImageSize::Percent)
0321         w = srcSize.width() * imgSize.width / 100;
0322     else if (imgSize.widthUnit == ImageSize::Pixel)
0323         w = imgSize.width;
0324 
0325     if (imgSize.widthUnit == ImageSize::Auto) {
0326         if (imgSize.heightUnit == ImageSize::Auto)
0327             return QSizeF(srcSize.width(), srcSize.height());
0328         else if (h == 0)
0329             w = 0;
0330         else
0331             w = h / srcSize.height() * srcSize.width();
0332     } else if (imgSize.heightUnit == ImageSize::Auto) {
0333         if (w == 0)
0334             h = 0;
0335         else
0336             h = w / srcSize.width() * srcSize.height();
0337     }
0338 
0339     return QSizeF(w,h);
0340 }
0341 
0342 void ImageEntry::startConfigDialog()
0343 {
0344     ImageSettingsDialog* dialog = new ImageSettingsDialog(worksheet()->worksheetView());
0345     dialog->setData(m_imagePath, m_displaySize, m_printSize,
0346                     m_useDisplaySizeForPrinting);
0347     connect(dialog, &ImageSettingsDialog::dataChanged, this, &ImageEntry::setImageData);
0348     dialog->show();
0349 }
0350 
0351 void ImageEntry::setImageData(const QString& path,
0352                               const ImageSize& displaySize,
0353                               const ImageSize& printSize,
0354                               bool useDisplaySizeForPrinting)
0355 {
0356     if (path != m_imagePath) {
0357         m_imageWatcher->removePath(m_imagePath);
0358         m_imageWatcher->addPath(path);
0359         m_imagePath = path;
0360     }
0361 
0362     m_displaySize = displaySize;
0363     m_printSize = printSize;
0364     m_useDisplaySizeForPrinting = useDisplaySizeForPrinting;
0365 
0366     updateEntry();
0367 }
0368 
0369 void ImageEntry::addActionsToBar(ActionBar* actionBar)
0370 {
0371     actionBar->addButton(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"),
0372                          this, SLOT(startConfigDialog()));
0373 }
0374 
0375 void ImageEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force)
0376 {
0377     if (size().width() == w && m_textItem->pos().x() == entry_zone_x && !force)
0378         return;
0379 
0380     //TODO somethinkg wrong with geometry and control element: control element appears in wrong place
0381     const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
0382 
0383     double width;
0384     if (m_imageItem && m_imageItem->isVisible()) {
0385         m_imageItem->setGeometry(entry_zone_x, 0, w - margin - entry_zone_x, true);
0386         width = m_imageItem->width();
0387     } else {
0388         m_textItem->setGeometry(entry_zone_x, 0, w - margin - entry_zone_x, true);
0389         width = m_textItem->width();
0390     }
0391 
0392     setSize(QSizeF(width + margin + entry_zone_x, height() + VerticalMargin));
0393 }
0394 
0395 bool ImageEntry::wantToEvaluate()
0396 {
0397     return false;
0398 }
0399 
0400 bool ImageEntry::wantFocus()
0401 {
0402     return false;
0403 }
0404 
0405 void ImageEntry::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*)
0406 {
0407     startConfigDialog();
0408 }