File indexing completed on 2024-05-12 07:41:25

0001 /*
0002     File                 : GuiTools.cpp
0003     Project              : LabPlot
0004     Description          :  contains several static functions which are used on frequently throughout the kde frontend.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2011-2013 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "GuiTools.h"
0012 #include "backend/core/Settings.h"
0013 #include "backend/worksheet/plots/cartesian/Symbol.h"
0014 
0015 #include <KConfigGroup>
0016 #include <KLocalizedString>
0017 
0018 #include <QActionGroup>
0019 #include <QApplication>
0020 #include <QColor>
0021 #include <QComboBox>
0022 #include <QFileDialog>
0023 #include <QImageReader>
0024 #include <QLineEdit>
0025 #include <QMenu>
0026 #include <QPainter>
0027 #include <QScreen>
0028 
0029 #ifdef HAVE_POPPLER
0030 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0031 #include <poppler-qt6.h>
0032 #else
0033 #include <poppler-qt5.h>
0034 #endif
0035 #endif
0036 
0037 #include <array>
0038 
0039 static const int colorsCount = 26;
0040 static std::array<QColor, colorsCount> colors = {
0041     QColor(255, 255, 255), QColor(0, 0, 0),       QColor(192, 0, 0),     QColor(255, 0, 0), QColor(255, 192, 192), // red
0042     QColor(0, 192, 0),     QColor(0, 255, 0),     QColor(192, 255, 192), // green
0043     QColor(0, 0, 192),     QColor(0, 0, 255),     QColor(192, 192, 255), // blue
0044     QColor(192, 192, 0),   QColor(255, 255, 0),   QColor(255, 255, 192), // yellow
0045     QColor(0, 192, 192),   QColor(0, 255, 255),   QColor(192, 255, 255), // cyan
0046     QColor(192, 0, 192),   QColor(255, 0, 255),   QColor(255, 192, 255), // magenta
0047     QColor(192, 88, 0),    QColor(255, 128, 0),   QColor(255, 168, 88), // orange
0048     QColor(128, 128, 128), QColor(160, 160, 160), QColor(195, 195, 195) // grey
0049 };
0050 
0051 bool GuiTools::isDarkMode() {
0052     return (QApplication::palette().color(QPalette::Base).lightness() < 128);
0053 }
0054 
0055 /*!
0056     fills the ComboBox \c combobox with the six possible Qt::PenStyles, the color \c color is used.
0057 */
0058 void GuiTools::updatePenStyles(QComboBox* comboBox, const QColor& color) {
0059     int index = comboBox->currentIndex();
0060     comboBox->clear();
0061 
0062     QPainter pa;
0063     int offset = 2;
0064     int w = 50;
0065     int h = 10;
0066     QPixmap pm(w, h);
0067     comboBox->setIconSize(QSize(w, h));
0068 
0069     // loop over six possible Qt-PenStyles, draw on the pixmap and insert it
0070     // TODO: avoid copy-paste in all finctions!
0071     static std::array<QString, 6> list =
0072         {i18n("No Line"), i18n("Solid Line"), i18n("Dash Line"), i18n("Dot Line"), i18n("Dash-dot Line"), i18n("Dash-dot-dot Line")};
0073     for (int i = 0; i < 6; i++) {
0074         pm.fill(Qt::transparent);
0075         pa.begin(&pm);
0076         pa.setPen(QPen(color, 1, (Qt::PenStyle)i));
0077         pa.drawLine(offset, h / 2, w - offset, h / 2);
0078         pa.end();
0079         comboBox->addItem(QIcon(pm), list[i]);
0080     }
0081     comboBox->setCurrentIndex(index);
0082 }
0083 
0084 /*!
0085     fills the QMenu \c menu with the six possible Qt::PenStyles, the color \c color is used.
0086     QActions are created with \c actionGroup as the parent, if not available.
0087     If already available, onle the color in the QAction's icons is updated.
0088 */
0089 void GuiTools::updatePenStyles(QMenu* menu, QActionGroup* actionGroup, const QColor& color) {
0090     QPainter pa;
0091     int offset = 2;
0092     int w = 50;
0093     int h = 10;
0094     QPixmap pm(w, h);
0095 
0096     // loop over six possible Qt-PenStyles, draw on the pixmap and insert it
0097     static std::array<QString, 6> list =
0098         {i18n("No Line"), i18n("Solid Line"), i18n("Dash Line"), i18n("Dot Line"), i18n("Dash-dot Line"), i18n("Dash-dot-dot Line")};
0099 
0100     QAction* action;
0101     if (actionGroup->actions().isEmpty()) {
0102         // TODO setting of the icon size doesn't work here
0103         menu->setStyleSheet(QLatin1String("QMenu::icon { width:50px; height:10px; }"));
0104 
0105         for (int i = 0; i < 6; i++) {
0106             pm.fill(Qt::transparent);
0107             pa.begin(&pm);
0108             pa.setPen(QPen(color, 1, (Qt::PenStyle)i));
0109             pa.drawLine(offset, h / 2, w - offset, h / 2);
0110             pa.end();
0111             action = new QAction(QIcon(pm), list[i], actionGroup);
0112             action->setCheckable(true);
0113             menu->addAction(action);
0114         }
0115     } else {
0116         for (int i = 0; i < 6; i++) {
0117             pm.fill(Qt::transparent);
0118             pa.begin(&pm);
0119             pa.setPen(QPen(color, 1, (Qt::PenStyle)i));
0120             pa.drawLine(offset, h / 2, w - offset, h / 2);
0121             pa.end();
0122             action = actionGroup->actions().at(i);
0123             action->setIcon(QIcon(pm));
0124         }
0125     }
0126 }
0127 
0128 void GuiTools::selectPenStyleAction(QActionGroup* actionGroup, Qt::PenStyle style) {
0129     int index = (int)style;
0130     Q_ASSERT(index < actionGroup->actions().size());
0131     actionGroup->actions().at(index)->setChecked(true);
0132 }
0133 
0134 Qt::PenStyle GuiTools::penStyleFromAction(QActionGroup* actionGroup, QAction* action) {
0135     int index = actionGroup->actions().indexOf(action);
0136     return Qt::PenStyle(index);
0137 }
0138 
0139 /*!
0140     fills the ComboBox for the symbol filling patterns with the 14 possible Qt::BrushStyles.
0141 */
0142 void GuiTools::updateBrushStyles(QComboBox* comboBox, const QColor& color) {
0143     int index = comboBox->currentIndex();
0144     comboBox->clear();
0145 
0146     QPainter pa;
0147     int offset = 2;
0148     int w = 50;
0149     int h = 20;
0150     QPixmap pm(w, h);
0151     comboBox->setIconSize(QSize(w, h));
0152 
0153     QPen pen(Qt::SolidPattern, 1);
0154     pa.setPen(pen);
0155 
0156     static std::array<QString, 15> list = {i18n("None"),
0157                                            i18n("Uniform"),
0158                                            i18n("Extremely Dense"),
0159                                            i18n("Very Dense"),
0160                                            i18n("Somewhat Dense"),
0161                                            i18n("Half Dense"),
0162                                            i18n("Somewhat Sparse"),
0163                                            i18n("Very Sparse"),
0164                                            i18n("Extremely Sparse"),
0165                                            i18n("Horiz. Lines"),
0166                                            i18n("Vert. Lines"),
0167                                            i18n("Crossing Lines"),
0168                                            i18n("Backward Diag. Lines"),
0169                                            i18n("Forward Diag. Lines"),
0170                                            i18n("Crossing Diag. Lines")};
0171     const QColor& borderColor = GuiTools::isDarkMode() ? Qt::white : Qt::black;
0172     for (int i = 0; i < 15; i++) {
0173         pm.fill(Qt::transparent);
0174         pa.begin(&pm);
0175         pa.setPen(borderColor);
0176         pa.setRenderHint(QPainter::Antialiasing);
0177         pa.setBrush(QBrush(color, (Qt::BrushStyle)i));
0178         pa.drawRect(offset, offset, w - 2 * offset, h - 2 * offset);
0179         pa.end();
0180         comboBox->addItem(QIcon(pm), list[i]);
0181     }
0182 
0183     comboBox->setCurrentIndex(index);
0184 }
0185 
0186 void GuiTools::fillColorMenu(QMenu* menu, QActionGroup* actionGroup) {
0187     static const std::array<QString, colorsCount> colorNames = {
0188         i18n("White"),       i18n("Black"),        i18n("Dark Red"),   i18n("Red"),          i18n("Light Red"),   i18n("Dark Green"),    i18n("Green"),
0189         i18n("Light Green"), i18n("Dark Blue"),    i18n("Blue"),       i18n("Light Blue"),   i18n("Dark Yellow"), i18n("Yellow"),        i18n("Light Yellow"),
0190         i18n("Dark Cyan"),   i18n("Cyan"),         i18n("Light Cyan"), i18n("Dark Magenta"), i18n("Magenta"),     i18n("Light Magenta"), i18n("Dark Orange"),
0191         i18n("Orange"),      i18n("Light Orange"), i18n("Dark Grey"),  i18n("Grey"),         i18n("Light Grey")};
0192 
0193     QPixmap pix(16, 16);
0194     QPainter p(&pix);
0195     for (int i = 0; i < colorsCount; ++i) {
0196         p.fillRect(pix.rect(), colors[i]);
0197         auto* action = new QAction(QIcon(pix), colorNames[i], actionGroup);
0198         action->setCheckable(true);
0199         menu->addAction(action);
0200     }
0201 }
0202 
0203 /*!
0204  * Selects (checks) the action in the group \c actionGroup hat corresponds to the color \c color.
0205  * Unchecks the previously checked action if the color
0206  * was not found in the list of predefined colors.
0207  */
0208 void GuiTools::selectColorAction(QActionGroup* actionGroup, const QColor& color) {
0209     int index;
0210     for (index = 0; index < colorsCount; ++index) {
0211         if (color == colors[index]) {
0212             actionGroup->actions().at(index)->setChecked(true);
0213             break;
0214         }
0215     }
0216 
0217     if (index == colorsCount) {
0218         // the color was not found in the list of predefined colors
0219         //  -> uncheck the previously checked action
0220         QAction* checkedAction = actionGroup->checkedAction();
0221         if (checkedAction)
0222             checkedAction->setChecked(false);
0223     }
0224 }
0225 
0226 QColor& GuiTools::colorFromAction(QActionGroup* actionGroup, QAction* action) {
0227     int index = actionGroup->actions().indexOf(action);
0228     if (index == -1 || index >= colorsCount)
0229         index = 0;
0230 
0231     return colors[index];
0232 }
0233 
0234 // ComboBox with colors
0235 //  QImage img(16,16,QImage::Format_RGB32);
0236 //  QPainter p(&img);
0237 //  QRect rect = img.rect().adjusted(1,1,-1,-1);
0238 //  p.fillRect(rect, Qt::red);
0239 //  comboBox->setItemData(0, QPixmap::fromImage(img), Qt::DecorationRole);
0240 
0241 void GuiTools::highlight(QWidget* widget, bool invalid) {
0242     if (invalid)
0243         SET_WARNING_STYLE(widget)
0244     else
0245         widget->setStyleSheet(QString());
0246 }
0247 
0248 void GuiTools::addSymbolStyles(QComboBox* cb) {
0249     QPainter pa;
0250     QPen pen(Qt::SolidPattern, 0);
0251     const QColor& color = GuiTools::isDarkMode() ? Qt::white : Qt::black;
0252     pen.setColor(color);
0253     pa.setPen(pen);
0254 
0255     int iconSize = 20;
0256     QPixmap pm(iconSize, iconSize);
0257     cb->setIconSize(QSize(iconSize, iconSize));
0258     QTransform trafo;
0259     trafo.scale(15, 15);
0260 
0261     for (int i = 0; i < Symbol::stylesCount(); ++i) {
0262         // get styles in order
0263         const auto style = Symbol::indexToStyle(i);
0264         pm.fill(Qt::transparent);
0265         pa.begin(&pm);
0266         pa.setPen(pen);
0267         pa.setRenderHint(QPainter::Antialiasing);
0268         pa.translate(iconSize / 2, iconSize / 2);
0269         pa.drawPath(trafo.map(Symbol::stylePath(style)));
0270         pa.end();
0271         cb->addItem(QIcon(pm), Symbol::styleName(style), (int)style);
0272     }
0273 }
0274 
0275 QString GuiTools::openImageFile(const QString& className) {
0276     KConfigGroup conf = Settings::group(className);
0277     const QString& dir = conf.readEntry(QLatin1String("LastImageDir"), QString());
0278 
0279     QString formats;
0280     for (const QByteArray& format : QImageReader::supportedImageFormats()) {
0281         QString f = QLatin1String("*.") + QLatin1String(format.constData());
0282         if (f == QLatin1String("*.svg"))
0283             continue;
0284         formats += f + QLatin1Char(' ');
0285     }
0286 
0287     const QString& path = QFileDialog::getOpenFileName(nullptr, i18nc("@title:window", "Open Image File"), dir, i18n("Images (%1)", formats));
0288     if (!path.isEmpty()) {
0289         int pos = path.lastIndexOf(QLatin1String("/"));
0290         if (pos != -1) {
0291             QString newDir = path.left(pos);
0292             if (newDir != dir)
0293                 conf.writeEntry(QLatin1String("LastImageDir"), newDir);
0294         }
0295     }
0296 
0297     return path;
0298 }
0299 
0300 // convert PDF to QImage using Poppler
0301 QImage GuiTools::importPDFFile(const QString& fileName) {
0302     // DEBUG(Q_FUNC_INFO << ", PDF file name = " << STDSTRING(fileName));
0303 #ifdef HAVE_POPPLER
0304     auto* document = Poppler::Document::load(fileName);
0305     if (!document) {
0306         WARN("Failed to process PDF file" << STDSTRING(fileName));
0307         delete document;
0308         return {};
0309     }
0310 
0311     auto* page = document->page(0);
0312     if (!page) {
0313         WARN("Failed to process the first page in the PDF file.")
0314         delete document;
0315         return {};
0316     }
0317 
0318     document->setRenderHint(Poppler::Document::TextAntialiasing);
0319     document->setRenderHint(Poppler::Document::Antialiasing);
0320     document->setRenderHint(Poppler::Document::TextHinting);
0321     document->setRenderHint(Poppler::Document::TextSlightHinting);
0322     document->setRenderHint(Poppler::Document::ThinLineSolid);
0323 
0324     const static int dpi = QGuiApplication::primaryScreen()->logicalDotsPerInchX();
0325     QImage image = page->renderToImage(dpi, dpi);
0326 
0327     delete page;
0328     delete document;
0329 
0330     return image;
0331 #else
0332     Q_UNUSED(fileName)
0333     return {};
0334 #endif
0335 }
0336 
0337 QImage GuiTools::imageFromPDFData(const QByteArray& data, double zoomFactor) {
0338 #ifdef HAVE_POPPLER
0339     auto* document = Poppler::Document::loadFromData(data);
0340     if (!document) {
0341         WARN("Failed to process the byte array");
0342         delete document;
0343         return {};
0344     }
0345 
0346     auto* page = document->page(0);
0347     if (!page) {
0348         WARN("Failed to process the first page in the PDF file.")
0349         delete document;
0350         return {};
0351     }
0352 
0353     document->setRenderHint(Poppler::Document::TextAntialiasing);
0354     document->setRenderHint(Poppler::Document::Antialiasing);
0355     document->setRenderHint(Poppler::Document::TextHinting);
0356     document->setRenderHint(Poppler::Document::TextSlightHinting);
0357     document->setRenderHint(Poppler::Document::ThinLineSolid);
0358 
0359     const static int dpi = QGuiApplication::primaryScreen()->logicalDotsPerInchX();
0360     QImage image = page->renderToImage(zoomFactor * dpi, zoomFactor * dpi);
0361 
0362     delete page;
0363     delete document;
0364 
0365     return image;
0366 #else
0367     Q_UNUSED(data)
0368     Q_UNUSED(zoomFactor)
0369     return {};
0370 #endif
0371 }