diff --git a/ui/annotationwidgets.cpp b/ui/annotationwidgets.cpp index 678b0ca1f..2b3bd4612 100644 --- a/ui/annotationwidgets.cpp +++ b/ui/annotationwidgets.cpp @@ -1,840 +1,893 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "annotationwidgets.h" // qt/kde includes #include #include #include #include #include #include #include #include #include +#include +#include #include +#include #include #include #include #include #include #include #include #include #include #include #include "core/document.h" #include "guiutils.h" #include "pagepainter.h" #define FILEATTACH_ICONSIZE 48 -PixmapPreviewSelector::PixmapPreviewSelector( QWidget * parent ) - : QWidget( parent ) +PixmapPreviewSelector::PixmapPreviewSelector( QWidget * parent, PreviewPosition position ) + : QWidget( parent ), m_previewPosition( position ) { - QHBoxLayout * mainlay = new QHBoxLayout( this ); + QVBoxLayout * mainlay = new QVBoxLayout( this ); mainlay->setMargin( 0 ); + QHBoxLayout * toplay = new QHBoxLayout( this ); + toplay->setMargin( 0 ); + mainlay->addLayout( toplay ); m_comboItems = new KComboBox( this ); - mainlay->addWidget( m_comboItems ); - mainlay->setAlignment( m_comboItems, Qt::AlignTop ); + toplay->addWidget( m_comboItems ); + m_stampPushButton = new QPushButton(QIcon::fromTheme( "document-open" ), QString(), this ); + m_stampPushButton->setVisible( false ); + m_stampPushButton->setToolTip( i18nc( "@info:tooltip", "Select a custom stamp symbol from file") ); + toplay->addWidget(m_stampPushButton); m_iconLabel = new QLabel( this ); - mainlay->addWidget( m_iconLabel ); + switch ( m_previewPosition ) + { + case Side: + toplay->addWidget( m_iconLabel ); + break; + case Below: + mainlay->addWidget( m_iconLabel ); + mainlay->setAlignment( m_iconLabel, Qt::AlignHCenter ); + break; + } m_iconLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); m_iconLabel->setAlignment( Qt::AlignCenter ); m_iconLabel->setFrameStyle( QFrame::StyledPanel ); setPreviewSize( 32 ); setFocusPolicy( Qt::TabFocus ); setFocusProxy( m_comboItems ); connect( m_comboItems, SIGNAL(currentIndexChanged(QString)), this, SLOT(iconComboChanged(QString)) ); connect( m_comboItems, &QComboBox::editTextChanged, this, &PixmapPreviewSelector::iconComboChanged ); + connect( m_stampPushButton, &QPushButton::clicked, this, &PixmapPreviewSelector::selectCustomStamp ); } PixmapPreviewSelector::~PixmapPreviewSelector() { } void PixmapPreviewSelector::setIcon( const QString& icon ) { int id = m_comboItems->findData( QVariant( icon ), Qt::UserRole, Qt::MatchFixedString ); if ( id == -1 ) id = m_comboItems->findText( icon, Qt::MatchFixedString ); if ( id > -1 ) { m_comboItems->setCurrentIndex( id ); } else if ( m_comboItems->isEditable() ) { m_comboItems->addItem( icon, QVariant( icon ) ); m_comboItems->setCurrentIndex( m_comboItems->findText( icon, Qt::MatchFixedString ) ); } } QString PixmapPreviewSelector::icon() const { return m_icon; } void PixmapPreviewSelector::addItem( const QString& item, const QString& id ) { m_comboItems->addItem( item, QVariant( id ) ); setIcon( m_icon ); } void PixmapPreviewSelector::setPreviewSize( int size ) { m_previewSize = size; - m_iconLabel->setFixedSize( m_previewSize + 8, m_previewSize + 8 ); + switch( m_previewPosition ) + { + case Side: + m_iconLabel->setFixedSize( m_previewSize + 8, m_previewSize + 8 ); + break; + case Below: + m_iconLabel->setFixedSize( 3 * m_previewSize + 8, m_previewSize + 8 ); + break; + } iconComboChanged( m_icon ); } int PixmapPreviewSelector::previewSize() const { return m_previewSize; } void PixmapPreviewSelector::setEditable( bool editable ) { m_comboItems->setEditable( editable ); + m_stampPushButton->setVisible( editable ); } void PixmapPreviewSelector::iconComboChanged( const QString& icon ) { int id = m_comboItems->findText( icon, Qt::MatchFixedString ); if ( id >= 0 ) { m_icon = m_comboItems->itemData( id ).toString(); } else { m_icon = icon; } - QPixmap pixmap = GuiUtils::loadStamp( m_icon, QSize(), m_previewSize ); + QPixmap pixmap = GuiUtils::loadStamp( m_icon, m_previewSize ); const QRect cr = m_iconLabel->contentsRect(); if ( pixmap.width() > cr.width() || pixmap.height() > cr.height() ) pixmap = pixmap.scaled( cr.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); m_iconLabel->setPixmap( pixmap ); emit iconChanged( m_icon ); } +void PixmapPreviewSelector::selectCustomStamp() +{ + const QString customStampFile = QFileDialog::getOpenFileName(this, i18nc("@title:window file chooser", "Select custom stamp symbol"), + QString(), i18n("*.ico *.png *.xpm *.svg *.svgz | Icon Files (*.ico *.png *.xpm *.svg *.svgz)") ); + if ( !customStampFile.isEmpty() ) + { + QPixmap pixmap = GuiUtils::loadStamp( customStampFile, m_previewSize ); + if( pixmap.isNull() ) { + KMessageBox::sorry( this, + xi18nc("@info", "Could not load the file %1", customStampFile ), + i18nc("@title:window", "Invalid file") + ); + } else { + m_comboItems->setEditText(customStampFile); + } + } +} AnnotationWidget * AnnotationWidgetFactory::widgetFor( Okular::Annotation * ann ) { switch ( ann->subType() ) { case Okular::Annotation::AStamp: return new StampAnnotationWidget( ann ); break; case Okular::Annotation::AText: return new TextAnnotationWidget( ann ); break; case Okular::Annotation::ALine: return new LineAnnotationWidget( ann ); break; case Okular::Annotation::AHighlight: return new HighlightAnnotationWidget( ann ); break; case Okular::Annotation::AInk: return new InkAnnotationWidget( ann ); break; case Okular::Annotation::AGeom: return new GeomAnnotationWidget( ann ); break; case Okular::Annotation::AFileAttachment: return new FileAttachmentAnnotationWidget( ann ); break; case Okular::Annotation::ACaret: return new CaretAnnotationWidget( ann ); break; // shut up gcc default: ; } // cases not covered yet: return a generic widget return new AnnotationWidget( ann ); } AnnotationWidget::AnnotationWidget( Okular::Annotation * ann ) : m_ann( ann ) { } AnnotationWidget::~AnnotationWidget() { } Okular::Annotation::SubType AnnotationWidget::annotationType() const { return m_ann->subType(); } QWidget * AnnotationWidget::appearanceWidget() { if ( m_appearanceWidget ) return m_appearanceWidget; m_appearanceWidget = createAppearanceWidget(); return m_appearanceWidget; } QWidget * AnnotationWidget::extraWidget() { if ( m_extraWidget ) return m_extraWidget; m_extraWidget = createExtraWidget(); return m_extraWidget; } void AnnotationWidget::applyChanges() { if (m_colorBn) m_ann->style().setColor( m_colorBn->color() ); if (m_opacity) m_ann->style().setOpacity( (double)m_opacity->value() / 100.0 ); } QWidget * AnnotationWidget::createAppearanceWidget() { QWidget * widget = new QWidget(); QFormLayout * formlayout = new QFormLayout( widget ); formlayout->setLabelAlignment( Qt::AlignRight ); formlayout->setFieldGrowthPolicy( QFormLayout::AllNonFixedFieldsGrow ); createStyleWidget( formlayout ); return widget; } void AnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { Q_UNUSED( formlayout ); } void AnnotationWidget::addColorButton( QWidget * widget, QFormLayout * formlayout ) { m_colorBn = new KColorButton( widget ); m_colorBn->setColor( m_ann->style().color() ); formlayout->addRow( i18n( "&Color:" ), m_colorBn ); connect( m_colorBn, &KColorButton::changed, this, &AnnotationWidget::dataChanged ); } void AnnotationWidget::addOpacitySpinBox( QWidget * widget, QFormLayout * formlayout ) { m_opacity = new QSpinBox( widget ); m_opacity->setRange( 0, 100 ); m_opacity->setValue( (int)( m_ann->style().opacity() * 100 ) ); m_opacity->setSuffix( i18nc( "Suffix for the opacity level, eg '80 %'", " %" ) ); formlayout->addRow( i18n( "&Opacity:" ), m_opacity); connect( m_opacity, SIGNAL(valueChanged(int)), this, SIGNAL(dataChanged()) ); } void AnnotationWidget::addVerticalSpacer( QFormLayout * formlayout ) { formlayout->addItem( new QSpacerItem( 0, 5, QSizePolicy::Fixed, QSizePolicy::Fixed ) ); } QWidget * AnnotationWidget::createExtraWidget() { return nullptr; } TextAnnotationWidget::TextAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ) { m_textAnn = static_cast< Okular::TextAnnotation * >( ann ); } void TextAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); if ( m_textAnn->textType() == Okular::TextAnnotation::Linked ) { createPopupNoteStyleUi( widget, formlayout ); } else if ( m_textAnn->textType() == Okular::TextAnnotation::InPlace ) { if ( isTypewriter() ) createTypewriterStyleUi( widget, formlayout ); else createInlineNoteStyleUi( widget, formlayout ); } } void TextAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); if ( m_textAnn->textType() == Okular::TextAnnotation::Linked ) { Q_ASSERT( m_pixmapSelector ); m_textAnn->setTextIcon( m_pixmapSelector->icon() ); } else if ( m_textAnn->textType() == Okular::TextAnnotation::InPlace ) { Q_ASSERT( m_fontReq ); m_textAnn->setTextFont( m_fontReq->font() ); if ( !isTypewriter() ) { Q_ASSERT( m_textAlign && m_spinWidth ); m_textAnn->setInplaceAlignment( m_textAlign->currentIndex() ); m_textAnn->style().setWidth( m_spinWidth->value() ); } else { Q_ASSERT( m_textColorBn ); m_textAnn->setTextColor( m_textColorBn->color() ); } } } void TextAnnotationWidget::createPopupNoteStyleUi( QWidget * widget, QFormLayout * formlayout ) { addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); addVerticalSpacer( formlayout ); addPixmapSelector( widget, formlayout ); } void TextAnnotationWidget::createInlineNoteStyleUi( QWidget * widget, QFormLayout * formlayout ) { addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); addVerticalSpacer( formlayout ); addFontRequester( widget, formlayout ); addTextAlignComboBox( widget, formlayout ); addVerticalSpacer( formlayout ); addWidthSpinBox( widget, formlayout ); } void TextAnnotationWidget::createTypewriterStyleUi( QWidget * widget, QFormLayout * formlayout ) { addFontRequester( widget, formlayout ); addTextColorButton( widget, formlayout ); } void TextAnnotationWidget::addPixmapSelector( QWidget * widget, QFormLayout * formlayout ) { m_pixmapSelector = new PixmapPreviewSelector( widget ); formlayout->addRow( i18n( "Icon:" ), m_pixmapSelector ); m_pixmapSelector->addItem( i18n( "Comment" ), QStringLiteral("Comment") ); m_pixmapSelector->addItem( i18n( "Help" ), QStringLiteral("Help") ); m_pixmapSelector->addItem( i18n( "Insert" ), QStringLiteral("Insert") ); m_pixmapSelector->addItem( i18n( "Key" ), QStringLiteral("Key") ); m_pixmapSelector->addItem( i18n( "New paragraph" ), QStringLiteral("NewParagraph") ); m_pixmapSelector->addItem( i18n( "Note" ), QStringLiteral("Note") ); m_pixmapSelector->addItem( i18n( "Paragraph" ), QStringLiteral("Paragraph") ); m_pixmapSelector->setIcon( m_textAnn->textIcon() ); connect( m_pixmapSelector, &PixmapPreviewSelector::iconChanged, this, &AnnotationWidget::dataChanged ); } void TextAnnotationWidget::addFontRequester( QWidget * widget, QFormLayout * formlayout ) { m_fontReq = new KFontRequester( widget ); formlayout->addRow( i18n( "Font:" ), m_fontReq ); m_fontReq->setFont( m_textAnn->textFont() ); connect( m_fontReq, &KFontRequester::fontSelected, this, &AnnotationWidget::dataChanged ); } void TextAnnotationWidget::addTextColorButton( QWidget * widget, QFormLayout * formlayout ) { m_textColorBn = new KColorButton( widget ); m_textColorBn->setColor( m_textAnn->textColor() ); formlayout->addRow( i18n( "Text &color:" ), m_textColorBn ); connect( m_textColorBn, &KColorButton::changed, this, &AnnotationWidget::dataChanged ); } void TextAnnotationWidget::addTextAlignComboBox( QWidget * widget, QFormLayout * formlayout ) { m_textAlign = new KComboBox( widget ); formlayout->addRow( i18n( "&Align:" ), m_textAlign ); m_textAlign->addItem( i18n("Left") ); m_textAlign->addItem( i18n("Center") ); m_textAlign->addItem( i18n("Right") ); m_textAlign->setCurrentIndex( m_textAnn->inplaceAlignment() ); connect( m_textAlign, SIGNAL(currentIndexChanged(int)), this, SIGNAL(dataChanged()) ); } void TextAnnotationWidget::addWidthSpinBox( QWidget * widget, QFormLayout * formlayout ) { m_spinWidth = new QDoubleSpinBox( widget ); formlayout->addRow( i18n( "Border &width:" ), m_spinWidth ); m_spinWidth->setRange( 0, 100 ); m_spinWidth->setValue( m_textAnn->style().width() ); m_spinWidth->setSingleStep( 0.1 ); connect( m_spinWidth, SIGNAL(valueChanged(double)), this, SIGNAL(dataChanged()) ); } StampAnnotationWidget::StampAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ), m_pixmapSelector( nullptr ) { m_stampAnn = static_cast< Okular::StampAnnotation * >( ann ); } void StampAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); + KMessageWidget * brokenStampSupportWarning = new KMessageWidget( widget ); + brokenStampSupportWarning->setText( xi18nc("@info", "experimental feature." + "Stamps inserted in PDF documents are not visible in PDF readers other than Okular.") ); + brokenStampSupportWarning->setMessageType( KMessageWidget::Warning ); + brokenStampSupportWarning->setWordWrap( true ); + brokenStampSupportWarning->setCloseButtonVisible( false ); + formlayout->insertRow( 0, brokenStampSupportWarning ); + addOpacitySpinBox( widget, formlayout ); addVerticalSpacer( formlayout ); - m_pixmapSelector = new PixmapPreviewSelector( widget ); + m_pixmapSelector = new PixmapPreviewSelector( widget, PixmapPreviewSelector::Below ); formlayout->addRow( i18n( "Stamp symbol:" ), m_pixmapSelector ); m_pixmapSelector->setEditable( true ); m_pixmapSelector->addItem( i18n( "Okular" ), QStringLiteral("okular") ); m_pixmapSelector->addItem( i18n( "Bookmark" ), QStringLiteral("bookmarks") ); m_pixmapSelector->addItem( i18n( "KDE" ), QStringLiteral("kde") ); m_pixmapSelector->addItem( i18n( "Information" ), QStringLiteral("help-about") ); m_pixmapSelector->addItem( i18n( "Approved" ), QStringLiteral("Approved") ); m_pixmapSelector->addItem( i18n( "As Is" ), QStringLiteral("AsIs") ); m_pixmapSelector->addItem( i18n( "Confidential" ), QStringLiteral("Confidential") ); m_pixmapSelector->addItem( i18n( "Departmental" ), QStringLiteral("Departmental") ); m_pixmapSelector->addItem( i18n( "Draft" ), QStringLiteral("Draft") ); m_pixmapSelector->addItem( i18n( "Experimental" ), QStringLiteral("Experimental") ); m_pixmapSelector->addItem( i18n( "Expired" ), QStringLiteral("Expired") ); m_pixmapSelector->addItem( i18n( "Final" ), QStringLiteral("Final") ); m_pixmapSelector->addItem( i18n( "For Comment" ), QStringLiteral("ForComment") ); m_pixmapSelector->addItem( i18n( "For Public Release" ), QStringLiteral("ForPublicRelease") ); m_pixmapSelector->addItem( i18n( "Not Approved" ), QStringLiteral("NotApproved") ); m_pixmapSelector->addItem( i18n( "Not For Public Release" ), QStringLiteral("NotForPublicRelease") ); m_pixmapSelector->addItem( i18n( "Sold" ), QStringLiteral("Sold") ); m_pixmapSelector->addItem( i18n( "Top Secret" ), QStringLiteral("TopSecret") ); m_pixmapSelector->setIcon( m_stampAnn->stampIconName() ); m_pixmapSelector->setPreviewSize( 64 ); connect( m_pixmapSelector, &PixmapPreviewSelector::iconChanged, this, &AnnotationWidget::dataChanged ); } void StampAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_stampAnn->setStampIconName( m_pixmapSelector->icon() ); } LineAnnotationWidget::LineAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ) { m_lineAnn = static_cast< Okular::LineAnnotation * >( ann ); if ( m_lineAnn->linePoints().count() == 2 ) m_lineType = 0; // line else if ( m_lineAnn->lineClosed() ) m_lineType = 1; // polygon else m_lineType = 2; // polyline } void LineAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); m_spinSize = new QDoubleSpinBox( widget ); m_spinSize->setRange( 1, 100 ); m_spinSize->setValue( m_lineAnn->style().width() ); connect( m_spinSize, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LineAnnotationWidget::dataChanged ); // Straight line if ( m_lineType == 0 ) { addVerticalSpacer( formlayout ); formlayout->addRow( i18n( "&Width:" ), m_spinSize ); //Line Term Styles addVerticalSpacer( formlayout ); m_startStyleCombo = new QComboBox( widget ); formlayout->addRow( i18n( "Line start:" ), m_startStyleCombo); m_endStyleCombo = new QComboBox( widget ); formlayout->addRow( i18n( "Line end:" ), m_endStyleCombo); //FIXME: Where does the tooltip goes?? const QList> termStyles { { Okular::LineAnnotation::Square, i18n( "Square" ) }, { Okular::LineAnnotation::Circle, i18n( "Circle" ) }, { Okular::LineAnnotation::Diamond, i18n( "Diamond" ) }, { Okular::LineAnnotation::OpenArrow, i18n( "Open Arrow" ) }, { Okular::LineAnnotation::ClosedArrow, i18n( "Closed Arrow" ) }, { Okular::LineAnnotation::None, i18n( "None" ) }, { Okular::LineAnnotation::Butt, i18n( "Butt" ) }, { Okular::LineAnnotation::ROpenArrow, i18n( "Right Open Arrow" ) }, { Okular::LineAnnotation::RClosedArrow, i18n( "Right Closed Arrow" ) }, { Okular::LineAnnotation::Slash, i18n( "Slash" ) } }; for ( const auto &item: termStyles ) { const QIcon icon = endStyleIcon( item.first, QGuiApplication::palette().color( QPalette::WindowText ) ); m_startStyleCombo->addItem( icon, item.second ); m_endStyleCombo->addItem( icon, item.second ); } m_startStyleCombo->setCurrentIndex( m_lineAnn->lineStartStyle() ); m_endStyleCombo->setCurrentIndex( m_lineAnn->lineEndStyle() ); //Leaders lengths addVerticalSpacer( formlayout ); m_spinLL = new QDoubleSpinBox( widget ); formlayout->addRow( i18n( "Leader line length:" ), m_spinLL ); m_spinLLE = new QDoubleSpinBox( widget ); formlayout->addRow( i18n( "Leader line extensions length:" ), m_spinLLE ); m_spinLL->setRange( -500, 500 ); m_spinLL->setValue( m_lineAnn->lineLeadingForwardPoint() ); m_spinLLE->setRange( 0, 500 ); m_spinLLE->setValue( m_lineAnn->lineLeadingBackwardPoint() ); connect( m_startStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &LineAnnotationWidget::dataChanged ); connect( m_endStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &LineAnnotationWidget::dataChanged ); connect( m_spinLL, SIGNAL(valueChanged(double)), this, SIGNAL(dataChanged()) ); connect( m_spinLLE, SIGNAL(valueChanged(double)), this, SIGNAL(dataChanged()) ); } else if ( m_lineType == 1 ) // Polygon { QHBoxLayout * colorlay = new QHBoxLayout(); m_useColor = new QCheckBox( i18n( "Enabled" ), widget ); colorlay->addWidget( m_useColor ); m_innerColor = new KColorButton( widget ); colorlay->addWidget( m_innerColor); formlayout->addRow( i18n( "Shape fill:" ), colorlay ); m_innerColor->setColor( m_lineAnn->lineInnerColor() ); if ( m_lineAnn->lineInnerColor().isValid() ) { m_useColor->setChecked( true ); } else { m_innerColor->setEnabled( false ); } addVerticalSpacer( formlayout ); formlayout->addRow( i18n( "&Width:" ), m_spinSize ); connect( m_innerColor, &KColorButton::changed, this, &AnnotationWidget::dataChanged ); connect( m_useColor, &QAbstractButton::toggled, this, &AnnotationWidget::dataChanged ); connect( m_useColor, &QCheckBox::toggled, m_innerColor, &KColorButton::setEnabled ); } } void LineAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); if ( m_lineType == 0 ) { Q_ASSERT(m_spinLL && m_spinLLE && m_startStyleCombo && m_endStyleCombo); m_lineAnn->setLineLeadingForwardPoint( m_spinLL->value() ); m_lineAnn->setLineLeadingBackwardPoint( m_spinLLE->value() ); m_lineAnn->setLineStartStyle( (Okular::LineAnnotation::TermStyle)m_startStyleCombo->currentIndex() ); m_lineAnn->setLineEndStyle( (Okular::LineAnnotation::TermStyle)m_endStyleCombo->currentIndex() ); } else if ( m_lineType == 1 ) { Q_ASSERT( m_useColor && m_innerColor ); if ( !m_useColor->isChecked() ) { m_lineAnn->setLineInnerColor( QColor() ); } else { m_lineAnn->setLineInnerColor( m_innerColor->color() ); } } Q_ASSERT( m_spinSize ); m_lineAnn->style().setWidth( m_spinSize->value() ); } QIcon LineAnnotationWidget::endStyleIcon( Okular::LineAnnotation::TermStyle endStyle, const QColor &lineColor ) { const int iconSize { 48 }; QImage image { iconSize, iconSize, QImage::Format_ARGB32}; image.fill( qRgba(0, 0, 0, 0) ); Okular::LineAnnotation prototype; prototype.setLinePoints( { { 0, 0.5 }, { 0.65, 0.5 } } ); prototype.setLineStartStyle(Okular::LineAnnotation::TermStyle::None); prototype.setLineEndStyle( endStyle ); prototype.style().setWidth( 4 ); prototype.style().setColor( lineColor ); prototype.style().setLineStyle( Okular::Annotation::LineStyle::Solid ); prototype.setBoundingRectangle( { 0, 0, 1, 1 } ); LineAnnotPainter linepainter { &prototype, QSize { iconSize, iconSize }, 1, QTransform() }; linepainter.draw( image ); return QIcon( QPixmap::fromImage( image ) ); } InkAnnotationWidget::InkAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ) { m_inkAnn = static_cast< Okular::InkAnnotation * >( ann ); } void InkAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); addVerticalSpacer( formlayout ); m_spinSize = new QDoubleSpinBox( widget ); formlayout->addRow( i18n( "&Width:" ), m_spinSize ); m_spinSize->setRange( 1, 100 ); m_spinSize->setValue( m_inkAnn->style().width() ); connect( m_spinSize, SIGNAL(valueChanged(double)), this, SIGNAL(dataChanged()) ); } void InkAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_inkAnn->style().setWidth( m_spinSize->value() ); } HighlightAnnotationWidget::HighlightAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ) { m_hlAnn = static_cast< Okular::HighlightAnnotation * >( ann ); } void HighlightAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); m_typeCombo = new KComboBox( widget ); formlayout->addRow( i18n( "Type:" ), m_typeCombo ); m_typeCombo->addItem( i18n( "Highlight" ) ); m_typeCombo->addItem( i18n( "Squiggle" ) ); m_typeCombo->addItem( i18n( "Underline" ) ); m_typeCombo->addItem( i18n( "Strike out" ) ); m_typeCombo->setCurrentIndex( m_hlAnn->highlightType() ); addVerticalSpacer( formlayout ); addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); connect( m_typeCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(dataChanged()) ); } void HighlightAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_hlAnn->setHighlightType( (Okular::HighlightAnnotation::HighlightType)m_typeCombo->currentIndex() ); } GeomAnnotationWidget::GeomAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ) { m_geomAnn = static_cast< Okular::GeomAnnotation * >( ann ); } void GeomAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); m_typeCombo = new KComboBox( widget ); formlayout->addRow( i18n( "Type:" ), m_typeCombo ); addVerticalSpacer( formlayout ); addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); QHBoxLayout * colorlay = new QHBoxLayout(); m_useColor = new QCheckBox( i18n( "Enabled" ), widget ); colorlay->addWidget(m_useColor); m_innerColor = new KColorButton( widget ); colorlay->addWidget( m_innerColor); formlayout->addRow( i18n( "Shape fill:" ), colorlay ); addVerticalSpacer( formlayout ); m_spinSize = new QDoubleSpinBox( widget ); formlayout->addRow( i18n( "&Width:" ), m_spinSize ); m_typeCombo->addItem( i18n( "Rectangle" ) ); m_typeCombo->addItem( i18n( "Ellipse" ) ); m_typeCombo->setCurrentIndex( m_geomAnn->geometricalType() ); m_innerColor->setColor( m_geomAnn->geometricalInnerColor() ); if ( m_geomAnn->geometricalInnerColor().isValid() ) { m_useColor->setChecked( true ); } else { m_innerColor->setEnabled( false ); } m_spinSize->setRange( 0, 100 ); m_spinSize->setValue( m_geomAnn->style().width() ); connect( m_typeCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(dataChanged()) ); connect( m_innerColor, &KColorButton::changed, this, &AnnotationWidget::dataChanged ); connect( m_useColor, &QAbstractButton::toggled, this, &AnnotationWidget::dataChanged ); connect(m_useColor, &QCheckBox::toggled, m_innerColor, &KColorButton::setEnabled); connect( m_spinSize, SIGNAL(valueChanged(double)), this, SIGNAL(dataChanged()) ); } void GeomAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_geomAnn->setGeometricalType( (Okular::GeomAnnotation::GeomType)m_typeCombo->currentIndex() ); if ( !m_useColor->isChecked() ) { m_geomAnn->setGeometricalInnerColor( QColor() ); } else { m_geomAnn->setGeometricalInnerColor( m_innerColor->color() ); } m_geomAnn->style().setWidth( m_spinSize->value() ); } FileAttachmentAnnotationWidget::FileAttachmentAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ), m_pixmapSelector( nullptr ) { m_attachAnn = static_cast< Okular::FileAttachmentAnnotation * >( ann ); } void FileAttachmentAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); addOpacitySpinBox( widget, formlayout ); m_pixmapSelector = new PixmapPreviewSelector( widget ); formlayout->addRow( i18n( "File attachment symbol:" ), m_pixmapSelector ); m_pixmapSelector->setEditable( true ); m_pixmapSelector->addItem( i18nc( "Symbol for file attachment annotations", "Graph" ), QStringLiteral("graph") ); m_pixmapSelector->addItem( i18nc( "Symbol for file attachment annotations", "Push Pin" ), QStringLiteral("pushpin") ); m_pixmapSelector->addItem( i18nc( "Symbol for file attachment annotations", "Paperclip" ), QStringLiteral("paperclip") ); m_pixmapSelector->addItem( i18nc( "Symbol for file attachment annotations", "Tag" ), QStringLiteral("tag") ); m_pixmapSelector->setIcon( m_attachAnn->fileIconName() ); connect( m_pixmapSelector, &PixmapPreviewSelector::iconChanged, this, &AnnotationWidget::dataChanged ); } QWidget * FileAttachmentAnnotationWidget::createExtraWidget() { QWidget * widget = new QWidget(); widget->setWindowTitle( i18nc( "'File' as normal file, that can be opened, saved, etc..", "File" ) ); Okular::EmbeddedFile *ef = m_attachAnn->embeddedFile(); const int size = ef->size(); const QString sizeString = size <= 0 ? i18nc( "Not available size", "N/A" ) : KFormat().formatByteSize( size ); const QString descString = ef->description().isEmpty() ? i18n( "No description available." ) : ef->description(); QHBoxLayout * mainLay = new QHBoxLayout( widget ); QFormLayout * lay = new QFormLayout(); mainLay->addLayout( lay ); QLabel * tmplabel = new QLabel( ef->name() , widget ); tmplabel->setTextInteractionFlags( Qt::TextSelectableByMouse ); lay->addRow( i18n( "Name:" ), tmplabel ); tmplabel = new QLabel( sizeString, widget ); tmplabel->setTextInteractionFlags( Qt::TextSelectableByMouse ); lay->addRow( i18n( "&Width:" ), tmplabel ); tmplabel = new QLabel( widget ); tmplabel->setTextFormat( Qt::PlainText ); tmplabel->setWordWrap( true ); tmplabel->setText( descString ); tmplabel->setTextInteractionFlags( Qt::TextSelectableByMouse ); lay->addRow( i18n( "Description:" ), tmplabel ); QMimeDatabase db; QMimeType mime = db.mimeTypeForFile( ef->name(), QMimeDatabase::MatchExtension); if ( mime.isValid() ) { tmplabel = new QLabel( widget ); tmplabel->setPixmap( QIcon::fromTheme( mime.iconName() ).pixmap( FILEATTACH_ICONSIZE, FILEATTACH_ICONSIZE ) ); tmplabel->setFixedSize( FILEATTACH_ICONSIZE, FILEATTACH_ICONSIZE ); QVBoxLayout * tmpLayout = new QVBoxLayout( widget ); tmpLayout->setAlignment( Qt::AlignTop ); mainLay->addLayout( tmpLayout ); tmpLayout->addWidget( tmplabel ); } return widget; } void FileAttachmentAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_attachAnn->setFileIconName( m_pixmapSelector->icon() ); } static QString caretSymbolToIcon( Okular::CaretAnnotation::CaretSymbol symbol ) { switch ( symbol ) { case Okular::CaretAnnotation::None: return QStringLiteral( "caret-none" ); case Okular::CaretAnnotation::P: return QStringLiteral( "caret-p" ); } return QString(); } static Okular::CaretAnnotation::CaretSymbol caretSymbolFromIcon( const QString &icon ) { if ( icon == QLatin1String( "caret-none" ) ) return Okular::CaretAnnotation::None; else if ( icon == QLatin1String( "caret-p" ) ) return Okular::CaretAnnotation::P; return Okular::CaretAnnotation::None; } CaretAnnotationWidget::CaretAnnotationWidget( Okular::Annotation * ann ) : AnnotationWidget( ann ), m_pixmapSelector( nullptr ) { m_caretAnn = static_cast< Okular::CaretAnnotation * >( ann ); } void CaretAnnotationWidget::createStyleWidget( QFormLayout * formlayout ) { QWidget * widget = qobject_cast( formlayout->parent() ); addColorButton( widget, formlayout ); addOpacitySpinBox( widget, formlayout ); m_pixmapSelector = new PixmapPreviewSelector( widget ); formlayout->addRow( i18n( "Caret symbol:" ), m_pixmapSelector ); m_pixmapSelector->addItem( i18nc( "Symbol for caret annotations", "None" ), QStringLiteral("caret-none") ); m_pixmapSelector->addItem( i18nc( "Symbol for caret annotations", "P" ), QStringLiteral("caret-p") ); m_pixmapSelector->setIcon( caretSymbolToIcon( m_caretAnn->caretSymbol() ) ); connect( m_pixmapSelector, &PixmapPreviewSelector::iconChanged, this, &AnnotationWidget::dataChanged ); } void CaretAnnotationWidget::applyChanges() { AnnotationWidget::applyChanges(); m_caretAnn->setCaretSymbol( caretSymbolFromIcon( m_pixmapSelector->icon() ) ); } #include "moc_annotationwidgets.cpp" diff --git a/ui/annotationwidgets.h b/ui/annotationwidgets.h index 6d2908eb1..79008819c 100644 --- a/ui/annotationwidgets.h +++ b/ui/annotationwidgets.h @@ -1,283 +1,289 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _ANNOTATIONWIDGETS_H_ #define _ANNOTATIONWIDGETS_H_ #include #include "core/annotations.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; class QFormLayout; class QLabel; +class QPushButton; class QWidget; class KColorButton; class QSpinBox; class KFontRequester; class AnnotationWidget; class PixmapPreviewSelector : public QWidget { Q_OBJECT public: - explicit PixmapPreviewSelector( QWidget * parent = nullptr ); + enum PreviewPosition{ Side, Below }; + + explicit PixmapPreviewSelector( QWidget * parent = nullptr, PreviewPosition position = Side ); virtual ~PixmapPreviewSelector(); void setIcon( const QString& icon ); QString icon() const; void addItem( const QString& item, const QString& id ); void setPreviewSize( int size ); int previewSize() const; void setEditable( bool editable ); Q_SIGNALS: void iconChanged( const QString& ); private Q_SLOTS: void iconComboChanged( const QString& icon ); + void selectCustomStamp(); private: QString m_icon; + QPushButton * m_stampPushButton; QLabel * m_iconLabel; QComboBox * m_comboItems; int m_previewSize; + PreviewPosition m_previewPosition; }; /** * A factory to create AnnotationWidget's. */ class AnnotationWidgetFactory { public: static AnnotationWidget * widgetFor( Okular::Annotation * ann ); }; class AnnotationWidget : public QObject { Q_OBJECT public: explicit AnnotationWidget( Okular::Annotation * ann ); virtual ~AnnotationWidget(); virtual Okular::Annotation::SubType annotationType() const; QWidget * appearanceWidget(); QWidget * extraWidget(); virtual void applyChanges(); Q_SIGNALS: void dataChanged(); protected: QWidget * createAppearanceWidget(); virtual void createStyleWidget(QFormLayout * formLayout); virtual QWidget * createExtraWidget(); void addColorButton( QWidget * widget, QFormLayout * formlayout ); void addOpacitySpinBox( QWidget * widget, QFormLayout * formlayout ); void addVerticalSpacer( QFormLayout * formlayout ); private: Okular::Annotation * m_ann; QWidget * m_appearanceWidget { nullptr }; QWidget * m_extraWidget { nullptr }; KColorButton *m_colorBn { nullptr }; QSpinBox *m_opacity { nullptr }; }; class QVBoxLayout; class QFormLayout; class TextAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit TextAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: void createPopupNoteStyleUi( QWidget * widget, QFormLayout * formlayout ); void createInlineNoteStyleUi( QWidget * widget, QFormLayout * formlayout ); void createTypewriterStyleUi( QWidget * widget, QFormLayout * formlayout ); void addPixmapSelector( QWidget * widget, QFormLayout * formlayout ); void addFontRequester( QWidget * widget, QFormLayout * formlayout ); void addTextColorButton( QWidget * widget, QFormLayout * formlayout ); void addTextAlignComboBox( QWidget * widget, QFormLayout * formlayout ); void addWidthSpinBox( QWidget * widget, QFormLayout * formlayout ); inline bool isTypewriter() const { return ( m_textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter ); } Okular::TextAnnotation * m_textAnn; PixmapPreviewSelector * m_pixmapSelector { nullptr }; KFontRequester * m_fontReq { nullptr }; KColorButton *m_textColorBn { nullptr }; QComboBox * m_textAlign { nullptr }; QDoubleSpinBox * m_spinWidth { nullptr }; }; class StampAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit StampAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: Okular::StampAnnotation * m_stampAnn; PixmapPreviewSelector * m_pixmapSelector; }; class LineAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit LineAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: static QIcon endStyleIcon( Okular::LineAnnotation::TermStyle endStyle, const QColor &lineColor ); Okular::LineAnnotation * m_lineAnn; int m_lineType; QDoubleSpinBox * m_spinLL { nullptr }; QDoubleSpinBox * m_spinLLE { nullptr }; QCheckBox * m_useColor { nullptr }; KColorButton * m_innerColor { nullptr }; QDoubleSpinBox * m_spinSize { nullptr }; QComboBox * m_startStyleCombo { nullptr }; QComboBox * m_endStyleCombo { nullptr }; }; class HighlightAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit HighlightAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: Okular::HighlightAnnotation * m_hlAnn; QComboBox * m_typeCombo; }; class GeomAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit GeomAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: Okular::GeomAnnotation * m_geomAnn; QComboBox * m_typeCombo; QCheckBox * m_useColor; KColorButton * m_innerColor; QDoubleSpinBox * m_spinSize; }; class FileAttachmentAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit FileAttachmentAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; QWidget * createExtraWidget() override; private: Okular::FileAttachmentAnnotation * m_attachAnn; PixmapPreviewSelector * m_pixmapSelector; }; class CaretAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit CaretAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: Okular::CaretAnnotation * m_caretAnn; PixmapPreviewSelector * m_pixmapSelector; }; class InkAnnotationWidget : public AnnotationWidget { Q_OBJECT public: explicit InkAnnotationWidget( Okular::Annotation * ann ); void applyChanges() override; protected: void createStyleWidget( QFormLayout * formlayout ) override; private: Okular::InkAnnotation * m_inkAnn; QDoubleSpinBox * m_spinSize; }; #endif diff --git a/ui/guiutils.cpp b/ui/guiutils.cpp index c184e027c..43edf82a1 100644 --- a/ui/guiutils.cpp +++ b/ui/guiutils.cpp @@ -1,285 +1,301 @@ /*************************************************************************** * Copyright (C) 2006-2007 by Pino Toscano * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "guiutils.h" // qt/kde includes #include #include #include #include #include #include #include #include // local includes #include "core/action.h" #include "core/annotations.h" #include "core/document.h" #include struct GuiUtilsHelper { GuiUtilsHelper() { } QSvgRenderer* svgStamps(); QList il; std::unique_ptr< QSvgRenderer > svgStampFile; }; QSvgRenderer* GuiUtilsHelper::svgStamps() { if ( !svgStampFile.get() ) { const QString stampFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/stamps.svg") ); if ( !stampFile.isEmpty() ) { svgStampFile.reset( new QSvgRenderer( stampFile ) ); if ( !svgStampFile->isValid() ) { svgStampFile.reset(); } } } return svgStampFile.get(); } Q_GLOBAL_STATIC( GuiUtilsHelper, s_data ) namespace GuiUtils { QString captionForAnnotation( const Okular::Annotation * ann ) { Q_ASSERT( ann ); const bool hasComment = !ann->contents().isEmpty(); QString ret; switch( ann->subType() ) { case Okular::Annotation::AText: if( ( (Okular::TextAnnotation*)ann )->textType() == Okular::TextAnnotation::Linked ) ret = i18n( "Pop-up Note" ); else { if( ( (Okular::TextAnnotation*)ann )->inplaceIntent() == Okular::TextAnnotation::TypeWriter ) ret = i18n( "Typewriter" ); else ret = i18n( "Inline Note" ); } break; case Okular::Annotation::ALine: if( ( (Okular::LineAnnotation*)ann )->linePoints().count() == 2 ) ret = hasComment ? i18n( "Straight Line with Comment" ) : i18n( "Straight Line" ); else ret = hasComment ? i18n( "Polygon with Comment" ) : i18n( "Polygon" ); break; case Okular::Annotation::AGeom: ret = hasComment ? i18n( "Geometry with Comment" ) : i18n( "Geometry" ); break; case Okular::Annotation::AHighlight: switch ( ( (Okular::HighlightAnnotation*)ann )->highlightType() ) { case Okular::HighlightAnnotation::Highlight: ret = hasComment ? i18n( "Highlight with Comment" ) : i18n( "Highlight" ); break; case Okular::HighlightAnnotation::Squiggly: ret = hasComment ? i18n( "Squiggle with Comment" ) : i18n( "Squiggle" ); break; case Okular::HighlightAnnotation::Underline: ret = hasComment ? i18n( "Underline with Comment" ) : i18n( "Underline" ); break; case Okular::HighlightAnnotation::StrikeOut: ret = hasComment ? i18n( "Strike Out with Comment" ) : i18n( "Strike Out" ); break; } break; case Okular::Annotation::AStamp: ret = hasComment ? i18n( "Stamp with Comment" ) : i18n( "Stamp" ); break; case Okular::Annotation::AInk: ret = hasComment ? i18n( "Freehand Line with Comment" ) : i18n( "Freehand Line" ); break; case Okular::Annotation::ACaret: ret = i18n( "Caret" ); break; case Okular::Annotation::AFileAttachment: ret = i18n( "File Attachment" ); break; case Okular::Annotation::ASound: ret = i18n( "Sound" ); break; case Okular::Annotation::AMovie: ret = i18n( "Movie" ); break; case Okular::Annotation::AScreen: ret = i18nc( "Caption for a screen annotation", "Screen" ); break; case Okular::Annotation::AWidget: ret = i18nc( "Caption for a widget annotation", "Widget" ); break; case Okular::Annotation::ARichMedia: ret = i18nc( "Caption for a rich media annotation", "Rich Media" ); break; case Okular::Annotation::A_BASE: break; } return ret; } QString authorForAnnotation( const Okular::Annotation * ann ) { Q_ASSERT( ann ); return !ann->author().isEmpty() ? ann->author() : i18nc( "Unknown author", "Unknown" ); } QString contentsHtml( const Okular::Annotation * ann ) { QString text = ann->contents().toHtmlEscaped(); text.replace( QLatin1Char('\n'), QLatin1String("
") ); return text; } QString prettyToolTip( const Okular::Annotation * ann ) { Q_ASSERT( ann ); QString author = authorForAnnotation( ann ); QString contents = contentsHtml( ann ); QString tooltip = QStringLiteral( "" ) + i18n( "Author: %1", author ) + QStringLiteral( "" ); if ( !contents.isEmpty() ) tooltip += QStringLiteral( "

