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 }