File indexing completed on 2024-04-28 04:33:05

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Pino Toscano <pino@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "guiutils.h"
0008 
0009 // qt/kde includes
0010 #include <KLocalizedString>
0011 #include <KMessageBox>
0012 #include <QApplication>
0013 #include <QFileDialog>
0014 #include <QPainter>
0015 #include <QStandardPaths>
0016 #include <QTextDocument>
0017 
0018 // local includes
0019 #include "core/action.h"
0020 #include "core/annotations.h"
0021 #include "core/document.h"
0022 
0023 #include <memory>
0024 
0025 namespace GuiUtils
0026 {
0027 QString captionForAnnotation(const Okular::Annotation *ann)
0028 {
0029     Q_ASSERT(ann);
0030 
0031     const bool hasComment = !ann->contents().isEmpty();
0032 
0033     QString ret;
0034     switch (ann->subType()) {
0035     case Okular::Annotation::AText:
0036         if (((Okular::TextAnnotation *)ann)->textType() == Okular::TextAnnotation::Linked) {
0037             ret = i18n("Pop-up Note");
0038         } else {
0039             if (((Okular::TextAnnotation *)ann)->inplaceIntent() == Okular::TextAnnotation::TypeWriter) {
0040                 ret = i18n("Typewriter");
0041             } else {
0042                 ret = i18n("Inline Note");
0043             }
0044         }
0045         break;
0046     case Okular::Annotation::ALine:
0047         if (((Okular::LineAnnotation *)ann)->linePoints().count() == 2) {
0048             ret = hasComment ? i18n("Straight Line with Comment") : i18n("Straight Line");
0049         } else {
0050             ret = hasComment ? i18n("Polygon with Comment") : i18n("Polygon");
0051         }
0052         break;
0053     case Okular::Annotation::AGeom:
0054         ret = hasComment ? i18n("Geometry with Comment") : i18n("Geometry");
0055         break;
0056     case Okular::Annotation::AHighlight:
0057         switch (((Okular::HighlightAnnotation *)ann)->highlightType()) {
0058         case Okular::HighlightAnnotation::Highlight:
0059             ret = hasComment ? i18n("Highlight with Comment") : i18n("Highlight");
0060             break;
0061         case Okular::HighlightAnnotation::Squiggly:
0062             ret = hasComment ? i18n("Squiggle with Comment") : i18n("Squiggle");
0063             break;
0064         case Okular::HighlightAnnotation::Underline:
0065             ret = hasComment ? i18n("Underline with Comment") : i18n("Underline");
0066             break;
0067         case Okular::HighlightAnnotation::StrikeOut:
0068             ret = hasComment ? i18n("Strike Out with Comment") : i18n("Strike Out");
0069             break;
0070         }
0071         break;
0072     case Okular::Annotation::AStamp:
0073         ret = hasComment ? i18n("Stamp with Comment") : i18n("Stamp");
0074         break;
0075     case Okular::Annotation::AInk:
0076         ret = hasComment ? i18n("Freehand Line with Comment") : i18n("Freehand Line");
0077         break;
0078     case Okular::Annotation::ACaret:
0079         ret = i18n("Caret");
0080         break;
0081     case Okular::Annotation::AFileAttachment:
0082         ret = i18n("File Attachment");
0083         break;
0084     case Okular::Annotation::ASound:
0085         ret = i18n("Sound");
0086         break;
0087     case Okular::Annotation::AMovie:
0088         ret = i18n("Movie");
0089         break;
0090     case Okular::Annotation::AScreen:
0091         ret = i18nc("Caption for a screen annotation", "Screen");
0092         break;
0093     case Okular::Annotation::AWidget:
0094         ret = i18nc("Caption for a widget annotation", "Widget");
0095         break;
0096     case Okular::Annotation::ARichMedia:
0097         ret = i18nc("Caption for a rich media annotation", "Rich Media");
0098         break;
0099     case Okular::Annotation::A_BASE:
0100         break;
0101     }
0102     return ret;
0103 }
0104 
0105 QString authorForAnnotation(const Okular::Annotation *ann)
0106 {
0107     Q_ASSERT(ann);
0108 
0109     return !ann->author().isEmpty() ? ann->author() : i18nc("Unknown author", "Unknown");
0110 }
0111 
0112 QString contentsHtml(const Okular::Annotation *ann)
0113 {
0114     QString text = ann->contents().toHtmlEscaped();
0115     text.replace(QLatin1Char('\n'), QLatin1String("<br>"));
0116     return text;
0117 }
0118 
0119 QString prettyToolTip(const Okular::Annotation *ann)
0120 {
0121     Q_ASSERT(ann);
0122 
0123     QString author = authorForAnnotation(ann);
0124     QString contents = contentsHtml(ann);
0125 
0126     QString tooltip = QStringLiteral("<qt><b>") + i18n("Author: %1", author) + QStringLiteral("</b>");
0127     if (!contents.isEmpty()) {
0128         tooltip += QStringLiteral("<div style=\"font-size: 4px;\"><hr /></div>") + contents;
0129     }
0130 
0131     tooltip += QLatin1String("</qt>");
0132 
0133     return tooltip;
0134 }
0135 
0136 void saveEmbeddedFile(Okular::EmbeddedFile *ef, QWidget *parent)
0137 {
0138     const QString caption = i18n("Where do you want to save %1?", ef->name());
0139     const QString path = QFileDialog::getSaveFileName(parent, caption, ef->name());
0140     if (path.isEmpty()) {
0141         return;
0142     }
0143     QFile targetFile(path);
0144     writeEmbeddedFile(ef, parent, targetFile);
0145 }
0146 
0147 void writeEmbeddedFile(Okular::EmbeddedFile *ef, QWidget *parent, QFile &target)
0148 {
0149     if (!target.open(QIODevice::WriteOnly)) {
0150         KMessageBox::error(parent, i18n("Could not open \"%1\" for writing. File was not saved.", target.fileName()));
0151         return;
0152     }
0153     target.write(ef->data());
0154     target.close();
0155 }
0156 
0157 Okular::Movie *renditionMovieFromScreenAnnotation(const Okular::ScreenAnnotation *annotation)
0158 {
0159     if (!annotation) {
0160         return nullptr;
0161     }
0162 
0163     if (annotation->action() && annotation->action()->actionType() == Okular::Action::Rendition) {
0164         Okular::RenditionAction *renditionAction = static_cast<Okular::RenditionAction *>(annotation->action());
0165         return renditionAction->movie();
0166     }
0167 
0168     return nullptr;
0169 }
0170 
0171 // from Arthur - qt4
0172 static inline int qt_div_255(int x)
0173 {
0174     return (x + (x >> 8) + 0x80) >> 8;
0175 }
0176 
0177 void colorizeImage(QImage &grayImage, const QColor &color, unsigned int destAlpha)
0178 {
0179     // Make sure that the image is Format_ARGB32_Premultiplied
0180     if (grayImage.format() != QImage::Format_ARGB32_Premultiplied) {
0181         grayImage = grayImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
0182     }
0183 
0184     // iterate over all pixels changing the alpha component value
0185     unsigned int *data = reinterpret_cast<unsigned int *>(grayImage.bits());
0186     unsigned int pixels = grayImage.width() * grayImage.height();
0187     int red = color.red(), green = color.green(), blue = color.blue();
0188 
0189     for (unsigned int i = 0; i < pixels; ++i) { // optimize this loop keeping byte order into account
0190         int source = data[i];
0191         int sourceSat = qRed(source);
0192         int newR = qt_div_255(sourceSat * red), newG = qt_div_255(sourceSat * green), newB = qt_div_255(sourceSat * blue);
0193         if (int sourceAlpha = qAlpha(source); sourceAlpha == 255) {
0194             // use destAlpha
0195             data[i] = qRgba(newR, newG, newB, destAlpha);
0196         } else {
0197             // use destAlpha * sourceAlpha product
0198             if (destAlpha < 255) {
0199                 sourceAlpha = qt_div_255(destAlpha * sourceAlpha);
0200             }
0201             data[i] = qRgba(newR, newG, newB, sourceAlpha);
0202         }
0203     }
0204 }
0205 
0206 QIcon createColorIcon(const QList<QColor> &colors, const QIcon &background, ColorIconFlags flags)
0207 {
0208     QIcon colorIcon;
0209 
0210     // Create a pixmap for each common icon size.
0211     for (int size : {16, 22, 24, 32, 48}) {
0212         QPixmap pixmap(QSize(size, size) * qApp->devicePixelRatio());
0213         pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
0214         pixmap.fill(Qt::transparent);
0215         QPainter painter(&pixmap);
0216         // Configure hairlines for visualization of outline or transparency (visualizeTransparent):
0217         painter.setPen(QPen(qApp->palette().color(QPalette::Active, QPalette::WindowText), 0));
0218         painter.setBrush(Qt::NoBrush);
0219 
0220         if (background.isNull()) {
0221             // Full-size color rectangles.
0222             // Draw rectangles left to right:
0223             for (int i = 0; i < colors.count(); ++i) {
0224                 if (!colors.at(i).isValid()) {
0225                     continue;
0226                 }
0227                 QRect rect(QPoint(size * i / colors.count(), 0), QPoint(size * (i + 1) / colors.count(), size));
0228                 if ((flags & VisualizeTransparent) && (colors.at(i) == Qt::transparent)) {
0229                     painter.drawLine(rect.topLeft(), rect.bottomRight());
0230                     painter.drawLine(rect.bottomLeft(), rect.topRight());
0231                 } else {
0232                     painter.fillRect(rect, colors.at(i));
0233                 }
0234             }
0235 
0236             // Draw hairline outline:
0237             // To get the hairline on the outermost pixels, we shrink the rectangle by a half pixel on each edge.
0238             const qreal halfPixelWidth = 0.5 / pixmap.devicePixelRatio();
0239             painter.drawRect(QRectF(QPointF(halfPixelWidth, halfPixelWidth), QPointF(qreal(size) - halfPixelWidth, qreal(size) - halfPixelWidth)));
0240         } else {
0241             // Lower 25% color rectangles.
0242             // Draw background icon:
0243             background.paint(&painter, QRect(QPoint(0, 0), QSize(size, size)));
0244 
0245             // Draw rectangles left to right:
0246             for (int i = 0; i < colors.count(); ++i) {
0247                 if (!colors.at(i).isValid()) {
0248                     continue;
0249                 }
0250                 QRect rect(QPoint(size * i / colors.count(), size * 3 / 4), QPoint(size * (i + 1) / colors.count(), size));
0251                 if ((flags & VisualizeTransparent) && (colors.at(i) == Qt::transparent)) {
0252                     painter.drawLine(rect.topLeft(), rect.bottomRight());
0253                     painter.drawLine(rect.bottomLeft(), rect.topRight());
0254                 } else {
0255                     painter.fillRect(rect, colors.at(i));
0256                 }
0257             }
0258         }
0259 
0260         painter.end();
0261         colorIcon.addPixmap(pixmap);
0262     }
0263 
0264     return colorIcon;
0265 }
0266 
0267 QIcon createOpacityIcon(qreal opacity)
0268 {
0269     QIcon opacityIcon;
0270 
0271     // Create a pixmap for each common icon size.
0272     for (int size : {16, 22, 24, 32, 48}) {
0273         QPixmap pixmap(QSize(size, size) * qApp->devicePixelRatio());
0274         pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
0275         pixmap.fill(Qt::transparent);
0276         QPainter painter(&pixmap);
0277         painter.setPen(Qt::NoPen);
0278         painter.setBrush(qApp->palette().color(QPalette::Active, QPalette::WindowText));
0279 
0280         // Checkerboard pattern
0281         painter.drawRect(QRectF(QPoint(0, 0), QPoint(size, size) / 2));
0282         painter.drawRect(QRectF(QPoint(size, size) / 2, QPoint(size, size)));
0283 
0284         // Opacity
0285         painter.setOpacity(opacity);
0286         painter.drawRect(QRect(QPoint(0, 0), QPoint(size, size)));
0287 
0288         painter.end();
0289         opacityIcon.addPixmap(pixmap);
0290     }
0291 
0292     return opacityIcon;
0293 }
0294 
0295 }