File indexing completed on 2024-04-28 15:51:38

0001 /*
0002     SPDX-FileCopyrightText: 2019 Simone Gaiarin <simgunz@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "annotationactionhandler.h"
0008 
0009 // qt includes
0010 #include <QActionGroup>
0011 #include <QBitmap>
0012 #include <QColorDialog>
0013 #include <QFileInfo>
0014 #include <QFontDialog>
0015 #include <QMenu>
0016 #include <QPainter>
0017 #include <QPen>
0018 
0019 // kde includes
0020 #include <KActionCollection>
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 #include <KParts/MainWindow>
0024 #include <KSelectAction>
0025 #include <KToolBar>
0026 #include <kwidgetsaddons_version.h>
0027 
0028 // local includes
0029 #include "actionbar.h"
0030 #include "annotationwidgets.h"
0031 #include "core/annotations.h"
0032 #include "gui/guiutils.h"
0033 #include "pageview.h"
0034 #include "pageviewannotator.h"
0035 #include "settings.h"
0036 #include "toggleactionmenu.h"
0037 
0038 class AnnotationActionHandlerPrivate
0039 {
0040 public:
0041     enum class AnnotationColor { Color, InnerColor };
0042     static const QList<QPair<KLocalizedString, QColor>> defaultColors;
0043     static const QList<double> widthStandardValues;
0044     static const QList<double> opacityStandardValues;
0045 
0046     explicit AnnotationActionHandlerPrivate(AnnotationActionHandler *qq)
0047         : q(qq)
0048         , annotator(nullptr)
0049         , agTools(nullptr)
0050         , agLastAction(nullptr)
0051         , aQuickTools(nullptr)
0052         , aQuickToolsBar(nullptr)
0053         , aGeomShapes(nullptr)
0054         , aStamp(nullptr)
0055         , aAddToQuickTools(nullptr)
0056         , aContinuousMode(nullptr)
0057         , aConstrainRatioAndAngle(nullptr)
0058         , aWidth(nullptr)
0059         , aColor(nullptr)
0060         , aInnerColor(nullptr)
0061         , aOpacity(nullptr)
0062         , aFont(nullptr)
0063         , aAdvancedSettings(nullptr)
0064         , aHideToolBar(nullptr)
0065         , aShowToolBar(nullptr)
0066         , aToolBarVisibility(nullptr)
0067         , aCustomStamp(nullptr)
0068         , aCustomWidth(nullptr)
0069         , aCustomOpacity(nullptr)
0070         , currentColor(QColor())
0071         , currentInnerColor(QColor())
0072         , currentFont(QFont())
0073         , currentWidth(-1)
0074         , selectedBuiltinTool(-1)
0075         , textToolsEnabled(false)
0076     {
0077     }
0078 
0079     QAction *selectActionItem(KSelectAction *aList, QAction *aCustomCurrent, double value, const QList<double> &defaultValues, const QIcon &icon, const QString &label);
0080 
0081     /**
0082      * @short Adds a custom stamp annotation action to the stamp list when the stamp is not a default stamp
0083      *
0084      * When @p stampIconName cannot be found among the default stamps, this method creates a new action
0085      * for the custom stamp annotation and adds it to the stamp action combo box.
0086      * If a custom action is already present in the list, it is removed before adding the new custom action.
0087      * If @p stampIconName matches a default stamp, any existing custom stamp annotation action is removed.
0088      */
0089     void maybeUpdateCustomStampAction(const QString &stampIconName);
0090     void parseTool(int toolId);
0091 
0092     void updateConfigActions(const QString &annotType = QLatin1String(""));
0093     void populateQuickAnnotations();
0094     KSelectAction *colorPickerAction(AnnotationColor colorType);
0095 
0096     const QIcon widthIcon(double width);
0097     const QIcon stampIcon(const QString &stampIconName);
0098 
0099     void selectTool(int toolId);
0100     void slotStampToolSelected(const QString &stamp);
0101     void slotQuickToolSelected(int favToolId);
0102     void slotSetColor(AnnotationColor colorType, const QColor &color = QColor());
0103     void slotSelectAnnotationFont();
0104     bool isQuickToolAction(QAction *aTool);
0105     bool isQuickToolStamp(int toolId);
0106     void assertToolBarExists(KParts::MainWindow *mw, const QString &toolBarName);
0107 
0108     AnnotationActionHandler *q;
0109 
0110     PageViewAnnotator *annotator;
0111 
0112     QList<QAction *> quickTools;
0113     QList<QAction *> textTools;
0114     QList<QAction *> textQuickTools;
0115     QActionGroup *agTools;
0116     QAction *agLastAction;
0117 
0118     ToggleActionMenu *aQuickTools;
0119     ActionBar *aQuickToolsBar;
0120     ToggleActionMenu *aGeomShapes;
0121     ToggleActionMenu *aStamp;
0122     QAction *aAddToQuickTools;
0123     KToggleAction *aContinuousMode;
0124     KToggleAction *aConstrainRatioAndAngle;
0125     KSelectAction *aWidth;
0126     KSelectAction *aColor;
0127     KSelectAction *aInnerColor;
0128     KSelectAction *aOpacity;
0129     QAction *aFont;
0130     QAction *aAdvancedSettings;
0131     QAction *aHideToolBar;
0132     QAction *aShowToolBar;
0133     KToggleAction *aToolBarVisibility;
0134 
0135     QAction *aCustomStamp;
0136     QAction *aCustomWidth;
0137     QAction *aCustomOpacity;
0138 
0139     QColor currentColor;
0140     QColor currentInnerColor;
0141     QFont currentFont;
0142     int currentWidth;
0143 
0144     int selectedBuiltinTool;
0145     bool textToolsEnabled;
0146 };
0147 
0148 const QList<QPair<KLocalizedString, QColor>> AnnotationActionHandlerPrivate::defaultColors = {{ki18nc("@item:inlistbox Color name", "Red"), Qt::red},
0149                                                                                               {ki18nc("@item:inlistbox Color name", "Orange"), QColor(255, 85, 0)},
0150                                                                                               {ki18nc("@item:inlistbox Color name", "Yellow"), Qt::yellow},
0151                                                                                               {ki18nc("@item:inlistbox Color name", "Green"), Qt::green},
0152                                                                                               {ki18nc("@item:inlistbox Color name", "Cyan"), Qt::cyan},
0153                                                                                               {ki18nc("@item:inlistbox Color name", "Blue"), Qt::blue},
0154                                                                                               {ki18nc("@item:inlistbox Color name", "Magenta"), Qt::magenta},
0155                                                                                               {ki18nc("@item:inlistbox Color name", "White"), Qt::white},
0156                                                                                               {ki18nc("@item:inlistbox Color name", "Gray"), Qt::gray},
0157                                                                                               {ki18nc("@item:inlistbox Color name", "Black"), Qt::black}
0158 
0159 };
0160 
0161 const QList<double> AnnotationActionHandlerPrivate::widthStandardValues = {1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5};
0162 
0163 const QList<double> AnnotationActionHandlerPrivate::opacityStandardValues = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};
0164 
0165 QAction *AnnotationActionHandlerPrivate::selectActionItem(KSelectAction *aList, QAction *aCustomCurrent, double value, const QList<double> &defaultValues, const QIcon &icon, const QString &label)
0166 {
0167     if (aCustomCurrent) {
0168         aList->removeAction(aCustomCurrent);
0169         delete aCustomCurrent;
0170     }
0171     QAction *aCustom = nullptr;
0172     const int defaultValueIdx = defaultValues.indexOf(value);
0173     if (defaultValueIdx >= 0) {
0174         aList->setCurrentItem(defaultValueIdx);
0175     } else {
0176         aCustom = new KToggleAction(icon, label, q);
0177         const int aBeforeIdx = std::lower_bound(defaultValues.begin(), defaultValues.end(), value) - defaultValues.begin();
0178         QAction *aBefore = aBeforeIdx < defaultValues.size() ? aList->actions().at(aBeforeIdx) : nullptr;
0179         aList->insertAction(aBefore, aCustom);
0180         aList->setCurrentAction(aCustom);
0181     }
0182     return aCustom;
0183 }
0184 
0185 void AnnotationActionHandlerPrivate::maybeUpdateCustomStampAction(const QString &stampIconName)
0186 {
0187     const auto defaultStamps = StampAnnotationWidget::defaultStamps();
0188     auto it = std::find_if(defaultStamps.begin(), defaultStamps.end(), [&stampIconName](const QPair<QString, QString> &element) { return element.second == stampIconName; });
0189     bool defaultStamp = it != defaultStamps.end();
0190 
0191     if (aCustomStamp) {
0192         aStamp->removeAction(aCustomStamp);
0193         agTools->removeAction(aCustomStamp);
0194         delete aCustomStamp;
0195         aCustomStamp = nullptr;
0196     }
0197     if (!defaultStamp) {
0198         QFileInfo info(stampIconName);
0199         QString stampActionName = info.fileName();
0200         aCustomStamp = new KToggleAction(stampIcon(stampIconName), stampActionName, q);
0201         aStamp->addAction(aCustomStamp);
0202         aStamp->setDefaultAction(aCustomStamp);
0203         agTools->addAction(aCustomStamp);
0204         aCustomStamp->setChecked(true);
0205         QObject::connect(aCustomStamp, &QAction::triggered, q, [this, stampIconName]() { slotStampToolSelected(stampIconName); });
0206     }
0207 }
0208 
0209 void AnnotationActionHandlerPrivate::parseTool(int toolId)
0210 {
0211     if (toolId == -1) {
0212         updateConfigActions();
0213         return;
0214     }
0215 
0216     QDomElement toolElement = annotator->builtinTool(toolId);
0217     const QString annotType = toolElement.attribute(QStringLiteral("type"));
0218     QDomElement engineElement = toolElement.firstChildElement(QStringLiteral("engine"));
0219     QDomElement annElement = engineElement.firstChildElement(QStringLiteral("annotation"));
0220 
0221     QColor color, innerColor, textColor;
0222     if (annElement.hasAttribute(QStringLiteral("color"))) {
0223         color = QColor(annElement.attribute(QStringLiteral("color")));
0224     }
0225     if (annElement.hasAttribute(QStringLiteral("innerColor"))) {
0226         innerColor = QColor(annElement.attribute(QStringLiteral("innerColor")));
0227     }
0228     if (annElement.hasAttribute(QStringLiteral("textColor"))) {
0229         textColor = QColor(annElement.attribute(QStringLiteral("textColor")));
0230     }
0231     if (textColor.isValid()) {
0232         currentColor = textColor;
0233         currentInnerColor = color;
0234     } else {
0235         currentColor = color;
0236         currentInnerColor = innerColor;
0237     }
0238 
0239     if (annElement.hasAttribute(QStringLiteral("font"))) {
0240         currentFont.fromString(annElement.attribute(QStringLiteral("font")));
0241     }
0242 
0243     // if the width value is not a default one, insert a new action in the width list
0244     if (annElement.hasAttribute(QStringLiteral("width"))) {
0245         double width = annElement.attribute(QStringLiteral("width")).toDouble();
0246         aCustomWidth = selectActionItem(aWidth, aCustomWidth, width, widthStandardValues, widthIcon(width), i18nc("@item:inlistbox", "Width %1", width));
0247     }
0248 
0249     // if the opacity value is not a default one, insert a new action in the opacity list
0250     if (annElement.hasAttribute(QStringLiteral("opacity"))) {
0251         double opacity = annElement.attribute(QStringLiteral("opacity")).toDouble();
0252         aCustomOpacity = selectActionItem(aOpacity, aCustomOpacity, opacity, opacityStandardValues, GuiUtils::createOpacityIcon(opacity), i18nc("@item:inlistbox", "%1%", opacity * 100));
0253     } else {
0254         aOpacity->setCurrentItem(opacityStandardValues.size() - 1); // 100 %
0255     }
0256 
0257     // if the tool is a custom stamp, insert a new action in the stamp list
0258     if (annotType == QStringLiteral("stamp")) {
0259         QString stampIconName = annElement.attribute(QStringLiteral("icon"));
0260         maybeUpdateCustomStampAction(stampIconName);
0261     }
0262 
0263     updateConfigActions(annotType);
0264 }
0265 
0266 void AnnotationActionHandlerPrivate::updateConfigActions(const QString &annotType)
0267 {
0268     const bool isAnnotationSelected = !annotType.isEmpty();
0269     const bool isTypewriter = annotType == QStringLiteral("typewriter");
0270     const bool isInlineNote = annotType == QStringLiteral("note-inline");
0271     const bool isText = isInlineNote || isTypewriter;
0272     const bool isPolygon = annotType == QStringLiteral("polygon");
0273     const bool isShape = annotType == QStringLiteral("rectangle") || annotType == QStringLiteral("ellipse") || isPolygon;
0274     const bool isStraightLine = annotType == QStringLiteral("straight-line");
0275     const bool isLine = annotType == QStringLiteral("ink") || isStraightLine;
0276     const bool isStamp = annotType == QStringLiteral("stamp");
0277 
0278     if (isTypewriter) {
0279         aColor->setIcon(GuiUtils::createColorIcon({currentColor}, QIcon::fromTheme(QStringLiteral("format-text-color"))));
0280     } else {
0281         aColor->setIcon(GuiUtils::createColorIcon({currentColor}, QIcon::fromTheme(QStringLiteral("format-stroke-color"))));
0282     }
0283     aInnerColor->setIcon(GuiUtils::createColorIcon({currentInnerColor}, QIcon::fromTheme(QStringLiteral("format-fill-color"))));
0284 
0285     aAddToQuickTools->setEnabled(isAnnotationSelected);
0286     aWidth->setEnabled(isLine || isShape);
0287     aColor->setEnabled(isAnnotationSelected && !isStamp);
0288     aInnerColor->setEnabled(isShape);
0289     aOpacity->setEnabled(isAnnotationSelected);
0290     aFont->setEnabled(isText);
0291     aConstrainRatioAndAngle->setEnabled(isStraightLine || isShape);
0292     aAdvancedSettings->setEnabled(isAnnotationSelected);
0293 
0294     // set tooltips
0295     if (!isAnnotationSelected) {
0296         aWidth->setToolTip(i18nc("@info:tooltip", "Annotation line width (No annotation selected)"));
0297         aColor->setToolTip(i18nc("@info:tooltip", "Annotation color (No annotation selected)"));
0298         aInnerColor->setToolTip(i18nc("@info:tooltip", "Annotation fill color (No annotation selected)"));
0299         aOpacity->setToolTip(i18nc("@info:tooltip", "Annotation opacity (No annotation selected)"));
0300         aFont->setToolTip(i18nc("@info:tooltip", "Annotation font (No annotation selected)"));
0301         aAddToQuickTools->setToolTip(i18nc("@info:tooltip", "Add the current annotation to the quick annotations menu (No annotation selected)"));
0302         aConstrainRatioAndAngle->setToolTip(i18nc("@info:tooltip", "Constrain shape ratio to 1:1 or line angle to 15° steps (No annotation selected)"));
0303         aAdvancedSettings->setToolTip(i18nc("@info:tooltip", "Advanced settings for the current annotation tool (No annotation selected)"));
0304         return;
0305     }
0306 
0307     if (isLine || isShape) {
0308         aWidth->setToolTip(i18nc("@info:tooltip", "Annotation line width"));
0309     } else {
0310         aWidth->setToolTip(i18nc("@info:tooltip", "Annotation line width (Current annotation has no line width)"));
0311     }
0312 
0313     if (isTypewriter) {
0314         aColor->setToolTip(i18nc("@info:tooltip", "Annotation text color"));
0315     } else if (isShape) {
0316         aColor->setToolTip(i18nc("@info:tooltip", "Annotation border color"));
0317     } else {
0318         aColor->setToolTip(i18nc("@info:tooltip", "Annotation color"));
0319     }
0320 
0321     if (isShape) {
0322         aInnerColor->setToolTip(i18nc("@info:tooltip", "Annotation fill color"));
0323     } else {
0324         aInnerColor->setToolTip(i18nc("@info:tooltip", "Annotation fill color (Current annotation has no fill color)"));
0325     }
0326 
0327     if (isText) {
0328         aFont->setToolTip(i18nc("@info:tooltip", "Annotation font"));
0329     } else {
0330         aFont->setToolTip(i18nc("@info:tooltip", "Annotation font (Current annotation has no font)"));
0331     }
0332 
0333     if (isStraightLine || isPolygon) {
0334         aConstrainRatioAndAngle->setToolTip(i18nc("@info:tooltip", "Constrain line angle to 15° steps"));
0335     } else if (isShape) {
0336         aConstrainRatioAndAngle->setToolTip(i18nc("@info:tooltip", "Constrain shape ratio to 1:1"));
0337     } else {
0338         aConstrainRatioAndAngle->setToolTip(i18nc("@info:tooltip", "Constrain shape ratio to 1:1 or line angle to 15° steps (Not supported by current annotation)"));
0339     }
0340 
0341     aOpacity->setToolTip(i18nc("@info:tooltip", "Annotation opacity"));
0342     aAddToQuickTools->setToolTip(i18nc("@info:tooltip", "Add the current annotation to the quick annotations menu"));
0343     aAdvancedSettings->setToolTip(i18nc("@info:tooltip", "Advanced settings for the current annotation tool"));
0344 }
0345 
0346 void AnnotationActionHandlerPrivate::populateQuickAnnotations()
0347 {
0348     if (!aQuickTools->isEnabled()) {
0349         return;
0350     }
0351 
0352     const QList<int> numberKeys = {Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0};
0353     const bool isFirstTimePopulated = aQuickTools->menu()->actions().count() == 0;
0354 
0355     // to be safe and avoid undefined states of the currently selected quick annotation
0356     if (isQuickToolAction(agTools->checkedAction())) {
0357         q->deselectAllAnnotationActions();
0358     }
0359 
0360     for (QAction *action : std::as_const(quickTools)) {
0361         aQuickTools->removeAction(action);
0362         aQuickToolsBar->removeAction(action);
0363         delete action;
0364     }
0365     quickTools.clear();
0366     textQuickTools.clear();
0367 
0368     int favToolId = 1;
0369     QList<int>::const_iterator shortcutNumber = numberKeys.begin();
0370     QDomElement favToolElement = annotator->quickTool(favToolId);
0371     int actionBarInsertPosition = 0;
0372     QAction *aSeparator = aQuickTools->menu()->actions().first();
0373     while (!favToolElement.isNull()) {
0374         QString itemText = favToolElement.attribute(QStringLiteral("name"));
0375         if (favToolElement.attribute(QStringLiteral("default"), QStringLiteral("false")) == QLatin1String("true")) {
0376             itemText = i18n(itemText.toLatin1().constData());
0377         }
0378         if (itemText.isEmpty()) {
0379             itemText = PageViewAnnotator::defaultToolName(favToolElement);
0380         }
0381         QIcon toolIcon = QIcon(PageViewAnnotator::makeToolPixmap(favToolElement));
0382         QAction *annFav = new KToggleAction(toolIcon, itemText, q);
0383         aQuickTools->insertAction(aSeparator, annFav);
0384         aQuickToolsBar->insertAction(actionBarInsertPosition++, annFav);
0385         agTools->addAction(annFav);
0386         quickTools.append(annFav);
0387         if (shortcutNumber != numberKeys.end()) {
0388             annFav->setShortcut(QKeySequence(*(shortcutNumber++)));
0389             annFav->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0390         }
0391         QObject::connect(annFav, &KToggleAction::toggled, q, [this, favToolId](bool checked) {
0392             if (checked) {
0393                 slotQuickToolSelected(favToolId);
0394             }
0395         });
0396         QDomElement engineElement = favToolElement.firstChildElement(QStringLiteral("engine"));
0397         if (engineElement.attribute(QStringLiteral("type")) == QStringLiteral("TextSelector")) {
0398             textQuickTools.append(annFav);
0399             annFav->setEnabled(textToolsEnabled);
0400         }
0401         favToolElement = annotator->quickTool(++favToolId);
0402     }
0403     aQuickToolsBar->recreateWidgets();
0404 
0405     // set the default action
0406     if (quickTools.isEmpty()) {
0407         aShowToolBar->setVisible(false);
0408         aQuickTools->addAction(aToolBarVisibility);
0409         aQuickTools->setDefaultAction(aToolBarVisibility);
0410         Okular::Settings::setQuickAnnotationDefaultAction(0);
0411         Okular::Settings::self()->save();
0412     } else {
0413         aShowToolBar->setVisible(true);
0414         aQuickTools->removeAction(aToolBarVisibility);
0415         aQuickTools->setDefaultAction(aQuickTools);
0416         int defaultAction = Okular::Settings::quickAnnotationDefaultAction();
0417         if (isFirstTimePopulated && defaultAction < quickTools.count()) {
0418             // we can reach here also if no quick tools were defined before, in that case defaultAction is correctly equal to zero
0419             aQuickTools->setDefaultAction(quickTools.at(defaultAction));
0420         } else {
0421             // if the quick tools have been modified we cannot restore the previous default action
0422             aQuickTools->setDefaultAction(quickTools.at(0));
0423             Okular::Settings::setQuickAnnotationDefaultAction(0);
0424             Okular::Settings::self()->save();
0425         }
0426     }
0427 }
0428 
0429 KSelectAction *AnnotationActionHandlerPrivate::colorPickerAction(AnnotationColor colorType)
0430 {
0431     auto colorList = defaultColors;
0432     QString aText(i18nc("@action:intoolbar Current annotation config option", "Color"));
0433     if (colorType == AnnotationColor::InnerColor) {
0434         aText = i18nc("@action:intoolbar Current annotation config option", "Fill Color");
0435         colorList.append(QPair<KLocalizedString, Qt::GlobalColor>(ki18nc("@item:inlistbox Color name", "Transparent"), Qt::transparent));
0436     }
0437     KSelectAction *aColorPicker = new KSelectAction(QIcon(), aText, q);
0438     aColorPicker->setToolBarMode(KSelectAction::MenuMode);
0439     for (const auto &colorNameValue : colorList) {
0440         QColor color(colorNameValue.second);
0441         QAction *colorAction = new QAction(GuiUtils::createColorIcon({color}, QIcon(), GuiUtils::VisualizeTransparent), colorNameValue.first.toString(), q);
0442         aColorPicker->addAction(colorAction);
0443         QObject::connect(colorAction, &QAction::triggered, q, [this, colorType, color]() { slotSetColor(colorType, color); });
0444     }
0445     QAction *aCustomColor = new QAction(QIcon::fromTheme(QStringLiteral("color-picker")), i18nc("@item:inlistbox", "Custom Color..."), q);
0446     aColorPicker->addAction(aCustomColor);
0447     QObject::connect(aCustomColor, &QAction::triggered, q, [this, colorType]() { slotSetColor(colorType); });
0448     return aColorPicker;
0449 }
0450 
0451 const QIcon AnnotationActionHandlerPrivate::widthIcon(double width)
0452 {
0453     QPixmap pm(32, 32);
0454     pm.fill(Qt::transparent);
0455     QPainter p(&pm);
0456     p.setRenderHint(QPainter::Antialiasing);
0457     p.setPen(QPen(Qt::black, 2 * width, Qt::SolidLine, Qt::RoundCap));
0458     p.drawLine(0, pm.height() / 2, pm.width(), pm.height() / 2);
0459     p.end();
0460     return QIcon(pm);
0461 }
0462 
0463 const QIcon AnnotationActionHandlerPrivate::stampIcon(const QString &stampIconName)
0464 {
0465     QPixmap stampPix = Okular::AnnotationUtils::loadStamp(stampIconName, 32);
0466     if (stampPix.width() == stampPix.height()) {
0467         return QIcon(stampPix);
0468     } else {
0469         return QIcon::fromTheme(QStringLiteral("tag"));
0470     }
0471 }
0472 
0473 void AnnotationActionHandlerPrivate::selectTool(int toolId)
0474 {
0475     selectedBuiltinTool = toolId;
0476     annotator->selectBuiltinTool(toolId, PageViewAnnotator::ShowTip::Yes);
0477     parseTool(toolId);
0478 }
0479 
0480 void AnnotationActionHandlerPrivate::slotStampToolSelected(const QString &stamp)
0481 {
0482     selectedBuiltinTool = PageViewAnnotator::STAMP_TOOL_ID;
0483     annotator->selectStampTool(stamp); // triggers a reparsing thus calling parseTool
0484 }
0485 
0486 void AnnotationActionHandlerPrivate::slotQuickToolSelected(int favToolId)
0487 {
0488     annotator->selectQuickTool(favToolId);
0489     selectedBuiltinTool = -1;
0490     updateConfigActions();
0491     Okular::Settings::setQuickAnnotationDefaultAction(favToolId - 1);
0492     Okular::Settings::self()->save();
0493 }
0494 
0495 void AnnotationActionHandlerPrivate::slotSetColor(AnnotationColor colorType, const QColor &color)
0496 {
0497     QColor selectedColor(color);
0498     if (!selectedColor.isValid()) {
0499         selectedColor = QColorDialog::getColor(currentColor, nullptr, i18nc("@title:window", "Select color"));
0500         if (!selectedColor.isValid()) {
0501             return;
0502         }
0503     }
0504     if (colorType == AnnotationColor::Color) {
0505         currentColor = selectedColor;
0506         annotator->setAnnotationColor(selectedColor);
0507     } else if (colorType == AnnotationColor::InnerColor) {
0508         currentInnerColor = selectedColor;
0509         annotator->setAnnotationInnerColor(selectedColor);
0510     }
0511 }
0512 
0513 void AnnotationActionHandlerPrivate::slotSelectAnnotationFont()
0514 {
0515     bool ok;
0516     QFont selectedFont = QFontDialog::getFont(&ok, currentFont);
0517     if (ok) {
0518         currentFont = selectedFont;
0519         annotator->setAnnotationFont(currentFont);
0520     }
0521 }
0522 
0523 bool AnnotationActionHandlerPrivate::isQuickToolAction(QAction *aTool)
0524 {
0525     return quickTools.contains(aTool);
0526 }
0527 
0528 bool AnnotationActionHandlerPrivate::isQuickToolStamp(int toolId)
0529 {
0530     QDomElement toolElement = annotator->quickTool(toolId);
0531     const QString annotType = toolElement.attribute(QStringLiteral("type"));
0532     QDomElement engineElement = toolElement.firstChildElement(QStringLiteral("engine"));
0533     QDomElement annElement = engineElement.firstChildElement(QStringLiteral("annotation"));
0534     return annotType == QStringLiteral("stamp");
0535 }
0536 
0537 void AnnotationActionHandlerPrivate::assertToolBarExists(KParts::MainWindow *mw, const QString &toolBarName)
0538 {
0539     QList<KToolBar *> toolbars = mw->toolBars();
0540     auto itToolBar = std::find_if(toolbars.begin(), toolbars.end(), [&](const KToolBar *toolBar) { return toolBar->objectName() == toolBarName; });
0541     Q_ASSERT(itToolBar != toolbars.end());
0542 }
0543 
0544 AnnotationActionHandler::AnnotationActionHandler(PageViewAnnotator *parent, KActionCollection *ac)
0545     : QObject(parent)
0546     , d(new AnnotationActionHandlerPrivate(this))
0547 {
0548     d->annotator = parent;
0549 
0550     // toolbar visibility actions
0551     d->aToolBarVisibility = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-freehand")), i18n("&Annotations"), this);
0552     d->aHideToolBar = new QAction(QIcon::fromTheme(QStringLiteral("dialog-close")), i18nc("@action:intoolbar Hide the toolbar", "Hide"), this);
0553     d->aShowToolBar = new QAction(QIcon::fromTheme(QStringLiteral("draw-freehand")), i18nc("@action:intoolbar Show the builtin annotation toolbar", "Show more annotation tools"), this);
0554 
0555     // Text markup actions
0556     KToggleAction *aHighlighter = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-highlight")), i18nc("@action:intoolbar Annotation tool", "Highlighter"), this);
0557     KToggleAction *aUnderline = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-underline")), i18nc("@action:intoolbar Annotation tool", "Underline"), this);
0558     KToggleAction *aSquiggle = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-underline-squiggle")), i18nc("@action:intoolbar Annotation tool", "Squiggle"), this);
0559     KToggleAction *aStrikeout = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-strikethrough")), i18nc("@action:intoolbar Annotation tool", "Strike Out"), this);
0560     // Notes actions
0561     KToggleAction *aTypewriter = new KToggleAction(QIcon::fromTheme(QStringLiteral("tool-text")), i18nc("@action:intoolbar Annotation tool", "Typewriter"), this);
0562     KToggleAction *aInlineNote = new KToggleAction(QIcon::fromTheme(QStringLiteral("note")), i18nc("@action:intoolbar Annotation tool", "Inline Note"), this);
0563     KToggleAction *aPopupNote = new KToggleAction(QIcon::fromTheme(QStringLiteral("edit-comment")), i18nc("@action:intoolbar Annotation tool", "Popup Note"), this);
0564     KToggleAction *aFreehandLine = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-freehand")), i18nc("@action:intoolbar Annotation tool", "Freehand Line"), this);
0565     // Geometrical shapes actions
0566     KToggleAction *aStraightLine = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-line")), i18nc("@action:intoolbar Annotation tool", "Straight line"), this);
0567     KToggleAction *aArrow = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-arrow")), i18nc("@action:intoolbar Annotation tool", "Arrow"), this);
0568     KToggleAction *aRectangle = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-rectangle")), i18nc("@action:intoolbar Annotation tool", "Rectangle"), this);
0569     KToggleAction *aEllipse = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-ellipse")), i18nc("@action:intoolbar Annotation tool", "Ellipse"), this);
0570     KToggleAction *aPolygon = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-polyline")), i18nc("@action:intoolbar Annotation tool", "Polygon"), this);
0571     d->aGeomShapes = new ToggleActionMenu(i18nc("@action", "Geometrical shapes"), this);
0572     d->aGeomShapes->setEnabled(true); // Need to explicitly set this once, or refreshActions() in part.cpp will disable this action
0573     d->aGeomShapes->setPopupMode(QToolButton::MenuButtonPopup);
0574     d->aGeomShapes->addAction(aArrow);
0575     d->aGeomShapes->addAction(aStraightLine);
0576     d->aGeomShapes->addAction(aRectangle);
0577     d->aGeomShapes->addAction(aEllipse);
0578     d->aGeomShapes->addAction(aPolygon);
0579     d->aGeomShapes->setDefaultAction(aArrow);
0580     connect(d->aGeomShapes->menu(), &QMenu::triggered, d->aGeomShapes, &ToggleActionMenu::setDefaultAction);
0581 
0582     // The order in which the actions are added is relevant to connect
0583     // them to the correct toolId defined in tools.xml
0584     d->agTools = new QActionGroup(this);
0585     d->agTools->addAction(aHighlighter);
0586     d->agTools->addAction(aUnderline);
0587     d->agTools->addAction(aSquiggle);
0588     d->agTools->addAction(aStrikeout);
0589     d->agTools->addAction(aTypewriter);
0590     d->agTools->addAction(aInlineNote);
0591     d->agTools->addAction(aPopupNote);
0592     d->agTools->addAction(aFreehandLine);
0593     d->agTools->addAction(aArrow);
0594     d->agTools->addAction(aStraightLine);
0595     d->agTools->addAction(aRectangle);
0596     d->agTools->addAction(aEllipse);
0597     d->agTools->addAction(aPolygon);
0598 
0599     d->textTools.append(aHighlighter);
0600     d->textTools.append(aUnderline);
0601     d->textTools.append(aSquiggle);
0602     d->textTools.append(aStrikeout);
0603 
0604     int toolId = 1;
0605     const QList<QAction *> tools = d->agTools->actions();
0606     for (const auto &ann : tools) {
0607         // action group workaround: connecting to toggled instead of triggered
0608         connect(ann, &QAction::toggled, this, [this, toolId](bool checked) {
0609             if (checked) {
0610                 d->selectTool(toolId);
0611             }
0612         });
0613         toolId++;
0614     }
0615 
0616     // Stamp action
0617     d->aStamp = new ToggleActionMenu(QIcon::fromTheme(QStringLiteral("tag")), i18nc("@action", "Stamp"), this);
0618     d->aStamp->setPopupMode(QToolButton::MenuButtonPopup);
0619     for (const auto &stamp : StampAnnotationWidget::defaultStamps()) {
0620         KToggleAction *ann = new KToggleAction(d->stampIcon(stamp.second), stamp.first, this);
0621         d->aStamp->addAction(ann);
0622         d->agTools->addAction(ann);
0623         // action group workaround: connecting to toggled instead of triggered
0624         // (because deselectAllAnnotationActions has to call triggered)
0625         connect(ann, &QAction::toggled, this, [this, stamp](bool checked) {
0626             if (checked) {
0627                 d->slotStampToolSelected(stamp.second);
0628             }
0629         });
0630     }
0631     if (!d->aStamp->menu()->actions().isEmpty()) {
0632         d->aStamp->setDefaultAction(d->aStamp->menu()->actions().first());
0633     }
0634     connect(d->aStamp->menu(), &QMenu::triggered, d->aStamp, &ToggleActionMenu::setDefaultAction);
0635 
0636     // Quick annotations action
0637     d->aQuickTools = new ToggleActionMenu(i18nc("@action:intoolbar Show list of quick annotation tools", "Quick Annotations"), this);
0638     d->aQuickTools->setPopupMode(QToolButton::MenuButtonPopup);
0639     d->aQuickTools->setIcon(QIcon::fromTheme(QStringLiteral("draw-freehand")));
0640     d->aQuickTools->setToolTip(i18nc("@info:tooltip", "Choose an annotation tool from the quick annotations"));
0641     d->aQuickTools->setEnabled(true); // required to ensure that populateQuickAnnotations is executed the first time
0642     // set the triggered quick annotation as default action (but avoid setting 'Configure...' as default action)
0643     connect(d->aQuickTools->menu(), &QMenu::triggered, this, [this](QAction *action) {
0644         if (action->isCheckable()) {
0645             d->aQuickTools->setDefaultAction(action);
0646         }
0647     });
0648 
0649     d->aQuickToolsBar = new ActionBar(this);
0650     d->aQuickToolsBar->setText(i18n("Quick Annotation Bar"));
0651 
0652     QAction *aQuickToolsSeparator = new QAction(this);
0653     aQuickToolsSeparator->setSeparator(true);
0654     d->aQuickTools->addAction(aQuickToolsSeparator);
0655     d->aQuickTools->addAction(d->aShowToolBar);
0656     QAction *aConfigAnnotation = ac->action(QStringLiteral("options_configure_annotations"));
0657     if (aConfigAnnotation) {
0658         d->aQuickTools->addAction(aConfigAnnotation);
0659         d->aQuickToolsBar->addAction(aConfigAnnotation);
0660     }
0661     d->populateQuickAnnotations();
0662 
0663     // Add to quick annotation action
0664     d->aAddToQuickTools = new QAction(QIcon::fromTheme(QStringLiteral("favorite")), i18nc("@action:intoolbar Add current annotation tool to the quick annotations list", "Add to Quick Annotations"), this);
0665 
0666     // Pin action
0667     d->aContinuousMode = new KToggleAction(QIcon::fromTheme(QStringLiteral("pin")), i18nc("@action:intoolbar When checked keep the current annotation tool active after use", "Keep Active"), this);
0668     d->aContinuousMode->setToolTip(i18nc("@info:tooltip", "Keep the annotation tool active after use"));
0669     d->aContinuousMode->setChecked(d->annotator->continuousMode());
0670 
0671     // Constrain angle action
0672     d->aConstrainRatioAndAngle =
0673         new KToggleAction(QIcon::fromTheme(QStringLiteral("snap-angle")), i18nc("@action When checked, line annotations are constrained to 15° steps, shape annotations to 1:1 ratio", "Constrain Ratio and Angle of Annotation Tools"), this);
0674     d->aConstrainRatioAndAngle->setChecked(d->annotator->constrainRatioAndAngleActive());
0675 
0676     // Annotation settings actions
0677     d->aColor = d->colorPickerAction(AnnotationActionHandlerPrivate::AnnotationColor::Color);
0678     d->aInnerColor = d->colorPickerAction(AnnotationActionHandlerPrivate::AnnotationColor::InnerColor);
0679     d->aFont = new QAction(QIcon::fromTheme(QStringLiteral("font-face")), i18nc("@action:intoolbar Current annotation config option", "Font"), this);
0680     d->aAdvancedSettings = new QAction(QIcon::fromTheme(QStringLiteral("settings-configure")), i18nc("@action:intoolbar Current annotation advanced settings", "Annotation Settings"), this);
0681 
0682     // Width list
0683     d->aWidth = new KSelectAction(QIcon::fromTheme(QStringLiteral("edit-line-width")), i18nc("@action:intoolbar Current annotation config option", "Line width"), this);
0684     d->aWidth->setToolBarMode(KSelectAction::MenuMode);
0685     for (auto width : d->widthStandardValues) {
0686         KToggleAction *ann = new KToggleAction(d->widthIcon(width), i18nc("@item:inlistbox", "Width %1", width), this);
0687         d->aWidth->addAction(ann);
0688         connect(ann, &QAction::triggered, this, [this, width]() { d->annotator->setAnnotationWidth(width); });
0689     }
0690 
0691     // Opacity list
0692     d->aOpacity = new KSelectAction(QIcon::fromTheme(QStringLiteral("edit-opacity")), i18nc("@action:intoolbar Current annotation config option", "Opacity"), this);
0693     d->aOpacity->setToolBarMode(KSelectAction::MenuMode);
0694     for (double opacity : d->opacityStandardValues) {
0695         KToggleAction *ann = new KToggleAction(GuiUtils::createOpacityIcon(opacity), i18nc("@item:inlistbox Annotation opacity percentage level, make sure to include %1 in your translation", "%1%", opacity * 100), this);
0696         d->aOpacity->addAction(ann);
0697         connect(ann, &QAction::triggered, this, [this, opacity]() { d->annotator->setAnnotationOpacity(opacity); });
0698     }
0699 
0700     connect(d->aAddToQuickTools, &QAction::triggered, d->annotator, &PageViewAnnotator::addToQuickAnnotations);
0701     connect(d->aContinuousMode, &QAction::toggled, d->annotator, &PageViewAnnotator::setContinuousMode);
0702     connect(d->aConstrainRatioAndAngle, &QAction::toggled, d->annotator, &PageViewAnnotator::setConstrainRatioAndAngle);
0703     connect(d->aAdvancedSettings, &QAction::triggered, d->annotator, &PageViewAnnotator::slotAdvancedSettings);
0704     connect(d->aFont, &QAction::triggered, std::bind(&AnnotationActionHandlerPrivate::slotSelectAnnotationFont, d));
0705 
0706     // action group workaround: allows unchecking the currently selected annotation action.
0707     // Other parts of code dependent to this workaround are marked with "action group workaround".
0708     connect(d->agTools, &QActionGroup::triggered, this, [this](QAction *action) {
0709         if (action == d->agLastAction) {
0710             d->agLastAction = nullptr;
0711             d->agTools->checkedAction()->setChecked(false);
0712             d->selectTool(-1);
0713         } else {
0714             d->agLastAction = action;
0715             // Show the annotation toolbar whenever builtin tool actions are triggered (e.g using shortcuts)
0716             if (!d->isQuickToolAction(action)) {
0717                 d->aToolBarVisibility->setChecked(true);
0718             }
0719         }
0720     });
0721 
0722     ac->addAction(QStringLiteral("mouse_toggle_annotate"), d->aToolBarVisibility);
0723     ac->addAction(QStringLiteral("hide_annotation_toolbar"), d->aHideToolBar);
0724     ac->addAction(QStringLiteral("quick_annotation_action_bar"), d->aQuickToolsBar);
0725     ac->addAction(QStringLiteral("annotation_highlighter"), aHighlighter);
0726     ac->addAction(QStringLiteral("annotation_underline"), aUnderline);
0727     ac->addAction(QStringLiteral("annotation_squiggle"), aSquiggle);
0728     ac->addAction(QStringLiteral("annotation_strike_out"), aStrikeout);
0729     ac->addAction(QStringLiteral("annotation_typewriter"), aTypewriter);
0730     ac->addAction(QStringLiteral("annotation_inline_note"), aInlineNote);
0731     ac->addAction(QStringLiteral("annotation_popup_note"), aPopupNote);
0732     ac->addAction(QStringLiteral("annotation_freehand_line"), aFreehandLine);
0733     ac->addAction(QStringLiteral("annotation_arrow"), aArrow);
0734     ac->addAction(QStringLiteral("annotation_straight_line"), aStraightLine);
0735     ac->addAction(QStringLiteral("annotation_rectangle"), aRectangle);
0736     ac->addAction(QStringLiteral("annotation_ellipse"), aEllipse);
0737     ac->addAction(QStringLiteral("annotation_polygon"), aPolygon);
0738     ac->addAction(QStringLiteral("annotation_geometrical_shape"), d->aGeomShapes);
0739     ac->addAction(QStringLiteral("annotation_stamp"), d->aStamp);
0740     ac->addAction(QStringLiteral("annotation_favorites"), d->aQuickTools);
0741     ac->addAction(QStringLiteral("annotation_bookmark"), d->aAddToQuickTools);
0742     ac->addAction(QStringLiteral("annotation_settings_pin"), d->aContinuousMode);
0743     ac->addAction(QStringLiteral("annotation_constrain_ratio_angle"), d->aConstrainRatioAndAngle);
0744     ac->addAction(QStringLiteral("annotation_settings_width"), d->aWidth);
0745     ac->addAction(QStringLiteral("annotation_settings_color"), d->aColor);
0746     ac->addAction(QStringLiteral("annotation_settings_inner_color"), d->aInnerColor);
0747     ac->addAction(QStringLiteral("annotation_settings_opacity"), d->aOpacity);
0748     ac->addAction(QStringLiteral("annotation_settings_font"), d->aFont);
0749     ac->addAction(QStringLiteral("annotation_settings_advanced"), d->aAdvancedSettings);
0750 
0751     ac->setDefaultShortcut(d->aToolBarVisibility, Qt::Key_F6);
0752     ac->setDefaultShortcut(aHighlighter, Qt::ALT | Qt::Key_1);
0753     ac->setDefaultShortcut(aUnderline, Qt::ALT | Qt::Key_2);
0754     ac->setDefaultShortcut(aSquiggle, Qt::ALT | Qt::Key_3);
0755     ac->setDefaultShortcut(aStrikeout, Qt::ALT | Qt::Key_4);
0756     ac->setDefaultShortcut(aTypewriter, Qt::ALT | Qt::Key_5);
0757     ac->setDefaultShortcut(aInlineNote, Qt::ALT | Qt::Key_6);
0758     ac->setDefaultShortcut(aPopupNote, Qt::ALT | Qt::Key_7);
0759     ac->setDefaultShortcut(aFreehandLine, Qt::ALT | Qt::Key_8);
0760     ac->setDefaultShortcut(aArrow, Qt::ALT | Qt::Key_9);
0761     ac->setDefaultShortcut(aRectangle, Qt::ALT | Qt::Key_0);
0762     ac->setDefaultShortcut(d->aAddToQuickTools, QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_B));
0763     d->updateConfigActions();
0764 
0765     connect(Okular::Settings::self(), &Okular::Settings::primaryAnnotationToolBarChanged, this, &AnnotationActionHandler::setupAnnotationToolBarVisibilityAction);
0766 }
0767 
0768 AnnotationActionHandler::~AnnotationActionHandler()
0769 {
0770     // delete the private data storage structure
0771     delete d;
0772 }
0773 
0774 void AnnotationActionHandler::setupAnnotationToolBarVisibilityAction()
0775 {
0776     // find the main window associated to the toggle toolbar action
0777     QList<QObject *> objects = d->aToolBarVisibility->associatedObjects();
0778     auto itMainWindow = std::find_if(objects.begin(), objects.end(), [](const QObject *object) { return qobject_cast<const KParts::MainWindow *>(object) != nullptr; });
0779     Q_ASSERT(itMainWindow != objects.end());
0780     KParts::MainWindow *mw = qobject_cast<KParts::MainWindow *>(*itMainWindow);
0781 
0782     // ensure that the annotation toolbars have been created
0783     d->assertToolBarExists(mw, QStringLiteral("annotationToolBar"));
0784     d->assertToolBarExists(mw, QStringLiteral("quickAnnotationToolBar"));
0785 
0786     KToolBar *annotationToolBar = mw->toolBar(QStringLiteral("annotationToolBar"));
0787     connect(annotationToolBar, &QToolBar::visibilityChanged, this, &AnnotationActionHandler::slotAnnotationToolBarVisibilityChanged, Qt::UniqueConnection);
0788     // show action
0789     connect(d->aShowToolBar, &QAction::triggered, annotationToolBar, &KToolBar::show, Qt::UniqueConnection);
0790     // hide action
0791     connect(d->aHideToolBar, &QAction::triggered, annotationToolBar, &KToolBar::hide, Qt::UniqueConnection);
0792 
0793     KToolBar *primaryAnnotationToolBar = annotationToolBar;
0794     if (Okular::Settings::primaryAnnotationToolBar() == Okular::Settings::EnumPrimaryAnnotationToolBar::QuickAnnotationToolBar) {
0795         primaryAnnotationToolBar = mw->toolBar(QStringLiteral("quickAnnotationToolBar"));
0796     }
0797     d->aToolBarVisibility->setChecked(false);
0798     d->aToolBarVisibility->disconnect();
0799     d->aToolBarVisibility->setChecked(primaryAnnotationToolBar->isVisible());
0800     connect(primaryAnnotationToolBar, &QToolBar::visibilityChanged, d->aToolBarVisibility, &QAction::setChecked, Qt::UniqueConnection);
0801     connect(d->aToolBarVisibility, &QAction::toggled, primaryAnnotationToolBar, &KToolBar::setVisible, Qt::UniqueConnection);
0802     d->aShowToolBar->setEnabled(!primaryAnnotationToolBar->isVisible());
0803 }
0804 
0805 void AnnotationActionHandler::reparseBuiltinToolsConfig()
0806 {
0807     d->parseTool(d->selectedBuiltinTool);
0808 }
0809 
0810 void AnnotationActionHandler::reparseQuickToolsConfig()
0811 {
0812     d->populateQuickAnnotations();
0813 }
0814 
0815 void AnnotationActionHandler::setToolsEnabled(bool on)
0816 {
0817     const QList<QAction *> tools = d->agTools->actions();
0818     for (QAction *ann : tools) {
0819         ann->setEnabled(on);
0820     }
0821     d->aQuickTools->setEnabled(on);
0822     d->aGeomShapes->setEnabled(on);
0823     d->aStamp->setEnabled(on);
0824     d->aContinuousMode->setEnabled(on);
0825 }
0826 
0827 void AnnotationActionHandler::setTextToolsEnabled(bool on)
0828 {
0829     d->textToolsEnabled = on;
0830     for (QAction *ann : std::as_const(d->textTools)) {
0831         ann->setEnabled(on);
0832     }
0833     for (QAction *ann : std::as_const(d->textQuickTools)) {
0834         ann->setEnabled(on);
0835     }
0836 }
0837 
0838 void AnnotationActionHandler::deselectAllAnnotationActions()
0839 {
0840     QAction *checkedAction = d->agTools->checkedAction();
0841     if (checkedAction) {
0842         checkedAction->trigger(); // action group workaround: using trigger instead of setChecked
0843     }
0844 }
0845 
0846 void AnnotationActionHandler::slotAnnotationToolBarVisibilityChanged(bool visible)
0847 {
0848     d->aShowToolBar->setEnabled(!visible);
0849     if (!visible && !d->isQuickToolAction(d->agTools->checkedAction())) {
0850         deselectAllAnnotationActions();
0851     }
0852 }
0853 
0854 #include "moc_annotationactionhandler.cpp"