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"