" ) + contents; tooltip += QLatin1String("
"); return tooltip; } -QPixmap loadStamp( const QString& _name, const QSize& size, int iconSize ) +QPixmap loadStamp( const QString& nameOrPath, int size, bool keepAspectRatio ) { - const QString name = _name.toLower(); + const QString name = nameOrPath.toLower(); + + // _name is the name of an Okular stamp symbols ( multiple symbols in a single *.svg file) QSvgRenderer * r = nullptr; if ( ( r = s_data->svgStamps() ) && r->elementExists( name ) ) { - const QRectF stampElemRect = r->boundsOnElement( name ); - const QRectF stampRect( size.isValid() ? QRectF( QPointF( 0, 0 ), size ) : stampElemRect ); - QPixmap pixmap( stampRect.size().toSize() ); + const QSize stampSize = r->boundsOnElement( name ).size().toSize(); + const QSize pixmapSize = stampSize.scaled( size, size, + keepAspectRatio ? Qt::KeepAspectRatioByExpanding + : Qt::IgnoreAspectRatio ); + QPixmap pixmap( pixmapSize ); pixmap.fill( Qt::transparent ); QPainter p( &pixmap ); r->render( &p, name ); p.end(); return pixmap; } + + // _name is a path (do this before loading as icon name to avoid some rare weirdness ) QPixmap pixmap; + pixmap.load( nameOrPath ); + if ( !pixmap.isNull() ) { + pixmap = pixmap.scaled( size, size, + keepAspectRatio ? Qt::KeepAspectRatioByExpanding + : Qt::IgnoreAspectRatio, + Qt::SmoothTransformation ); + return pixmap; + } + + // _name is an icon name const KIconLoader * il = iconLoader(); QString path; - const int minSize = iconSize > 0 ? iconSize : qMin( size.width(), size.height() ); - pixmap = il->loadIcon( name, KIconLoader::User, minSize, KIconLoader::DefaultState, QStringList(), &path, true ); + pixmap = il->loadIcon( name, KIconLoader::User, size, KIconLoader::DefaultState, QStringList(), &path, true ); if ( path.isEmpty() ) - pixmap = il->loadIcon( name, KIconLoader::NoGroup, minSize ); - return pixmap; + pixmap = il->loadIcon( name, KIconLoader::NoGroup, size ); + + return pixmap; // can be a null pixmap } void addIconLoader( KIconLoader * loader ) { s_data->il.append( loader ); } void removeIconLoader( KIconLoader * loader ) { s_data->il.removeAll( loader ); } KIconLoader* iconLoader() { return s_data->il.isEmpty() ? KIconLoader::global() : s_data->il.back(); } void saveEmbeddedFile( Okular::EmbeddedFile *ef, QWidget *parent ) { const QString caption = i18n( "Where do you want to save %1?", ef->name() ); const QString path = QFileDialog::getSaveFileName( parent, caption, ef->name() ); if ( path.isEmpty() ) return; QFile targetFile( path ); writeEmbeddedFile( ef, parent, targetFile ); } void writeEmbeddedFile( Okular::EmbeddedFile *ef, QWidget *parent, QFile& target ) { if ( !target.open( QIODevice::WriteOnly ) ) { KMessageBox::error( parent, i18n( "Could not open \"%1\" for writing. File was not saved.", target.fileName() ) ); return; } target.write( ef->data() ); target.close(); } Okular::Movie* renditionMovieFromScreenAnnotation( const Okular::ScreenAnnotation *annotation ) { if ( !annotation ) return nullptr; if ( annotation->action() && annotation->action()->actionType() == Okular::Action::Rendition ) { Okular::RenditionAction *renditionAction = static_cast< Okular::RenditionAction * >( annotation->action() ); return renditionAction->movie(); } return nullptr; } // from Arthur - qt4 static inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; } void colorizeImage( QImage & grayImage, const QColor & color, unsigned int destAlpha ) { // Make sure that the image is Format_ARGB32_Premultiplied if ( grayImage.format() != QImage::Format_ARGB32_Premultiplied ) grayImage = grayImage.convertToFormat( QImage::Format_ARGB32_Premultiplied ); // iterate over all pixels changing the alpha component value unsigned int * data = (unsigned int *)grayImage.bits(); unsigned int pixels = grayImage.width() * grayImage.height(); int red = color.red(), green = color.green(), blue = color.blue(); int source, sourceSat, sourceAlpha; for( unsigned int i = 0; i < pixels; ++i ) { // optimize this loop keeping byte order into account source = data[i]; sourceSat = qRed( source ); int newR = qt_div_255( sourceSat * red ), newG = qt_div_255( sourceSat * green ), newB = qt_div_255( sourceSat * blue ); if ( (sourceAlpha = qAlpha( source )) == 255 ) { // use destAlpha data[i] = qRgba( newR, newG, newB, destAlpha ); } else { // use destAlpha * sourceAlpha product if ( destAlpha < 255 ) sourceAlpha = qt_div_255( destAlpha * sourceAlpha ); data[i] = qRgba( newR, newG, newB, sourceAlpha ); } } } } diff --git a/ui/guiutils.h b/ui/guiutils.h index 295dfa93d..a1a9cf123 100644 --- a/ui/guiutils.h +++ b/ui/guiutils.h @@ -1,63 +1,70 @@ /*************************************************************************** * Copyright (C) 2006-2007 by Pino Toscano * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef OKULAR_GUIUTILS_H #define OKULAR_GUIUTILS_H #include class QColor; class QImage; class QPixmap; class QSize; class QWidget; class QFile; class KIconLoader; namespace Okular { class Annotation; class EmbeddedFile; class Movie; class ScreenAnnotation; } namespace GuiUtils { /** * Returns the translated string with the type of the given @p annotation. */ QString captionForAnnotation( const Okular::Annotation * annotation ); QString authorForAnnotation( const Okular::Annotation * annotation ); QString contentsHtml( const Okular::Annotation * annotation ); QString prettyToolTip( const Okular::Annotation * annotation ); - QPixmap loadStamp( const QString& name, const QSize& size, int iconSize = 0 ); + /** + * Returns a pixmap for a stamp symbol + * + * @p name Name of a Okular stamp symbol, icon or path to an image + * @p size Size of the pixmap (ignore aspect ratio). Takes precedence over @p iconSize + * @p iconSize Maximum size of the pixmap (keep aspect ratio) + */ + QPixmap loadStamp( const QString& nameOrPath, int size, bool keepAspectRatio = true ); void addIconLoader( KIconLoader * loader ); void removeIconLoader( KIconLoader * loader ); KIconLoader* iconLoader(); void saveEmbeddedFile( Okular::EmbeddedFile *ef, QWidget *parent ); void writeEmbeddedFile( Okular::EmbeddedFile *ef, QWidget *parent, QFile& targetFile ); /** * Returns the movie object that is referenced by a rendition action of the passed screen @p annotation * or @c 0 if the screen annotation has no rendition action set or the rendition action does not contain * a media rendition. */ Okular::Movie* renditionMovieFromScreenAnnotation( const Okular::ScreenAnnotation * annotation ); // colorize a gray image to the given color void colorizeImage( QImage & image, const QColor & color, unsigned int alpha = 255 ); } #endif diff --git a/ui/pagepainter.cpp b/ui/pagepainter.cpp index bdf8e83f2..123689403 100644 --- a/ui/pagepainter.cpp +++ b/ui/pagepainter.cpp @@ -1,1229 +1,1229 @@ /*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "pagepainter.h" // qt / kde includes #include #include #include #include #include #include #include #include #include #include // system includes #include // local includes #include "core/area.h" #include "core/page.h" #include "core/page_p.h" #include "core/annotations.h" #include "core/utils.h" #include "guiutils.h" #include "settings.h" #include "core/observer.h" #include "core/tile.h" #include "settings_core.h" #include "ui/debug_ui.h" Q_GLOBAL_STATIC_WITH_ARGS( QPixmap, busyPixmap, ( KIconLoader::global()->loadIcon(QLatin1String("okular"), KIconLoader::NoGroup, IconSize(KIconLoader::Desktop), KIconLoader::DefaultState, QStringList(), 0, true) ) ) #define TEXTANNOTATION_ICONSIZE 24 inline QPen buildPen( const Okular::Annotation *ann, double width, const QColor &color ) { QPen p( QBrush( color ), width, ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin ); return p; } void PagePainter::paintPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits ) { paintCroppedPageOnPainter( destPainter, page, observer, flags, scaledWidth, scaledHeight, limits, Okular::NormalizedRect( 0, 0, 1, 1 ), nullptr ); } void PagePainter::paintCroppedPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits, const Okular::NormalizedRect &crop, Okular::NormalizedPoint *viewPortPoint ) { qreal dpr = destPainter->device()->devicePixelRatioF(); /* Calculate the cropped geometry of the page */ QRect scaledCrop = crop.geometry( scaledWidth, scaledHeight ); /* variables prefixed with d are in the device pixels coordinate system, which translates to the rendered output - that means, * multiplied with the device pixel ratio of the target PaintDevice */ const QRect dScaledCrop(QRectF(scaledCrop.x() * dpr, scaledCrop.y() * dpr, scaledCrop.width() * dpr, scaledCrop.height() * dpr).toAlignedRect()); int croppedWidth = scaledCrop.width(); int croppedHeight = scaledCrop.height(); int dScaledWidth = ceil(scaledWidth * dpr); int dScaledHeight = ceil(scaledHeight * dpr); const QRect dLimits(QRectF(limits.x() * dpr, limits.y() * dpr, limits.width() * dpr, limits.height() * dpr).toAlignedRect()); QColor paperColor = Qt::white; QColor backgroundColor = paperColor; if ( Okular::SettingsCore::changeColors() ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: backgroundColor = Qt::black; break; case Okular::SettingsCore::EnumRenderMode::Paper: paperColor = Okular::SettingsCore::paperColor(); backgroundColor = paperColor; break; case Okular::SettingsCore::EnumRenderMode::Recolor: backgroundColor = Okular::Settings::recolorBackground(); break; default: ; } } destPainter->fillRect( limits, backgroundColor ); const bool hasTilesManager = page->hasTilesManager( observer ); QPixmap pixmap; if ( !hasTilesManager ) { /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/ const QPixmap *p = page->_o_nearestPixmap( observer, dScaledWidth, dScaledHeight ); if (p != NULL) { pixmap = *p; pixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); } /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/ double pixmapRescaleRatio = !pixmap.isNull() ? dScaledWidth / (double)pixmap.width() : -1; long pixmapPixels = !pixmap.isNull() ? (long)pixmap.width() * (long)pixmap.height() : 0; if ( pixmap.isNull() || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 || (dScaledWidth > pixmap.width() && pixmapPixels > 60000000L) ) { // draw something on the blank page: the okular icon or a cross (as a fallback) if ( !busyPixmap()->isNull() ) { busyPixmap->setDevicePixelRatio(dpr); destPainter->drawPixmap( QPoint( 10, 10 ), *busyPixmap() ); } else { destPainter->setPen( Qt::gray ); destPainter->drawLine( 0, 0, croppedWidth-1, croppedHeight-1 ); destPainter->drawLine( 0, croppedHeight-1, croppedWidth-1, 0 ); } return; } } /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/ bool canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty(); bool canDrawTextSelection = (flags & TextSelection) && page->textSelection(); bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty(); bool enhanceLinks = (flags & EnhanceLinks) && Okular::Settings::highlightLinks(); bool enhanceImages = (flags & EnhanceImages) && Okular::Settings::highlightImages(); // vectors containing objects to draw // make this a qcolor, rect map, since we don't need // to know s_id here! we are only drawing this right? QList< QPair > * bufferedHighlights = nullptr; QList< Okular::Annotation * > * bufferedAnnotations = nullptr; QList< Okular::Annotation * > * unbufferedAnnotations = nullptr; Okular::Annotation *boundingRectOnlyAnn = nullptr; // Paint the bounding rect of this annotation // fill up lists with visible annotation/highlight objects/text selections if ( canDrawHighlights || canDrawTextSelection || canDrawAnnotations ) { // precalc normalized 'limits rect' for intersection double nXMin = ( (double)limits.left() / scaledWidth ) + crop.left, nXMax = ( (double)limits.right() / scaledWidth ) + crop.left, nYMin = ( (double)limits.top() / scaledHeight ) + crop.top, nYMax = ( (double)limits.bottom() / scaledHeight ) + crop.top; // append all highlights inside limits to their list if ( canDrawHighlights ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); QLinkedList< Okular::HighlightAreaRect * >::const_iterator h2It = page->m_highlights.constBegin(), hEnd = page->m_highlights.constEnd(); Okular::HighlightAreaRect::const_iterator hIt; for ( ; h2It != hEnd; ++h2It ) for (hIt=(*h2It)->constBegin(); hIt!=(*h2It)->constEnd(); ++hIt) { if ((*hIt).intersects(limitRect)) bufferedHighlights->append( qMakePair((*h2It)->color,*hIt) ); } delete limitRect; //} } if ( canDrawTextSelection ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); const Okular::RegularAreaRect *textSelection = page->textSelection(); Okular::HighlightAreaRect::const_iterator hIt = textSelection->constBegin(), hEnd = textSelection->constEnd(); for ( ; hIt != hEnd; ++hIt ) { if ( (*hIt).intersects( limitRect ) ) bufferedHighlights->append( qMakePair( page->textSelectionColor(), *hIt ) ); } delete limitRect; //} } // append annotations inside limits to the un/buffered list if ( canDrawAnnotations ) { QLinkedList< Okular::Annotation * >::const_iterator aIt = page->m_annotations.constBegin(), aEnd = page->m_annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * ann = *aIt; int flags = ann->flags(); if ( flags & Okular::Annotation::Hidden ) continue; if ( flags & Okular::Annotation::ExternallyDrawn ) { // ExternallyDrawn annots are never rendered by PagePainter. // Just paint the boundingRect if the annot is moved or resized. if ( flags & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) ) { boundingRectOnlyAnn = ann; } continue; } bool intersects = ann->transformedBoundingRectangle().intersects( nXMin, nYMin, nXMax, nYMax ); if ( ann->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * ta = static_cast< Okular::TextAnnotation * >( ann ); if ( ta->textType() == Okular::TextAnnotation::Linked ) { Okular::NormalizedRect iconrect( ann->transformedBoundingRectangle().left, ann->transformedBoundingRectangle().top, ann->transformedBoundingRectangle().left + TEXTANNOTATION_ICONSIZE / page->width(), ann->transformedBoundingRectangle().top + TEXTANNOTATION_ICONSIZE / page->height() ); intersects = iconrect.intersects( nXMin, nYMin, nXMax, nYMax ); } } if ( intersects ) { Okular::Annotation::SubType type = ann->subType(); if ( type == Okular::Annotation::ALine || type == Okular::Annotation::AHighlight || type == Okular::Annotation::AInk /*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/ ) { if ( !bufferedAnnotations ) bufferedAnnotations = new QList< Okular::Annotation * >(); bufferedAnnotations->append( ann ); } else { if ( !unbufferedAnnotations ) unbufferedAnnotations = new QList< Okular::Annotation * >(); unbufferedAnnotations->append( ann ); } } } } // end of intersections checking } /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/ bool bufferAccessibility = (flags & Accessibility) && Okular::SettingsCore::changeColors() && (Okular::SettingsCore::renderMode() != Okular::SettingsCore::EnumRenderMode::Paper); bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations || viewPortPoint; QPixmap * backPixmap = nullptr; QPainter * mixedPainter = nullptr; QRect limitsInPixmap = limits.translated( scaledCrop.topLeft() ); QRect dLimitsInPixmap = dLimits.translated( dScaledCrop.topLeft() ); // limits within full (scaled but uncropped) pixmap /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/ if ( !useBackBuffer ) { if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect = QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect(); QRect limitsInTile = limits & tileRect; QRectF dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap* tilePixmap = tile.pixmap(); tilePixmap->setDevicePixelRatio( qApp->devicePixelRatio() ); if ( tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height() ) { destPainter->drawPixmap( limitsInTile.topLeft(), *tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); } else { destPainter->drawPixmap( tileRect, *tilePixmap ); } } tIt++; } } else { QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); scaledCroppedPixmap.setDevicePixelRatio(dpr); destPainter->drawPixmap( limits.topLeft(), scaledCroppedPixmap, QRectF(0, 0, dLimits.width(),dLimits.height())); } // 4A.2. active painter is the one passed to this method mixedPainter = destPainter; } /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/ else { // the image over which we are going to draw QImage backImage = QImage( dLimits.width(), dLimits.height(), QImage::Format_ARGB32_Premultiplied ); backImage.setDevicePixelRatio(dpr); backImage.fill( paperColor ); QPainter p( &backImage ); if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect(QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect()); QRect limitsInTile = limits & tileRect; QRect dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap* tilePixmap = tile.pixmap(); tilePixmap->setDevicePixelRatio( qApp->devicePixelRatio() ); if ( tilePixmap->width() == dTileRect.width() && tilePixmap->height() == dTileRect.height() ) { p.drawPixmap( limitsInTile.translated( -limits.topLeft() ).topLeft(), *tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); } else { double xScale = tilePixmap->width() / (double)dTileRect.width(); double yScale = tilePixmap->height() / (double)dTileRect.height(); QTransform transform( xScale, 0, 0, yScale, 0, 0 ); p.drawPixmap( limitsInTile.translated( -limits.topLeft() ), *tilePixmap, transform.mapRect( dLimitsInTile ).translated( -transform.mapRect( dTileRect ).topLeft() ) ); } } ++tIt; } } else { // 4B.1. draw the page pixmap: normal or scaled QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); scaledCroppedPixmap.setDevicePixelRatio(dpr); p.drawPixmap( 0, 0, scaledCroppedPixmap ); } p.end(); // 4B.2. modify pixmap following accessibility settings if ( bufferAccessibility ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: // Invert image pixels using QImage internal function backImage.invertPixels(QImage::InvertRgb); break; case Okular::SettingsCore::EnumRenderMode::Recolor: recolor(&backImage, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground()); break; case Okular::SettingsCore::EnumRenderMode::BlackWhite: // Manual Gray and Contrast unsigned int * data = (unsigned int *)backImage.bits(); int val, pixels = backImage.width() * backImage.height(), con = Okular::Settings::bWContrast(), thr = 255 - Okular::Settings::bWThreshold(); for( int i = 0; i < pixels; ++i ) { val = qGray( data[i] ); if ( val > thr ) val = 128 + (127 * (val - thr)) / (255 - thr); else if ( val < thr ) val = (128 * val) / thr; if ( con > 2 ) { val = con * ( val - thr ) / 2 + thr; if ( val > 255 ) val = 255; else if ( val < 0 ) val = 0; } data[i] = qRgba( val, val, val, 255 ); } break; } } // 4B.3. highlight rects in page if ( bufferedHighlights ) { // draw highlights that are inside the 'limits' paint region for (const auto& highlight : *bufferedHighlights) { const Okular::NormalizedRect & r = highlight.second; // find out the rect to highlight on pixmap QRect highlightRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).intersected( limits ); highlightRect.translate( -limits.left(), -limits.top() ); const QColor highlightColor = highlight.first; QPainter painter(&backImage); painter.setCompositionMode(QPainter::CompositionMode_Multiply); painter.fillRect(highlightRect, highlightColor); auto frameColor = highlightColor.darker(150); const QRect frameRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).translated( -limits.left(), -limits.top() ); painter.setPen(frameColor); painter.drawRect(frameRect); } } // 4B.4. paint annotations [COMPOSITED ONES] if ( bufferedAnnotations ) { // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied // and have to be so that the QPainter::CompositionMode_Multiply works // we could also put a // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) // that would be almost a noop, but we'll leave the assert for now Q_ASSERT(backImage.format() == QImage::Format_ARGB32_Premultiplied); // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates double pageScale = (double)croppedWidth / page->width(); double xOffset = (double)limits.left() / (double)scaledWidth + crop.left, xScale = (double)scaledWidth / (double)limits.width(), yOffset = (double)limits.top() / (double)scaledHeight + crop.top, yScale = (double)scaledHeight / (double)limits.height(); // paint all buffered annotations in the page QList< Okular::Annotation * >::const_iterator aIt = bufferedAnnotations->constBegin(), aEnd = bufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; Okular::Annotation::SubType type = a->subType(); QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlphaF( a->style().opacity() ); // draw LineAnnotation MISSING: caption, dash pattern, endings for multipoint lines if ( type == Okular::Annotation::ALine ) { LineAnnotPainter linepainter { (Okular::LineAnnotation *) a, { page->width(), page->height() }, pageScale, { xScale, 0., 0., yScale, -xOffset * xScale, -yOffset * yScale } }; linepainter.draw( backImage ); } // draw HighlightAnnotation MISSING: under/strike width, feather, capping else if ( type == Okular::Annotation::AHighlight ) { // get the annotation Okular::HighlightAnnotation * ha = (Okular::HighlightAnnotation *) a; Okular::HighlightAnnotation::HighlightType type = ha->highlightType(); // draw each quad of the annotation int quads = ha->highlightQuads().size(); for ( int q = 0; q < quads; q++ ) { NormalizedPath path; const Okular::HighlightAnnotation::Quad & quad = ha->highlightQuads()[ q ]; // normalize page point to image for ( int i = 0; i < 4; i++ ) { Okular::NormalizedPoint point; point.x = (quad.transformedPoint( i ).x - xOffset) * xScale; point.y = (quad.transformedPoint( i ).y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image switch ( type ) { // highlight the whole rect case Okular::HighlightAnnotation::Highlight: drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // highlight the bottom part of the rect case Okular::HighlightAnnotation::Squiggly: path[ 3 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 3 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 2 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 2 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // make a line at 3/4 of the height case Okular::HighlightAnnotation::Underline: path[ 0 ].x = ( 3 * path[ 0 ].x + path[ 3 ].x ) / 4.0; path[ 0 ].y = ( 3 * path[ 0 ].y + path[ 3 ].y ) / 4.0; path[ 1 ].x = ( 3 * path[ 1 ].x + path[ 2 ].x ) / 4.0; path[ 1 ].y = ( 3 * path[ 1 ].y + path[ 2 ].y ) / 4.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; // make a line at 1/2 of the height case Okular::HighlightAnnotation::StrikeOut: path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; } } } // draw InkAnnotation MISSING:invar width, PENTRACER else if ( type == Okular::Annotation::AInk ) { // get the annotation Okular::InkAnnotation * ia = (Okular::InkAnnotation *) a; // draw each ink path const QList< QLinkedList > transformedInkPaths = ia->transformedInkPaths(); const QPen inkPen = buildPen( a, a->style().width(), acolor ); int paths = transformedInkPaths.size(); for ( int p = 0; p < paths; p++ ) { NormalizedPath path; const QLinkedList & inkPath = transformedInkPaths[ p ]; // normalize page point to image QLinkedList::const_iterator pIt = inkPath.constBegin(), pEnd = inkPath.constEnd(); for ( ; pIt != pEnd; ++pIt ) { const Okular::NormalizedPoint & inkPoint = *pIt; Okular::NormalizedPoint point; point.x = (inkPoint.x - xOffset) * xScale; point.y = (inkPoint.y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image drawShapeOnImage( backImage, path, false, inkPen, QBrush(), pageScale ); } } } // end current annotation drawing } if(viewPortPoint) { QPainter painter(&backImage); painter.translate( -limits.left(), -limits.top() ); painter.setPen( QApplication::palette().color( QPalette::Active, QPalette::Highlight ) ); painter.drawLine( 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1, viewPortPoint->y * scaledHeight + 1 ); // ROTATION CURRENTLY NOT IMPLEMENTED /* if( page->rotation() == Okular::Rotation0) { } else if(page->rotation() == Okular::Rotation270) { painter.drawLine( viewPortPoint->y * scaledHeight + 1, 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1); } else if(page->rotation() == Okular::Rotation180) { painter.drawLine( 0, (1.0 - viewPortPoint->y) * scaledHeight - 1, scaledWidth - 1, (1.0 - viewPortPoint->y) * scaledHeight - 1 ); } else if(page->rotation() == Okular::Rotation90) // not right, rotation clock-wise { painter.drawLine( scaledWidth - (viewPortPoint->y * scaledHeight + 1), 0, scaledWidth - (viewPortPoint->y * scaledHeight + 1), scaledWidth - 1); } */ } // 4B.5. create the back pixmap converting from the local image backPixmap = new QPixmap( QPixmap::fromImage( backImage ) ); backPixmap->setDevicePixelRatio(dpr); // 4B.6. create a painter over the pixmap and set it as the active one mixedPainter = new QPainter( backPixmap ); mixedPainter->translate( -limits.left(), -limits.top() ); } /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/ if ( unbufferedAnnotations ) { // iterate over annotations and paint AText, AGeom, AStamp QList< Okular::Annotation * >::const_iterator aIt = unbufferedAnnotations->constBegin(), aEnd = unbufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; // honor opacity settings on supported types unsigned int opacity = (unsigned int)( a->style().color().alpha() * a->style().opacity() ); // skip the annotation drawing if all the annotation is fully // transparent, but not with text annotations if ( opacity <= 0 && a->subType() != Okular::Annotation::AText ) continue; QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlpha( opacity ); // get annotation boundary and drawn rect QRect annotBoundary = a->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect annotRect = annotBoundary.intersected( limits ); QRect innerRect( annotRect.left() - annotBoundary.left(), annotRect.top() - annotBoundary.top(), annotRect.width(), annotRect.height() ); QRectF dInnerRect(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr); Okular::Annotation::SubType type = a->subType(); // draw TextAnnotation if ( type == Okular::Annotation::AText ) { Okular::TextAnnotation * text = (Okular::TextAnnotation *)a; if ( text->textType() == Okular::TextAnnotation::InPlace ) { QImage image( annotBoundary.size(), QImage::Format_ARGB32 ); image.fill( acolor.rgba() ); QPainter painter( &image ); painter.setFont( text->textFont() ); painter.setPen( text->textColor() ); Qt::AlignmentFlag halign = ( text->inplaceAlignment() == 1 ? Qt::AlignHCenter : ( text->inplaceAlignment() == 2 ? Qt::AlignRight : Qt::AlignLeft ) ); const double invXScale = (double)page->width() / scaledWidth; const double invYScale = (double)page->height() / scaledHeight; const double borderWidth = text->style().width(); painter.scale( 1 / invXScale, 1 / invYScale ); painter.drawText( borderWidth * invXScale, borderWidth * invYScale, (image.width() - 2 * borderWidth) * invXScale, (image.height() - 2 * borderWidth) * invYScale, Qt::AlignTop | halign | Qt::TextWordWrap, text->contents() ); painter.resetTransform(); //Required as asking for a zero width pen results //in a default width pen (1.0) being created if ( borderWidth != 0 ) { QPen pen( Qt::black, borderWidth ); painter.setPen( pen ); painter.drawRect( 0, 0, image.width() - 1, image.height() - 1 ); } painter.end(); mixedPainter->drawImage( annotBoundary.topLeft(), image ); } else if ( text->textType() == Okular::TextAnnotation::Linked ) { // get pixmap, colorize and alpha-blend it QString path; QPixmap pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::User, 32, KIconLoader::DefaultState, QStringList(), &path, true ); if ( path.isEmpty() ) pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::NoGroup, 32 ); QRect annotBoundary2 = QRect( annotBoundary.topLeft(), QSize( TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr ) ); QRect annotRect2 = annotBoundary2.intersected( limits ); QRect innerRect2( annotRect2.left() - annotBoundary2.left(), annotRect2.top() - annotBoundary2.top(), annotRect2.width(), annotRect2.height() ); QPixmap scaledCroppedPixmap = pixmap.scaled(TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr).copy(dInnerRect.toAlignedRect()); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); // if the annotation color is valid (ie it was set), then // use it to colorize the icon, otherwise the icon will be // "gray" if ( a->style().color().isValid() ) GuiUtils::colorizeImage( scaledCroppedImage, a->style().color(), opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the mangled image to painter mixedPainter->drawPixmap( annotRect.topLeft(), pixmap); } } // draw StampAnnotation else if ( type == Okular::Annotation::AStamp ) { Okular::StampAnnotation * stamp = (Okular::StampAnnotation *)a; // get pixmap and alpha blend it if needed - QPixmap pixmap = GuiUtils::loadStamp( stamp->stampIconName(), annotBoundary.size() ); + QPixmap pixmap = GuiUtils::loadStamp( stamp->stampIconName(), annotBoundary.width() ); if ( !pixmap.isNull() ) // should never happen but can happen on huge sizes { const QRect dInnerRect(QRectF(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr).toAlignedRect()); QPixmap scaledCroppedPixmap = pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr).copy(dInnerRect); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); if ( opacity < 255 ) changeImageAlpha( scaledCroppedImage, opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the scaled and al mixedPainter->drawPixmap( annotRect.topLeft(), pixmap ); } } // draw GeomAnnotation else if ( type == Okular::Annotation::AGeom ) { Okular::GeomAnnotation * geom = (Okular::GeomAnnotation *)a; // check whether there's anything to draw if ( geom->style().width() || geom->geometricalInnerColor().isValid() ) { mixedPainter->save(); const double width = geom->style().width() * Okular::Utils::realDpi(nullptr).width() / ( 72.0 * 2.0 ) * scaledWidth / page->width(); QRectF r( .0, .0, annotBoundary.width(), annotBoundary.height() ); r.adjust( width, width, -width, -width ); r.translate( annotBoundary.topLeft() ); if ( geom->geometricalInnerColor().isValid() ) { r.adjust( width, width, -width, -width ); const QColor color = geom->geometricalInnerColor(); mixedPainter->setPen( Qt::NoPen ); mixedPainter->setBrush( QColor( color.red(), color.green(), color.blue(), opacity ) ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); r.adjust( -width, -width, width, width ); } if ( geom->style().width() ) // need to check the original size here.. { mixedPainter->setPen( buildPen( a, width * 2, acolor ) ); mixedPainter->setBrush( Qt::NoBrush ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); } mixedPainter->restore(); } } // draw extents rectangle if ( Okular::Settings::debugDrawAnnotationRect() ) { mixedPainter->setPen( a->style().color() ); mixedPainter->drawRect( annotBoundary ); } } } if ( boundingRectOnlyAnn ) { QRect annotBoundary = boundingRectOnlyAnn->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); mixedPainter->setPen( Qt::DashLine ); mixedPainter->drawRect( annotBoundary ); } /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/ if ( enhanceLinks || enhanceImages ) { mixedPainter->save(); mixedPainter->scale( scaledWidth, scaledHeight ); mixedPainter->translate( -crop.left, -crop.top ); QColor normalColor = QApplication::palette().color( QPalette::Active, QPalette::Highlight ); // enlarging limits for intersection is like growing the 'rectGeometry' below QRect limitsEnlarged = limits; limitsEnlarged.adjust( -2, -2, 2, 2 ); // draw rects that are inside the 'limits' paint region as opaque rects QLinkedList< Okular::ObjectRect * >::const_iterator lIt = page->m_rects.constBegin(), lEnd = page->m_rects.constEnd(); for ( ; lIt != lEnd; ++lIt ) { Okular::ObjectRect * rect = *lIt; if ( (enhanceLinks && rect->objectType() == Okular::ObjectRect::Action) || (enhanceImages && rect->objectType() == Okular::ObjectRect::Image) ) { if ( limitsEnlarged.intersects( rect->boundingRect( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ) ) ) { mixedPainter->strokePath( rect->region(), QPen( normalColor, 0 ) ); } } } mixedPainter->restore(); } /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/ if ( useBackBuffer ) { delete mixedPainter; destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap ); delete backPixmap; } // delete object containers delete bufferedHighlights; delete bufferedAnnotations; delete unbufferedAnnotations; } /** Private Helpers :: Pixmap conversion **/ void PagePainter::cropPixmapOnImage( QImage & dest, const QPixmap * src, const QRect & r ) { qreal dpr = src->devicePixelRatioF(); // handle quickly the case in which the whole pixmap has to be converted if ( r == QRect( 0, 0, src->width() / dpr, src->height() / dpr ) ) { dest = src->toImage(); dest = dest.convertToFormat(QImage::Format_ARGB32_Premultiplied); } // else copy a portion of the src to an internal pixmap (smaller) and convert it else { QImage croppedImage( r.width() * dpr, r.height() * dpr, QImage::Format_ARGB32_Premultiplied ); croppedImage.setDevicePixelRatio(dpr); QPainter p( &croppedImage ); p.drawPixmap( 0, 0, *src, r.left(), r.top(), r.width(), r.height() ); p.end(); dest = croppedImage; } } void PagePainter::recolor(QImage *image, const QColor &foreground, const QColor &background) { if (image->format() != QImage::Format_ARGB32_Premultiplied) { qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); } Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); const float scaleRed = background.redF() - foreground.redF(); const float scaleGreen = background.greenF() - foreground.greenF(); const float scaleBlue = background.blueF() - foreground.blueF(); for (int y=0; yheight(); y++) { QRgb *pixels = reinterpret_cast(image->scanLine(y)); for (int x=0; xwidth(); x++) { const int lightness = qGray(pixels[x]); pixels[x] = qRgba(scaleRed * lightness + foreground.red(), scaleGreen * lightness + foreground.green(), scaleBlue * lightness + foreground.blue(), qAlpha(pixels[x])); } } } /** Private Helpers :: Image Drawing **/ // from Arthur - qt4 static inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; } void PagePainter::changeImageAlpha( QImage & image, unsigned int destAlpha ) { // iterate over all pixels changing the alpha component value unsigned int * data = (unsigned int *)image.bits(); unsigned int pixels = image.width() * image.height(); int source, sourceAlpha; for( unsigned int i = 0; i < pixels; ++i ) { // optimize this loop keeping byte order into account source = data[i]; if ( (sourceAlpha = qAlpha( source )) == 255 ) { // use destAlpha data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), destAlpha ); } else { // use destAlpha * sourceAlpha product sourceAlpha = qt_div_255( destAlpha * sourceAlpha ); data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), sourceAlpha ); } } } void PagePainter::drawShapeOnImage( QImage & image, const NormalizedPath & normPath, bool closeShape, const QPen & pen, const QBrush & brush, double penWidthMultiplier, RasterOperation op //float antiAliasRadius ) { // safety checks int pointsNumber = normPath.size(); if ( pointsNumber < 2 ) return; int imageWidth = image.width(); int imageHeight = image.height(); double fImageWidth = (double)imageWidth; double fImageHeight = (double)imageHeight; // stroke outline double penWidth = (double)pen.width() * penWidthMultiplier; QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); QPen pen2 = pen; pen2.setWidthF(penWidth); painter.setPen(pen2); painter.setBrush(brush); if (op == Multiply) { painter.setCompositionMode(QPainter::CompositionMode_Multiply); } if ( brush.style() == Qt::NoBrush ) { // create a polygon QPolygonF poly( closeShape ? pointsNumber + 1 : pointsNumber ); for ( int i = 0; i < pointsNumber; ++i ) { poly[ i ] = QPointF( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) poly[ pointsNumber ] = poly[ 0 ]; painter.drawPolyline( poly ); } else { // create a 'path' QPainterPath path; path.setFillRule( Qt::WindingFill ); path.moveTo( normPath[ 0 ].x * fImageWidth, normPath[ 0 ].y * fImageHeight ); for ( int i = 1; i < pointsNumber; i++ ) { path.lineTo( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) path.closeSubpath(); painter.drawPath( path ); } } void PagePainter::drawEllipseOnImage( QImage & image, const NormalizedPath & rect, const QPen & pen, const QBrush & brush, double penWidthMultiplier, RasterOperation op ) { const double fImageWidth = (double) image.width(); const double fImageHeight = (double) image.height(); // stroke outline const double penWidth = (double)pen.width() * penWidthMultiplier; QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); QPen pen2 = pen; pen2.setWidthF(penWidth); painter.setPen(pen2); painter.setBrush(brush); if ( op == Multiply ) { painter.setCompositionMode(QPainter::CompositionMode_Multiply); } const QPointF &topLeft { rect[0].x * fImageWidth, rect[0].y * fImageHeight }; const QSizeF &size { (rect[1].x - rect[0].x) * fImageWidth, (rect[1].y - rect[0].y) * fImageHeight }; const QRectF imgRect { topLeft, size }; if ( brush.style() == Qt::NoBrush ) { painter.drawArc( imgRect, 0, 16*360 ); } else { painter.drawEllipse( imgRect ); } } LineAnnotPainter::LineAnnotPainter( const Okular::LineAnnotation * a, QSizeF pageSize, double pageScale, const QTransform &toNormalizedImage ) : la { a } , pageSize { pageSize } , pageScale { pageScale } , toNormalizedImage { toNormalizedImage } , aspectRatio { pageSize.height() / pageSize.width() } , linePen { buildPen( a, a->style().width(), a->style().color() ) } { if ( ( la->lineClosed() || la->transformedLinePoints().count() == 2 ) && la->lineInnerColor().isValid() ) { fillBrush = QBrush( la->lineInnerColor() ); } } void LineAnnotPainter::draw( QImage &image ) const { if ( la->transformedLinePoints().count() == 2 ) { const Okular::NormalizedPoint delta { la->transformedLinePoints().last().x - la->transformedLinePoints().first().x, la->transformedLinePoints().first().y - la->transformedLinePoints().last().y }; const double angle { atan2( delta.y * aspectRatio, delta.x ) }; const double cosA { cos( -angle ) }; const double sinA { sin( -angle ) }; const QTransform tmpMatrix = QTransform { cosA, sinA / aspectRatio, -sinA, cosA / aspectRatio, la->transformedLinePoints().first().x, la->transformedLinePoints().first().y }; const double deaspectedY { delta.y * aspectRatio }; const double mainSegmentLength { sqrt( delta.x * delta.x + deaspectedY * deaspectedY ) }; const double lineendSize { std::min( 6. * la->style().width() / pageSize.width(), mainSegmentLength / 2. ) }; drawShortenedLine( mainSegmentLength, lineendSize, image, tmpMatrix ); drawLineEnds( mainSegmentLength, lineendSize, image, tmpMatrix ); drawLeaderLine( 0., image, tmpMatrix ); drawLeaderLine( mainSegmentLength, image, tmpMatrix ); } else if ( la->transformedLinePoints().count() > 2 ) { drawMainLine( image ); } } void LineAnnotPainter::drawMainLine( QImage &image ) const { // draw the line as normalized path into image PagePainter::drawShapeOnImage( image, transformPath( la->transformedLinePoints(), toNormalizedImage ), la->lineClosed(), linePen, fillBrush, pageScale, PagePainter::Multiply ); } void LineAnnotPainter::drawShortenedLine( double mainSegmentLength, double size, QImage &image, const QTransform& toNormalizedPage ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const QList path { { shortenForArrow(size, la->lineStartStyle()), 0 }, { mainSegmentLength - shortenForArrow(size, la->lineEndStyle()), 0 } }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), la->lineClosed(), linePen, fillBrush, pageScale, PagePainter::Multiply ); } void LineAnnotPainter::drawLineEnds( double mainSegmentLength, double size, QImage &image, const QTransform& transform ) const { switch ( la->lineStartStyle() ) { case Okular::LineAnnotation::Square: drawLineEndSquare( 0, -size, transform, image ); break; case Okular::LineAnnotation::Circle: drawLineEndCircle( 0, -size, transform, image ); break; case Okular::LineAnnotation::Diamond: drawLineEndDiamond( 0, -size, transform, image ); break; case Okular::LineAnnotation::OpenArrow: drawLineEndArrow( 0, -size, 1., false, transform, image ); break; case Okular::LineAnnotation::ClosedArrow: drawLineEndArrow( 0, -size, 1., true, transform, image ); break; case Okular::LineAnnotation::None: break; case Okular::LineAnnotation::Butt: drawLineEndButt( 0, size, transform, image ); break; case Okular::LineAnnotation::ROpenArrow: drawLineEndArrow( 0, size, 1., false, transform, image ); break; case Okular::LineAnnotation::RClosedArrow: drawLineEndArrow( 0, size, 1., true, transform, image ); break; case Okular::LineAnnotation::Slash: drawLineEndSlash( 0, -size, transform, image ); break; } switch ( la->lineEndStyle() ) { case Okular::LineAnnotation::Square: drawLineEndSquare( mainSegmentLength, size, transform, image ); break; case Okular::LineAnnotation::Circle: drawLineEndCircle( mainSegmentLength, size, transform, image ); break; case Okular::LineAnnotation::Diamond: drawLineEndDiamond( mainSegmentLength, size, transform, image ); break; case Okular::LineAnnotation::OpenArrow: drawLineEndArrow( mainSegmentLength, size, 1., false, transform, image ); break; case Okular::LineAnnotation::ClosedArrow: drawLineEndArrow( mainSegmentLength, size, 1., true, transform, image ); break; case Okular::LineAnnotation::None: break; case Okular::LineAnnotation::Butt: drawLineEndButt( mainSegmentLength, size, transform, image ); break; case Okular::LineAnnotation::ROpenArrow: drawLineEndArrow( mainSegmentLength, size, -1., false, transform, image ); break; case Okular::LineAnnotation::RClosedArrow: drawLineEndArrow( mainSegmentLength, size, -1., true, transform, image ); break; case Okular::LineAnnotation::Slash: drawLineEndSlash( mainSegmentLength, size, transform, image ); break; } } void LineAnnotPainter::drawLineEndArrow( double xEndPos, double size, double flipX, bool close, const QTransform& toNormalizedPage, QImage &image ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const QList path { { xEndPos - size * flipX, size / 2. }, { xEndPos, 0 }, { xEndPos - size * flipX, -size / 2. }, }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), close, linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLineEndButt( double xEndPos, double size, const QTransform& toNormalizedPage, QImage &image ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const double halfSize { size / 2. }; const QList path { { xEndPos, halfSize }, { xEndPos, -halfSize }, }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLineEndCircle( double xEndPos, double size, const QTransform& toNormalizedPage, QImage &image ) const { /* transform the circle midpoint to intermediate normalized coordinates * where it's easy to construct the bounding rect of the circle */ Okular::NormalizedPoint center; toNormalizedPage.map( xEndPos - size / 2., 0, ¢er.x, ¢er.y ); const double halfSize { size / 2. }; const QList path { { center.x - halfSize, center.y - halfSize / aspectRatio }, { center.x + halfSize, center.y + halfSize / aspectRatio }, }; /* then transform bounding rect with toNormalizedImage */ PagePainter::drawEllipseOnImage( image, transformPath(path, toNormalizedImage), linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLineEndSquare( double xEndPos, double size, const QTransform& toNormalizedPage, QImage &image ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const QList path { { xEndPos, size / 2. }, { xEndPos - size, size / 2. }, { xEndPos - size, -size / 2. }, { xEndPos, -size / 2. } }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLineEndDiamond( double xEndPos, double size, const QTransform& toNormalizedPage, QImage &image ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const QList path { { xEndPos, 0 }, { xEndPos - size / 2., size / 2. }, { xEndPos - size, 0 }, { xEndPos - size / 2., -size / 2. } }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLineEndSlash( double xEndPos, double size, const QTransform& toNormalizedPage, QImage &image ) const { const QTransform combinedTransform { toNormalizedPage * toNormalizedImage }; const double halfSize { size / 2. }; const double xOffset { cos(M_PI/3.) * halfSize }; const QList path { { xEndPos - xOffset, halfSize }, { xEndPos + xOffset, -halfSize }, }; PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), true, linePen, fillBrush, pageScale, PagePainter::Multiply); } void LineAnnotPainter::drawLeaderLine( double xEndPos, QImage &image, const QTransform& toNormalizedPage ) const { const QTransform combinedTransform = toNormalizedPage * toNormalizedImage; const double ll = aspectRatio * la->lineLeadingForwardPoint() / pageSize.height(); const double lle = aspectRatio * la->lineLeadingBackwardPoint() / pageSize.height(); const int sign { ll > 0 ? -1 : 1 }; QList path; if ( fabs( ll ) > 0 ) { path.append( { xEndPos, ll } ); // do we have the extension on the "back"? if ( fabs( lle ) > 0 ) { path.append( { xEndPos, sign * lle } ); } else { path.append( { xEndPos, 0 } ); } } PagePainter::drawShapeOnImage( image, transformPath(path, combinedTransform), false, linePen, fillBrush, pageScale, PagePainter::Multiply); } double LineAnnotPainter::shortenForArrow( double size, Okular::LineAnnotation::TermStyle endStyle ) { double shortenBy { 0 }; if ( endStyle == Okular::LineAnnotation::Square || endStyle == Okular::LineAnnotation::Circle || endStyle == Okular::LineAnnotation::Diamond || endStyle == Okular::LineAnnotation::ClosedArrow ) { shortenBy = size; } return shortenBy; } /* kate: replace-tabs on; indent-width 4; */ diff --git a/ui/pageviewannotator.cpp b/ui/pageviewannotator.cpp index 5e353d7e3..d2f12dfb7 100644 --- a/ui/pageviewannotator.cpp +++ b/ui/pageviewannotator.cpp @@ -1,1283 +1,1283 @@ /*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "pageviewannotator.h" // qt / kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // system includes #include #include #include // local includes #include "core/area.h" #include "core/document.h" #include "core/page.h" #include "core/annotations.h" #include "settings.h" #include "annotationtools.h" #include "guiutils.h" #include "pageview.h" #include "debug_ui.h" /** @short PickPointEngine */ class PickPointEngine : public AnnotatorEngine { public: PickPointEngine( const QDomElement & engineElement ) : AnnotatorEngine( engineElement ), clicked( false ), xscale( 1.0 ), yscale( 1.0 ) { // parse engine specific attributes hoverIconName = engineElement.attribute( QStringLiteral("hoverIcon") ); iconName = m_annotElement.attribute( QStringLiteral("icon") ); if ( m_annotElement.attribute( QStringLiteral("type") ) == QLatin1String("Stamp") && !iconName.simplified().isEmpty() ) hoverIconName = iconName; center = QVariant( engineElement.attribute( QStringLiteral("center") ) ).toBool(); bool ok = true; size = engineElement.attribute( QStringLiteral("size"), QStringLiteral("32") ).toInt( &ok ); if ( !ok ) size = 32; m_block = QVariant( engineElement.attribute( QStringLiteral("block") ) ).toBool(); // create engine objects if ( !hoverIconName.simplified().isEmpty() ) - pixmap = GuiUtils::loadStamp( hoverIconName, QSize( size, size ) ); + pixmap = GuiUtils::loadStamp( hoverIconName, size ); } QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * page ) override { xscale=xScale; yscale=yScale; pagewidth = page->width(); pageheight = page->height(); // only proceed if pressing left button if ( button != Left ) return QRect(); // start operation on click if ( type == Press && clicked == false ) { clicked = true; startpoint.x=nX; startpoint.y=nY; } // repaint if moving while pressing else if ( type == Move && clicked == true ) { } // operation finished on release else if ( type == Release && clicked == true ) { m_creationCompleted = true; } else return QRect(); // update variables and extents (zoom invariant rect) point.x = nX; point.y = nY; if ( center ) { rect.left = nX - ( size / ( xScale * 2.0 ) ); rect.top = nY - ( size / ( yScale * 2.0 ) ); } else { rect.left = nX; rect.top = nY; } rect.right = rect.left + size; rect.bottom = rect.top + size; QRect boundrect = rect.geometry( (int)xScale, (int)yScale ).adjusted( 0, 0, 1, 1 ); if ( m_block ) { const Okular::NormalizedRect tmprect( qMin( startpoint.x, point.x ), qMin( startpoint.y, point.y ), qMax( startpoint.x, point.x ), qMax( startpoint.y, point.y ) ); boundrect |= tmprect.geometry( (int)xScale, (int)yScale ).adjusted( 0, 0, 1, 1 ); } return boundrect; } void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ ) override { if ( clicked ) { if ( m_block ) { const QPen origpen = painter->pen(); QPen pen = painter->pen(); pen.setStyle( Qt::DashLine ); painter->setPen( pen ); const Okular::NormalizedRect tmprect( qMin( startpoint.x, point.x ), qMin( startpoint.y, point.y ), qMax( startpoint.x, point.x ), qMax( startpoint.y, point.y ) ); const QRect realrect = tmprect.geometry( (int)xScale, (int)yScale ); painter->drawRect( realrect ); painter->setPen( origpen ); } if ( !pixmap.isNull() ) painter->drawPixmap( QPointF( rect.left * xScale, rect.top * yScale ), pixmap ); } } void addInPlaceTextAnnotation( Okular::Annotation * &ann, const QString &summary, const QString &content, Okular::TextAnnotation::InplaceIntent inplaceIntent ) { Okular::TextAnnotation * ta = new Okular::TextAnnotation(); ann = ta; ta->setFlags( ta->flags() | Okular::Annotation::FixedRotation ); ta->setContents( content ); ta->setTextType( Okular::TextAnnotation::InPlace ); ta->setInplaceIntent( inplaceIntent ); //set alignment if ( m_annotElement.hasAttribute( QStringLiteral("align") ) ) ta->setInplaceAlignment( m_annotElement.attribute( QStringLiteral("align") ).toInt() ); //set font if ( m_annotElement.hasAttribute( QStringLiteral("font") ) ) { QFont f; f.fromString( m_annotElement.attribute( QStringLiteral("font") ) ); ta->setTextFont( f ); } // set font color if ( m_annotElement.hasAttribute( QStringLiteral("textColor") ) ) { if ( inplaceIntent == Okular::TextAnnotation::TypeWriter ) ta->setTextColor( m_annotElement.attribute( QStringLiteral("textColor") ) ); else ta->setTextColor( Qt::black ); } //set width if ( m_annotElement.hasAttribute( QStringLiteral ( "width" ) ) ) { ta->style().setWidth( m_annotElement.attribute( QStringLiteral ( "width" ) ).toDouble() ); } //set boundary rect.left = qMin(startpoint.x,point.x); rect.top = qMin(startpoint.y,point.y); rect.right = qMax(startpoint.x,point.x); rect.bottom = qMax(startpoint.y,point.y); qCDebug(OkularUiDebug).nospace() << "xyScale=" << xscale << "," << yscale; static const int padding = 2; const QFontMetricsF mf(ta->textFont()); const QRectF rcf = mf.boundingRect( Okular::NormalizedRect( rect.left, rect.top, 1.0, 1.0 ).geometry( (int)pagewidth, (int)pageheight ).adjusted( padding, padding, -padding, -padding ), Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap, ta->contents() ); rect.right = qMax(rect.right, rect.left+(rcf.width()+padding*2)/pagewidth); rect.bottom = qMax(rect.bottom, rect.top+(rcf.height()+padding*2)/pageheight); ta->window().setSummary( summary ); } QList< Okular::Annotation* > end() override { // find out annotation's description node if ( m_annotElement.isNull() ) { m_creationCompleted = false; clicked = false; return QList< Okular::Annotation* >(); } // find out annotation's type Okular::Annotation * ann = nullptr; const QString typeString = m_annotElement.attribute( QStringLiteral("type") ); // create InPlace TextAnnotation from path if ( typeString == QLatin1String("FreeText") ) { bool resok; const QString content = QInputDialog::getMultiLineText(nullptr, i18n( "New Text Note" ), i18n( "Text of the new note:" ), QString(), &resok); if( resok ) addInPlaceTextAnnotation( ann, i18n("Inline Note"), content, Okular::TextAnnotation::Unknown ); } else if ( typeString == QLatin1String("Typewriter") ) { bool resok; const QString content = QInputDialog::getMultiLineText(nullptr, i18n( "New Text Note" ), i18n( "Text of the new note:" ), QString(), &resok); if( resok ) addInPlaceTextAnnotation( ann, i18n("Typewriter"), content, Okular::TextAnnotation::TypeWriter ); } else if ( typeString == QLatin1String("Text") ) { Okular::TextAnnotation * ta = new Okular::TextAnnotation(); ann = ta; ta->setTextType( Okular::TextAnnotation::Linked ); ta->setTextIcon( iconName ); //ta->window.flags &= ~(Okular::Annotation::Hidden); const double iconhei=0.03; rect.left = point.x; rect.top = point.y; rect.right=rect.left+iconhei; rect.bottom=rect.top+iconhei*xscale/yscale; ta->window().setSummary( i18n( "Pop-up Note" ) ); } // create StampAnnotation from path else if ( typeString == QLatin1String("Stamp") ) { Okular::StampAnnotation * sa = new Okular::StampAnnotation(); ann = sa; sa->setStampIconName( iconName ); // set boundary rect.left = qMin( startpoint.x, point.x ); rect.top = qMin( startpoint.y, point.y ); rect.right = qMax( startpoint.x, point.x ); rect.bottom = qMax( startpoint.y, point.y ); const QRectF rcf = rect.geometry( (int)xscale, (int)yscale ); const int ml = ( rcf.bottomRight() - rcf.topLeft() ).toPoint().manhattanLength(); if ( ml <= QApplication::startDragDistance() ) { - const double stampxscale = size / xscale; - const double stampyscale = size / yscale; + const double stampxscale = pixmap.width() / xscale; + const double stampyscale = pixmap.height() / yscale; if ( center ) { rect.left = point.x - stampxscale / 2; rect.top = point.y - stampyscale / 2; } else { rect.left = point.x; rect.top = point.y; } rect.right = rect.left + stampxscale; rect.bottom = rect.top + stampyscale; } } // create GeomAnnotation else if ( typeString == QLatin1String("GeomSquare") || typeString == QLatin1String("GeomCircle") ) { Okular::GeomAnnotation * ga = new Okular::GeomAnnotation(); ann = ga; // set the type if ( typeString == QLatin1String("GeomSquare") ) ga->setGeometricalType( Okular::GeomAnnotation::InscribedSquare ); else ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle ); if ( m_annotElement.hasAttribute( QStringLiteral("width") ) ) ann->style().setWidth( m_annotElement.attribute( QStringLiteral("width") ).toDouble() ); if ( m_annotElement.hasAttribute( QStringLiteral("innerColor") ) ) ga->setGeometricalInnerColor( QColor( m_annotElement.attribute( QStringLiteral("innerColor") ) ) ); //set boundary rect.left = qMin( startpoint.x, point.x ); rect.top = qMin( startpoint.y, point.y ); rect.right = qMax( startpoint.x, point.x ); rect.bottom = qMax( startpoint.y, point.y ); } m_creationCompleted = false; clicked = false; // safety check if ( !ann ) return QList< Okular::Annotation* >(); // set common attributes ann->style().setColor( m_annotElement.hasAttribute( QStringLiteral("color") ) ? m_annotElement.attribute( QStringLiteral("color") ) : m_engineColor ); if ( m_annotElement.hasAttribute( QStringLiteral("opacity") ) ) ann->style().setOpacity( m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble() ); // set the bounding rectangle, and make sure that the newly created // annotation lies within the page by translating it if necessary if ( rect.right > 1 ) { rect.left -= rect.right - 1; rect.right = 1; } if ( rect.bottom > 1 ) { rect.top -= rect.bottom - 1; rect.bottom = 1; } ann->setBoundingRectangle( rect ); // return annotation return QList< Okular::Annotation* >() << ann; } private: bool clicked; Okular::NormalizedRect rect; Okular::NormalizedPoint startpoint; Okular::NormalizedPoint point; QPixmap pixmap; QString hoverIconName, iconName; int size; double xscale,yscale; double pagewidth, pageheight; bool center; bool m_block; }; /** @short PolyLineEngine */ class PolyLineEngine : public AnnotatorEngine { public: PolyLineEngine( const QDomElement & engineElement ) : AnnotatorEngine( engineElement ), last( false ) { // parse engine specific attributes m_block = engineElement.attribute( QStringLiteral("block") ) == QLatin1String("true"); bool ok = true; // numofpoints represents the max number of points for the current // polygon/polyline, with a pair of exceptions: // -1 means: the polyline must close on the first point (polygon) // 0 means: construct as many points as you want, right-click // to construct the last point numofpoints = engineElement.attribute( QStringLiteral("points") ).toInt( &ok ); if ( !ok ) numofpoints = -1; } QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/ ) override { // only proceed if pressing left button // if ( button != Left ) // return rect; // start operation if ( type == Press ) { newPoint.x = nX; newPoint.y = nY; if ( button == Right ) last = true; } // move the second point else if ( type == Move ) { movingpoint.x = nX; movingpoint.y = nY; const QRect oldmovingrect = movingrect; movingrect = rect | QRect( (int)( movingpoint.x * xScale ), (int)( movingpoint.y * yScale ), 1, 1 ); return oldmovingrect | movingrect; } else if ( type == Release ) { const Okular::NormalizedPoint tmppoint(nX, nY); if ( fabs( tmppoint.x - newPoint.x ) + fabs( tmppoint.y - newPoint.y ) > 1e-2 ) return rect; if ( numofpoints == -1 && points.count() > 1 && ( fabs( points[0].x - newPoint.x ) + fabs( points[0].y - newPoint.y ) < 1e-2 ) ) { last = true; } else { points.append( newPoint ); rect |= QRect( (int)( newPoint.x * xScale ), (int)( newPoint.y * yScale ), 1, 1 ); } // end creation if we have constructed the last point of enough points if ( last || points.count() == numofpoints ) { m_creationCompleted = true; last = false; normRect = Okular::NormalizedRect( rect, xScale, yScale ); } } return rect; } void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ ) override { if ( points.count() < 1 ) return; if ( m_block && points.count() == 2 ) { const Okular::NormalizedPoint first = points[0]; const Okular::NormalizedPoint second = points[1]; // draw a semitransparent block around the 2 points painter->setPen( m_engineColor ); painter->setBrush( QBrush( m_engineColor.lighter(), Qt::Dense4Pattern ) ); painter->drawRect( (int)(first.x * (double)xScale), (int)(first.y * (double)yScale), (int)((second.x - first.x) * (double)xScale), (int)((second.y - first.y) * (double)yScale) ); } else { // draw a polyline that connects the constructed points painter->setPen( QPen( m_engineColor, 2 ) ); for ( int i = 1; i < points.count(); ++i ) painter->drawLine( (int)(points[i - 1].x * (double)xScale), (int)(points[i - 1].y * (double)yScale), (int)(points[i].x * (double)xScale), (int)(points[i].y * (double)yScale) ); painter->drawLine( (int)(points.last().x * (double)xScale), (int)(points.last().y * (double)yScale), (int)(movingpoint.x * (double)xScale), (int)(movingpoint.y * (double)yScale) ); } } QList< Okular::Annotation* > end() override { m_creationCompleted = false; // find out annotation's description node if ( m_annotElement.isNull() ) return QList< Okular::Annotation* >(); // find out annotation's type Okular::Annotation * ann = nullptr; const QString typeString = m_annotElement.attribute( QStringLiteral("type") ); // create LineAnnotation from path if ( typeString == QLatin1String("Line") || typeString == QLatin1String("Polyline") || typeString == QLatin1String("Polygon") ) { if ( points.count() < 2 ) return QList< Okular::Annotation* >(); //add note Okular::LineAnnotation * la = new Okular::LineAnnotation(); ann = la; QLinkedList list; for ( int i = 0; i < points.count(); ++i ) list.append( points[ i ] ); la->setLinePoints( list ); if ( numofpoints == -1 ) { la->setLineClosed( true ); if ( m_annotElement.hasAttribute( QStringLiteral("innerColor") ) ) la->setLineInnerColor( QColor( m_annotElement.attribute( QStringLiteral("innerColor") ) ) ); } else if ( numofpoints == 2 ) { if ( m_annotElement.hasAttribute( QStringLiteral("leadFwd") ) ) la->setLineLeadingForwardPoint( m_annotElement.attribute( QStringLiteral("leadFwd") ).toDouble() ); if ( m_annotElement.hasAttribute( QStringLiteral("leadBack") ) ) la->setLineLeadingBackwardPoint( m_annotElement.attribute( QStringLiteral("leadBack") ).toDouble() ); } if ( m_annotElement.hasAttribute( QStringLiteral("startStyle") ) ) la->setLineStartStyle( (Okular::LineAnnotation::TermStyle)m_annotElement.attribute( QStringLiteral("startStyle") ).toInt() ); if ( m_annotElement.hasAttribute( QStringLiteral("endStyle") ) ) la->setLineEndStyle( (Okular::LineAnnotation::TermStyle)m_annotElement.attribute( QStringLiteral("endStyle") ).toInt() ); la->setBoundingRectangle( normRect ); } // safety check if ( !ann ) return QList< Okular::Annotation* >(); if ( m_annotElement.hasAttribute( QStringLiteral("width") ) ) ann->style().setWidth( m_annotElement.attribute( QStringLiteral("width") ).toDouble() ); // set common attributes ann->style().setColor( m_annotElement.hasAttribute( QStringLiteral("color") ) ? m_annotElement.attribute( QStringLiteral("color") ) : m_engineColor ); if ( m_annotElement.hasAttribute( QStringLiteral("opacity") ) ) ann->style().setOpacity( m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble() ); // return annotation return QList< Okular::Annotation* >() << ann; } private: QList points; Okular::NormalizedPoint newPoint; Okular::NormalizedPoint movingpoint; QRect rect; QRect movingrect; Okular::NormalizedRect normRect; bool m_block; bool last; int numofpoints; }; /** @short TextSelectorEngine */ class TextSelectorEngine : public AnnotatorEngine { public: TextSelectorEngine( const QDomElement & engineElement, PageView * pageView ) : AnnotatorEngine( engineElement ), m_pageView( pageView ) { // parse engine specific attributes } QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/ ) override { // only proceed if pressing left button if ( button != Left ) return QRect(); if ( type == Press ) { lastPoint.x = nX; lastPoint.y = nY; const QRect oldrect = rect; rect = QRect(); return oldrect; } else if ( type == Move ) { if ( item() ) { const QPoint start( (int)( lastPoint.x * item()->uncroppedWidth() ), (int)( lastPoint.y * item()->uncroppedHeight() ) ); const QPoint end( (int)( nX * item()->uncroppedWidth() ), (int)( nY * item()->uncroppedHeight() ) ); selection.reset(); std::unique_ptr newselection( m_pageView->textSelectionForItem( item(), start, end ) ); if ( newselection && !newselection->isEmpty() ) { const QList geom = newselection->geometry( (int)xScale, (int)yScale ); QRect newrect; Q_FOREACH ( const QRect& r, geom ) { if ( newrect.isNull() ) newrect = r; else newrect |= r; } rect |= newrect; selection = std::move(newselection); } } } else if ( type == Release && selection ) { m_creationCompleted = true; } return rect; } void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ ) override { if ( selection ) { painter->setPen( Qt::NoPen ); QColor col = m_engineColor; col.setAlphaF( 0.5 ); painter->setBrush( col ); foreach( const Okular::NormalizedRect & r, *selection ) { painter->drawRect( r.geometry( (int)xScale, (int)yScale ) ); } } } QList< Okular::Annotation* > end() override { m_creationCompleted = false; // safety checks if ( m_annotElement.isNull() || !selection ) return QList< Okular::Annotation* >(); // find out annotation's type Okular::Annotation * ann = nullptr; const QString typeString = m_annotElement.attribute( QStringLiteral("type") ); Okular::HighlightAnnotation::HighlightType type = Okular::HighlightAnnotation::Highlight; bool typevalid = false; // create HighlightAnnotation's from the selected area if ( typeString == QLatin1String("Highlight") ) { type = Okular::HighlightAnnotation::Highlight; typevalid = true; } else if ( typeString == QLatin1String("Squiggly") ) { type = Okular::HighlightAnnotation::Squiggly; typevalid = true; } else if ( typeString == QLatin1String("Underline") ) { type = Okular::HighlightAnnotation::Underline; typevalid = true; } else if ( typeString == QLatin1String("StrikeOut") ) { type = Okular::HighlightAnnotation::StrikeOut; typevalid = true; } if ( typevalid ) { Okular::HighlightAnnotation * ha = new Okular::HighlightAnnotation(); ha->setHighlightType( type ); ha->setBoundingRectangle( Okular::NormalizedRect( rect, item()->uncroppedWidth(), item()->uncroppedHeight() ) ); foreach ( const Okular::NormalizedRect & r, *selection ) { Okular::HighlightAnnotation::Quad q; q.setCapStart( false ); q.setCapEnd( false ); q.setFeather( 1.0 ); q.setPoint( Okular::NormalizedPoint( r.left, r.bottom ), 0 ); q.setPoint( Okular::NormalizedPoint( r.right, r.bottom ), 1 ); q.setPoint( Okular::NormalizedPoint( r.right, r.top ), 2 ); q.setPoint( Okular::NormalizedPoint( r.left, r.top ), 3 ); ha->highlightQuads().append( q ); } ann = ha; } selection.reset(); // safety check if ( !ann ) return QList< Okular::Annotation* >(); // set common attributes ann->style().setColor( m_annotElement.hasAttribute( QStringLiteral("color") ) ? m_annotElement.attribute( QStringLiteral("color") ) : m_engineColor ); if ( m_annotElement.hasAttribute( QStringLiteral("opacity") ) ) ann->style().setOpacity( m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble() ); // return annotations return QList< Okular::Annotation* >() << ann; } QCursor cursor() const override { return Qt::IBeamCursor; } private: // data PageView * m_pageView; // TODO: support more pages std::unique_ptr selection; Okular::NormalizedPoint lastPoint; QRect rect; }; /** PageViewAnnotator **/ PageViewAnnotator::PageViewAnnotator( PageView * parent, Okular::Document * storage ) : QObject( parent ), m_document( storage ), m_pageView( parent ), m_toolBar( nullptr ), m_engine( nullptr ), m_textToolsEnabled( false ), m_toolsEnabled( false ), m_continuousMode( false ), m_hidingWasForced( false ), m_lastToolID( -1 ), m_lockedItem( nullptr ) { reparseConfig(); } void PageViewAnnotator::reparseConfig() { m_items.clear(); // Read tool list from configuration. It's a list of XML elements const QStringList userTools = Okular::Settings::annotationTools(); // Populate m_toolsDefinition QDomDocument doc; m_toolsDefinition = doc.createElement( QStringLiteral("annotatingTools") ); foreach ( const QString &toolXml, userTools ) { QDomDocument entryParser; if ( entryParser.setContent( toolXml ) ) m_toolsDefinition.appendChild( doc.importNode( entryParser.documentElement(), true ) ); else qCWarning(OkularUiDebug) << "Skipping malformed tool XML in AnnotationTools setting"; } // Create the AnnotationToolItems from the XML dom tree QDomNode toolDescription = m_toolsDefinition.firstChild(); while ( toolDescription.isElement() ) { QDomElement toolElement = toolDescription.toElement(); if ( toolElement.tagName() == QLatin1String("tool") ) { AnnotationToolItem item; item.id = toolElement.attribute(QStringLiteral("id")).toInt(); if ( toolElement.hasAttribute( QStringLiteral("name") ) ) item.text = toolElement.attribute( QStringLiteral("name") ); else item.text = defaultToolName( toolElement ); item.pixmap = makeToolPixmap( toolElement ); QDomNode shortcutNode = toolElement.elementsByTagName( QStringLiteral("shortcut") ).item( 0 ); if ( shortcutNode.isElement() ) item.shortcut = shortcutNode.toElement().text(); QDomNodeList engineNodeList = toolElement.elementsByTagName( QStringLiteral("engine") ); if ( engineNodeList.size() > 0 ) { QDomElement engineEl = engineNodeList.item( 0 ).toElement(); if ( !engineEl.isNull() && engineEl.hasAttribute( QStringLiteral("type") ) ) item.isText = engineEl.attribute( QStringLiteral("type") ) == QLatin1String( "TextSelector" ); } m_items.push_back( item ); } toolDescription = toolDescription.nextSibling(); } } PageViewAnnotator::~PageViewAnnotator() { delete m_engine; } void PageViewAnnotator::setEnabled( bool on ) { if ( !on ) { // remove toolBar if ( m_toolBar ) m_toolBar->hideAndDestroy(); m_toolBar = nullptr; // deactivate the active tool, if any slotToolSelected( -1 ); return; } // if no tools are defined, don't show the toolbar if ( !m_toolsDefinition.hasChildNodes() ) return; // create toolBar if ( !m_toolBar ) { m_toolBar = new PageViewToolBar( m_pageView, m_pageView->viewport() ); m_toolBar->setSide( (PageViewToolBar::Side)Okular::Settings::editToolBarPlacement() ); m_toolBar->setItems( m_items ); m_toolBar->setToolsEnabled( m_toolsEnabled ); m_toolBar->setTextToolsEnabled( m_textToolsEnabled ); connect(m_toolBar, &PageViewToolBar::toolSelected, this, &PageViewAnnotator::slotToolSelected); connect(m_toolBar, &PageViewToolBar::orientationChanged, this, &PageViewAnnotator::slotSaveToolbarOrientation); connect(m_toolBar, &PageViewToolBar::buttonDoubleClicked, this, &PageViewAnnotator::slotToolDoubleClicked); m_toolBar->setCursor(Qt::ArrowCursor); } // show the toolBar m_toolBar->showAndAnimate(); } void PageViewAnnotator::setTextToolsEnabled( bool enabled ) { m_textToolsEnabled = enabled; if ( m_toolBar ) m_toolBar->setTextToolsEnabled( m_textToolsEnabled ); } void PageViewAnnotator::setToolsEnabled( bool enabled ) { m_toolsEnabled = enabled; if ( m_toolBar ) m_toolBar->setToolsEnabled( m_toolsEnabled ); } void PageViewAnnotator::setHidingForced( bool forced ) { m_hidingWasForced = forced; } bool PageViewAnnotator::hidingWasForced() const { return m_hidingWasForced; } bool PageViewAnnotator::active() const { return m_engine && m_toolBar; } bool PageViewAnnotator::annotating() const { return active() && m_lockedItem; } QCursor PageViewAnnotator::cursor() const { return m_engine->cursor(); } QRect PageViewAnnotator::performRouteMouseOrTabletEvent(const AnnotatorEngine::EventType & eventType, const AnnotatorEngine::Button & button, const QPointF & pos, PageViewItem * item ) { // if the right mouse button was pressed, we simply do nothing. In this way, we are still editing the annotation // and so this function will receive and process the right mouse button release event too. If we detach now the annotation tool, // the release event will be processed by the PageView class which would create the annotation property widget, and we do not want this. if ( button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Press ) return QRect(); else if ( button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Release ) { detachAnnotation(); return QRect(); } // 1. lock engine to current item if ( !m_lockedItem && eventType == AnnotatorEngine::Press ) { m_lockedItem = item; m_engine->setItem( m_lockedItem ); } if ( !m_lockedItem ) { return QRect(); } // find out normalized mouse coords inside current item const QRect & itemRect = m_lockedItem->uncroppedGeometry(); const QPointF eventPos = m_pageView->contentAreaPoint( pos ); const double nX = qBound( 0.0, m_lockedItem->absToPageX( eventPos.x() ), 1.0 ); const double nY = qBound( 0.0, m_lockedItem->absToPageY( eventPos.y() ), 1.0 ); QRect modifiedRect; // 2. use engine to perform operations const QRect paintRect = m_engine->event( eventType, button, nX, nY, itemRect.width(), itemRect.height(), m_lockedItem->page() ); // 3. update absolute extents rect and send paint event(s) if ( paintRect.isValid() ) { // 3.1. unite old and new painting regions QRegion compoundRegion( m_lastDrawnRect ); m_lastDrawnRect = paintRect; m_lastDrawnRect.translate( itemRect.left(), itemRect.top() ); // 3.2. decompose paint region in rects and send paint events const QVector rects = compoundRegion.united( m_lastDrawnRect ).rects(); const QPoint areaPos = m_pageView->contentAreaPosition(); for ( int i = 0; i < rects.count(); i++ ) m_pageView->viewport()->update( rects[i].translated( -areaPos ) ); modifiedRect = compoundRegion.boundingRect() | m_lastDrawnRect; } // 4. if engine has finished, apply Annotation to the page if ( m_engine->creationCompleted() ) { // apply engine data to the Annotation's and reset engine QList< Okular::Annotation* > annotations = m_engine->end(); // attach the newly filled annotations to the page foreach ( Okular::Annotation * annotation, annotations ) { if ( !annotation ) continue; annotation->setCreationDate( QDateTime::currentDateTime() ); annotation->setModificationDate( QDateTime::currentDateTime() ); annotation->setAuthor( Okular::Settings::identityAuthor() ); m_document->addPageAnnotation( m_lockedItem->pageNumber(), annotation ); if ( annotation->openDialogAfterCreation() ) m_pageView->openAnnotationWindow( annotation, m_lockedItem->pageNumber() ); } if ( m_continuousMode ) slotToolSelected( m_lastToolID ); else detachAnnotation(); } return modifiedRect; } QRect PageViewAnnotator::routeMouseEvent( QMouseEvent * e, PageViewItem * item ) { AnnotatorEngine::EventType eventType; AnnotatorEngine::Button button; // figure out the event type and button AnnotatorEngine::decodeEvent( e, &eventType, &button ); return performRouteMouseOrTabletEvent( eventType, button, e->localPos(), item ); } QRect PageViewAnnotator::routeTabletEvent( QTabletEvent * e, PageViewItem * item, const QPoint & localOriginInGlobal ) { // Unlike routeMouseEvent, routeTabletEvent must explicitly ignore events it doesn't care about so that // the corresponding mouse event will later be delivered. if ( !item ) { e->ignore(); return QRect(); } // We set all tablet events that take place over the annotations toolbar to ignore so that corresponding mouse // events will be delivered to the toolbar. However, we still allow the annotations code to handle // TabletMove and TabletRelease events in case the user is drawing an annotation onto the toolbar. const QPoint toolBarPos = m_toolBar->mapFromGlobal( e->globalPos() ); const QRect toolBarRect = m_toolBar->rect(); if ( toolBarRect.contains( toolBarPos ) ) { e->ignore(); if (e->type() == QEvent::TabletPress) return QRect(); } AnnotatorEngine::EventType eventType; AnnotatorEngine::Button button; // figure out the event type and button AnnotatorEngine::decodeEvent( e, &eventType, &button ); const QPointF globalPosF = e->globalPosF(); const QPointF localPosF = globalPosF - localOriginInGlobal; return performRouteMouseOrTabletEvent( eventType, button, localPosF, item ); } bool PageViewAnnotator::routeKeyEvent( QKeyEvent * event ) { if ( event->key() == Qt::Key_Escape ) { detachAnnotation(); return true; } return false; } bool PageViewAnnotator::routePaints( const QRect & wantedRect ) const { return m_engine && m_toolBar && wantedRect.intersects( m_lastDrawnRect ) && m_lockedItem; } void PageViewAnnotator::routePaint( QPainter * painter, const QRect & paintRect ) { // if there's no locked item, then there's no decided place to draw on if ( !m_lockedItem ) return; #ifndef NDEBUG // [DEBUG] draw the paint region if enabled if ( Okular::Settings::debugDrawAnnotationRect() ) painter->drawRect( paintRect ); #endif // move painter to current itemGeometry rect const QRect & itemRect = m_lockedItem->uncroppedGeometry(); painter->save(); painter->translate( itemRect.topLeft() ); // TODO: Clip annotation painting to cropped page. // transform cliprect from absolute to item relative coords QRect annotRect = paintRect.intersected( m_lastDrawnRect ); annotRect.translate( -itemRect.topLeft() ); // use current engine for painting (in virtual page coordinates) m_engine->paint( painter, m_lockedItem->uncroppedWidth(), m_lockedItem->uncroppedHeight(), annotRect ); painter->restore(); } void PageViewAnnotator::slotToolSelected( int toolID ) { // terminate any previous operation if ( m_engine ) { delete m_engine; m_engine = nullptr; } m_lockedItem = nullptr; if ( m_lastDrawnRect.isValid() ) { m_pageView->viewport()->update( m_lastDrawnRect.translated( -m_pageView->contentAreaPosition() ) ); m_lastDrawnRect = QRect(); } if ( toolID != m_lastToolID ) m_continuousMode = false; // store current tool for later usage m_lastToolID = toolID; // handle tool deselection if ( toolID == -1 ) { m_pageView->displayMessage( QString() ); m_pageView->updateCursor(); return; } // for the selected tool create the Engine QDomNode toolNode = m_toolsDefinition.firstChild(); while ( toolNode.isElement() ) { QDomElement toolElement = toolNode.toElement(); toolNode = toolNode.nextSibling(); // only find out the element describing selected tool if ( toolElement.tagName() != QLatin1String("tool") || toolElement.attribute(QStringLiteral("id")).toInt() != toolID ) continue; // parse tool properties QDomNode toolSubNode = toolElement.firstChild(); while ( toolSubNode.isElement() ) { QDomElement toolSubElement = toolSubNode.toElement(); toolSubNode = toolSubNode.nextSibling(); // create the AnnotatorEngine if ( toolSubElement.tagName() == QLatin1String("engine") ) { QString type = toolSubElement.attribute( QStringLiteral("type") ); if ( type == QLatin1String("SmoothLine") ) m_engine = new SmoothPathEngine( toolSubElement ); else if ( type == QLatin1String("PickPoint") ) m_engine = new PickPointEngine( toolSubElement ); else if ( type == QLatin1String("PolyLine") ) m_engine = new PolyLineEngine( toolSubElement ); else if ( type == QLatin1String("TextSelector") ) m_engine = new TextSelectorEngine( toolSubElement, m_pageView ); else qCWarning(OkularUiDebug).nospace() << "tools.xml: engine type:'" << type << "' is not defined!"; } // display the tooltip const QString annotType = toolElement.attribute( QStringLiteral("type") ); QString tip; if ( annotType == QLatin1String("ellipse") ) tip = i18nc( "Annotation tool", "Draw an ellipse (drag to select a zone)" ); else if ( annotType == QLatin1String("highlight") ) tip = i18nc( "Annotation tool", "Highlight text" ); else if ( annotType == QLatin1String("ink") ) tip = i18nc( "Annotation tool", "Draw a freehand line" ); else if ( annotType == QLatin1String("note-inline") ) tip = i18nc( "Annotation tool", "Inline Text Annotation (drag to select a zone)" ); else if ( annotType == QLatin1String("note-linked") ) tip = i18nc( "Annotation tool", "Put a pop-up note" ); else if ( annotType == QLatin1String("polygon") ) tip = i18nc( "Annotation tool", "Draw a polygon (click on the first point to close it)" ); else if ( annotType == QLatin1String("rectangle") ) tip = i18nc( "Annotation tool", "Draw a rectangle" ); else if ( annotType == QLatin1String("squiggly") ) tip = i18nc( "Annotation tool", "Squiggle text" ); else if ( annotType == QLatin1String("stamp") ) tip = i18nc( "Annotation tool", "Put a stamp symbol" ); else if ( annotType == QLatin1String("straight-line") ) tip = i18nc( "Annotation tool", "Draw a straight line" ); else if ( annotType == QLatin1String("strikeout") ) tip = i18nc( "Annotation tool", "Strike out text" ); else if ( annotType == QLatin1String("underline") ) tip = i18nc( "Annotation tool", "Underline text" ); else if ( annotType == QLatin1String("typewriter") ) tip = i18nc( "Annotation tool", "Typewriter Annotation (drag to select a zone)" ); if ( !tip.isEmpty() && !m_continuousMode ) m_pageView->displayMessage( tip, QString(), PageViewMessage::Annotation ); } // consistency warning if ( !m_engine ) { qCWarning(OkularUiDebug) << "tools.xml: couldn't find good engine description. check xml."; } m_pageView->updateCursor(); // stop after parsing selected tool's node break; } } void PageViewAnnotator::slotSaveToolbarOrientation( int side ) { Okular::Settings::setEditToolBarPlacement( (int)side ); Okular::Settings::self()->save(); } void PageViewAnnotator::slotToolDoubleClicked( int /*toolID*/ ) { m_continuousMode = true; } void PageViewAnnotator::detachAnnotation() { m_toolBar->selectButton( -1 ); } QString PageViewAnnotator::defaultToolName( const QDomElement &toolElement ) { const QString annotType = toolElement.attribute( QStringLiteral("type") ); if ( annotType == QLatin1String("ellipse") ) return i18n( "Ellipse" ); else if ( annotType == QLatin1String("highlight") ) return i18n( "Highlighter" ); else if ( annotType == QLatin1String("ink") ) return i18n( "Freehand Line" ); else if ( annotType == QLatin1String("note-inline") ) return i18n( "Inline Note" ); else if ( annotType == QLatin1String("note-linked") ) return i18n( "Pop-up Note" ); else if ( annotType == QLatin1String("polygon") ) return i18n( "Polygon" ); else if ( annotType == QLatin1String("rectangle") ) return i18n( "Rectangle" ); else if ( annotType == QLatin1String("squiggly") ) return i18n( "Squiggle" ); else if ( annotType == QLatin1String("stamp") ) return i18n( "Stamp" ); else if ( annotType == QLatin1String("straight-line") ) return i18n( "Straight Line" ); else if ( annotType == QLatin1String("strikeout") ) return i18n( "Strike out" ); else if ( annotType == QLatin1String("underline") ) return i18n( "Underline" ); else if ( annotType == QLatin1String("typewriter") ) return i18n( "Typewriter" ); else return QString(); } QPixmap PageViewAnnotator::makeToolPixmap( const QDomElement &toolElement ) { QPixmap pixmap( 32 * qApp->devicePixelRatio(), 32 * qApp->devicePixelRatio() ); pixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); const QString annotType = toolElement.attribute( QStringLiteral("type") ); // Load HiDPI variant on HiDPI screen QString imageVariant; if ( qApp->devicePixelRatio() > 1.05 ) { imageVariant = QStringLiteral("@2x"); } // Load base pixmap. We'll draw on top of it pixmap.load( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-base-okular" + imageVariant + ".png") ) ); /* Parse color, innerColor and icon (if present) */ QColor engineColor, innerColor, textColor, annotColor; QString icon; QDomNodeList engineNodeList = toolElement.elementsByTagName( QStringLiteral("engine") ); if ( engineNodeList.size() > 0 ) { QDomElement engineEl = engineNodeList.item( 0 ).toElement(); if ( !engineEl.isNull() && engineEl.hasAttribute( QStringLiteral("color") ) ) engineColor = QColor( engineEl.attribute( QStringLiteral("color") ) ); } QDomNodeList annotationNodeList = toolElement.elementsByTagName( QStringLiteral("annotation") ); if ( annotationNodeList.size() > 0 ) { QDomElement annotationEl = annotationNodeList.item( 0 ).toElement(); if ( !annotationEl.isNull() ) { if ( annotationEl.hasAttribute( QStringLiteral("color") ) ) annotColor = annotationEl.attribute( QStringLiteral("color") ); if ( annotationEl.hasAttribute( QStringLiteral("innerColor") ) ) innerColor = QColor( annotationEl.attribute( QStringLiteral("innerColor") ) ); if ( annotationEl.hasAttribute( QStringLiteral("textColor") ) ) textColor = QColor( annotationEl.attribute( QStringLiteral("textColor") ) ); if ( annotationEl.hasAttribute( QStringLiteral("icon") ) ) icon = annotationEl.attribute( QStringLiteral("icon") ); } } QPainter p( &pixmap ); if ( annotType == QLatin1String("ellipse") ) { p.setRenderHint( QPainter::Antialiasing ); if ( innerColor.isValid() ) p.setBrush( innerColor ); p.setPen( QPen( engineColor, 2 ) ); p.drawEllipse( 2, 7, 21, 14 ); } else if ( annotType == QLatin1String("highlight") ) { QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-highlighter-okular-colorizable" + imageVariant + ".png") ) ); QImage colorizedOverlay = overlay; GuiUtils::colorizeImage( colorizedOverlay, engineColor ); p.drawImage( QPoint(0,0), colorizedOverlay ); // Trail p.drawImage( QPoint(0,-32), overlay ); // Text + Shadow (uncolorized) p.drawImage( QPoint(0,-64), colorizedOverlay ); // Pen } else if ( annotType == QLatin1String("ink") ) { QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-ink-okular-colorizable" + imageVariant + ".png") ) ); QImage colorizedOverlay = overlay; GuiUtils::colorizeImage( colorizedOverlay, engineColor ); p.drawImage( QPoint(0,0), colorizedOverlay ); // Trail p.drawImage( QPoint(0,-32), overlay ); // Shadow (uncolorized) p.drawImage( QPoint(0,-64), colorizedOverlay ); // Pen } else if ( annotType == QLatin1String("note-inline") ) { QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-note-inline-okular-colorizable" + imageVariant + ".png") ) ); GuiUtils::colorizeImage( overlay, engineColor ); p.drawImage( QPoint(0,0), overlay ); } else if ( annotType == QLatin1String("note-linked") ) { QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-note-okular-colorizable" + imageVariant + ".png") ) ); GuiUtils::colorizeImage( overlay, engineColor ); p.drawImage( QPoint(0,0), overlay ); } else if ( annotType == QLatin1String("polygon") ) { QPainterPath path; path.moveTo( 0, 7 ); path.lineTo( 19, 7 ); path.lineTo( 19, 14 ); path.lineTo( 23, 14 ); path.lineTo( 23, 20 ); path.lineTo( 0, 20 ); if ( innerColor.isValid() ) p.setBrush( innerColor ); p.setPen( QPen( engineColor, 1 ) ); p.drawPath( path ); } else if ( annotType == QLatin1String("rectangle") ) { p.setRenderHint( QPainter::Antialiasing ); if ( innerColor.isValid() ) p.setBrush( innerColor ); p.setPen( QPen( engineColor, 2 ) ); p.drawRect( 2, 7, 21, 14 ); } else if ( annotType == QLatin1String("squiggly") ) { QPen pen( engineColor, 1 ); pen.setDashPattern( QVector() << 1 << 1 ); p.setPen( pen ); p.drawLine( 1, 13, 16, 13 ); p.drawLine( 2, 14, 15, 14 ); p.drawLine( 0, 20, 19, 20 ); p.drawLine( 1, 21, 18, 21 ); } else if ( annotType == QLatin1String("stamp") ) { - QPixmap stamp = GuiUtils::loadStamp( icon, QSize( 16, 16 ) ); + QPixmap stamp = GuiUtils::loadStamp( icon, 16, false /* keepAspectRatio */ ); p.setRenderHint( QPainter::Antialiasing ); p.drawPixmap( 16, 14, stamp ); } else if ( annotType == QLatin1String("straight-line") ) { QPainterPath path; path.moveTo( 1, 8 ); path.lineTo( 20, 8 ); path.lineTo( 1, 27 ); path.lineTo( 20, 27 ); p.setRenderHint( QPainter::Antialiasing ); p.setPen( QPen( engineColor, 1 ) ); p.drawPath( path ); // TODO To be discussed: This is not a straight line! } else if ( annotType == QLatin1String("strikeout") ) { p.setPen( QPen( engineColor, 1 ) ); p.drawLine( 1, 10, 16, 10 ); p.drawLine( 0, 17, 19, 17 ); } else if ( annotType == QLatin1String("underline") ) { p.setPen( QPen( engineColor, 1 ) ); p.drawLine( 1, 13, 16, 13 ); p.drawLine( 0, 20, 19, 20 ); } else if ( annotType == QLatin1String("typewriter") ) { QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-typewriter-okular-colorizable" + imageVariant + ".png") ) ); GuiUtils::colorizeImage( overlay, textColor ); p.drawImage( QPoint(-2,2), overlay ); } else { /* Unrecognized annotation type -- It shouldn't happen */ p.setPen( QPen( engineColor ) ); p.drawText( QPoint(20, 31), QStringLiteral("?") ); } return pixmap; } #include "moc_pageviewannotator.cpp" /* kate: replace-tabs on; indent-width 4; */