diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 917e65961..922e1d47c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,54 +1,54 @@ include: - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-before.yml - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-applications-linux.yml build_ubuntu_18_04: stage: build image: ubuntu:bionic only: - merge_requests before_script: - sed -i -e 's/# deb-src/deb-src/g' /etc/apt/sources.list - apt-get update - apt-get build-dep --yes --no-install-recommends okular - apt-get install --yes --no-install-recommends ninja-build libkf5crash-dev script: - mkdir -p build && cd build - cmake -G Ninja .. - ninja build_ubuntu_20_04: stage: build image: ubuntu:focal only: - merge_requests before_script: - sed -i -e 's/# deb-src/deb-src/g' /etc/apt/sources.list - apt-get update - apt-get build-dep --yes --no-install-recommends okular - apt-get install --yes --no-install-recommends ninja-build script: - mkdir -p build && cd build - cmake -DOKULAR_UI=desktop -G Ninja .. - ninja - rm -rf * - cmake -DOKULAR_UI=mobile -G Ninja .. - ninja build_clazy_clang_tidy: stage: build image: debian:unstable only: - merge_requests before_script: - echo 'deb-src http://deb.debian.org/debian unstable main' >> /etc/apt/sources.list - apt-get update - apt-get build-dep --yes --no-install-recommends okular - apt-get install --yes --no-install-recommends ninja-build clazy clang clang-tidy python python-yaml libkf5crash-dev libkf5purpose-dev libegl-dev jq script: - srcdir=`pwd` && mkdir -p /tmp/okular_build && cd /tmp/okular_build && CC=clang CXX=clazy CXXFLAGS="-Werror -Wno-deprecated-declarations" cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja $srcdir && cat compile_commands.json | jq '[.[] | select(.file | contains("'"$srcdir"'"))]' > compile_commands.aux.json && cat compile_commands.aux.json | jq '[.[] | select(.file | contains("/synctex/")| not)]' > compile_commands.json - - CLAZY_CHECKS="qstring-arg,incorrect-emit,qhash-namespace,detaching-temporary,range-loop,qdeleteall,connect-not-normalized,inefficient-qlist-soft,strict-iterators,lambda-in-connect,fully-qualified-moc-types,qstring-ref,unused-non-trivial-variable" ninja + - CLAZY_CHECKS="qstring-arg,incorrect-emit,qhash-namespace,detaching-temporary,range-loop,qdeleteall,connect-not-normalized,inefficient-qlist-soft,strict-iterators,lambda-in-connect,fully-qualified-moc-types,qstring-ref,unused-non-trivial-variable,qcolor-from-literal,qcolor-from-literal" ninja # Fix the poppler header, remove when debian:unstable ships poppler 0.82 or later - sed -i "N;N;N;N; s#class MediaRendition\;\nclass MovieAnnotation\;\nclass ScreenAnnotation;#class MediaRendition\;#g" /usr/include/poppler/qt5/poppler-link.h - "run-clang-tidy -header-filter='.*/okular/.*' -checks='-*,performance-*,bugprone-*,readability-inconsistent-declaration-parameter-name,readability-string-compare,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-make-unique,modernize-make-shared,modernize-use-override,modernize-use-equals-delete,modernize-use-emplace,modernize-loop-convert,modernize-use-nullptr,-bugprone-macro-parentheses,-bugprone-narrowing-conversions,-bugprone-branch-clone,-bugprone-incorrect-roundings' -config=\"{WarningsAsErrors: '*'}\"" diff --git a/generators/ooo/converter.cpp b/generators/ooo/converter.cpp index 159a94f8e..d1ed4eb8d 100644 --- a/generators/ooo/converter.cpp +++ b/generators/ooo/converter.cpp @@ -1,594 +1,594 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 "converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "document.h" #include "styleinformation.h" #include "styleparser.h" using namespace OOO; class Style { public: Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ); QTextBlockFormat blockFormat() const; QTextCharFormat textFormat() const; private: QTextBlockFormat mBlockFormat; QTextCharFormat mTextFormat; }; Style::Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ) : mBlockFormat( blockFormat ), mTextFormat( textFormat ) { } QTextBlockFormat Style::blockFormat() const { return mBlockFormat; } QTextCharFormat Style::textFormat() const { return mTextFormat; } Converter::Converter() : mTextDocument( nullptr ), mCursor( nullptr ), mStyleInformation( nullptr ) { } Converter::~Converter() { } Okular::Document::OpenResult Converter::convertWithPassword( const QString &fileName, const QString &password ) { Document oooDocument( fileName ); if ( !oooDocument.open( password ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( oooDocument.lastErrorString(), -1 ); return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mTextDocument = new QTextDocument; mCursor = new QTextCursor( mTextDocument ); /** * Create the dom of the content */ QXmlSimpleReader reader; QXmlInputSource source; source.setData( oooDocument.content() ); QString errorMsg; QDomDocument document; if ( !document.setContent( &source, &reader, &errorMsg ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Invalid XML document: %1", errorMsg ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mStyleInformation = new StyleInformation(); /** * Read the style properties, so the are available when * parsing the content. */ StyleParser styleParser( &oooDocument, document, mStyleInformation ); if ( !styleParser.parse() ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to read style information" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } /** * Add all images of the document to resource framework */ const QMap images = oooDocument.images(); QMapIterator it( images ); while ( it.hasNext() ) { it.next(); mTextDocument->addResource( QTextDocument::ImageResource, QUrl( it.key() ), QImage::fromData( it.value() ) ); } /** * Set the correct page size */ const QString masterLayout = mStyleInformation->masterPageName(); const PageFormatProperty property = mStyleInformation->pageProperty( masterLayout ); const QSizeF dpi = Okular::Utils::realDpi(nullptr); int pageWidth = qRound(property.width() / 72.0 * dpi.width()); int pageHeight = qRound(property.height() / 72.0 * dpi.height()); if ( pageWidth == 0 ) pageWidth = 600; if ( pageHeight == 0 ) pageHeight = 800; mTextDocument->setPageSize( QSize( pageWidth, pageHeight ) ); QTextFrameFormat frameFormat; frameFormat.setMargin( qRound( property.margin() ) ); QTextFrame *rootFrame = mTextDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); /** * Parse the content of the document */ const QDomElement documentElement = document.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "body" ) ) { if ( !convertBody( element ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to convert document content" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } } element = element.nextSiblingElement(); } MetaInformation::List metaInformation = mStyleInformation->metaInformation(); for ( int i = 0; i < metaInformation.count(); ++i ) { emit addMetaData( metaInformation[ i ].key(), metaInformation[ i ].value(), metaInformation[ i ].title() ); } delete mCursor; delete mStyleInformation; mStyleInformation = nullptr; setDocument( mTextDocument ); return Okular::Document::OpenSuccess; } bool Converter::convertBody( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "text" ) ) { if ( !convertText( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertText( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "p" ) ) { mCursor->insertBlock(); if ( !convertParagraph( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "h" ) ) { mCursor->insertBlock(); if ( !convertHeader( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "list" ) ) { if ( !convertList( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "table" ) ) { if ( !convertTable( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertHeader( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat; QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); cursor->setBlockFormat( blockFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, textFormat ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } emit addTitle( element.attribute( QStringLiteral("outline-level"), QStringLiteral("0") ).toInt(), element.text(), cursor->block() ); return true; } bool Converter::convertParagraph( QTextCursor *cursor, const QDomElement &element, const QTextBlockFormat &parentFormat, bool merge ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat( parentFormat ); QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); if ( merge ) cursor->mergeBlockFormat( blockFormat ); else cursor->setBlockFormat( blockFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, textFormat ) ) return false; } else if ( childElement.tagName() == QLatin1String( "tab" ) ) { mCursor->insertText( QStringLiteral(" ") ); } else if ( childElement.tagName() == QLatin1String( "s" ) ) { QString spaces; spaces.fill( QLatin1Char(' '), childElement.attribute( QStringLiteral("c") ).toInt() ); mCursor->insertText( spaces ); } else if ( childElement.tagName() == QLatin1String( "frame" ) ) { if ( !convertFrame( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "a" ) ) { if ( !convertLink( cursor, childElement, textFormat ) ) return false; } else if ( childElement.tagName() == QLatin1String( "annotation" ) ) { if ( !convertAnnotation( cursor, childElement ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } return true; } bool Converter::convertTextNode( QTextCursor *cursor, const QDomText &element, const QTextCharFormat &format ) { cursor->insertText( element.data(), format ); return true; } bool Converter::convertSpan( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextCharFormat textFormat( format ); property.applyText( &textFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } return true; } bool Converter::convertList( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const ListFormatProperty property = mStyleInformation->listProperty( styleName ); QTextListFormat format; if ( cursor->currentList() ) { // we are in a nested list format = cursor->currentList()->format(); format.setIndent( format.indent() + 1 ); } property.apply( &format, 0 ); QTextList *list = cursor->insertList( format ); QDomElement itemChild = element.firstChildElement(); int loop = 0; while ( !itemChild.isNull() ) { if ( itemChild.tagName() == QLatin1String( "list-item" ) ) { loop++; QDomElement childElement = itemChild.firstChildElement(); while ( !childElement.isNull() ) { QTextBlock prevBlock; if ( childElement.tagName() == QLatin1String( "p" ) ) { if ( loop > 1 ) cursor->insertBlock(); prevBlock = cursor->block(); if ( !convertParagraph( cursor, childElement, QTextBlockFormat(), true ) ) return false; } else if ( childElement.tagName() == QLatin1String( "list" ) ) { prevBlock = cursor->block(); if ( !convertList( cursor, childElement ) ) return false; } if( prevBlock.isValid() ) list->add( prevBlock ); childElement = childElement.nextSiblingElement(); } } itemChild = itemChild.nextSiblingElement(); } return true; } static void enqueueNodeList( QQueue &queue, const QDomNodeList &list ) { for ( int i = 0; i < list.count(); ++i ) { queue.enqueue( list.at( i ) ); } } bool Converter::convertTable( const QDomElement &element ) { /** * Find out dimension of the table */ int rowCounter = 0; int columnCounter = 0; QQueue nodeQueue; enqueueNodeList( nodeQueue, element.childNodes() ); while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { rowCounter++; int counter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { counter++; } columnElement = columnElement.nextSiblingElement(); } columnCounter = qMax( columnCounter, counter ); } else if ( el.tagName() == QLatin1String( "table-header-rows" ) ) { enqueueNodeList( nodeQueue, el.childNodes() ); } } /** * Create table */ QTextTable *table = mCursor->insertTable( rowCounter, columnCounter ); mCursor->movePosition( QTextCursor::End ); /** * Fill table */ nodeQueue.clear(); enqueueNodeList( nodeQueue, element.childNodes() ); QTextTableFormat tableFormat; rowCounter = 0; while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { int columnCounter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( columnElement.attribute( QStringLiteral("style-name") ) ); QTextBlockFormat format; property.applyTableCell( &format ); QDomElement paragraphElement = columnElement.firstChildElement(); while ( !paragraphElement.isNull() ) { if ( paragraphElement.tagName() == QLatin1String( "p" ) ) { QTextTableCell cell = table->cellAt( rowCounter, columnCounter ); // Insert a frame into the cell and work on that, so we can handle // different parts of the cell having different block formatting QTextCursor cellCursor = cell.lastCursorPosition(); QTextFrameFormat frameFormat; frameFormat.setMargin( 1 ); // TODO: this shouldn't be hard coded QTextFrame *frame = cellCursor.insertFrame( frameFormat ); QTextCursor frameCursor = frame->firstCursorPosition(); frameCursor.setBlockFormat( format ); if ( !convertParagraph( &frameCursor, paragraphElement, format ) ) return false; } else if ( paragraphElement.tagName() == QLatin1String( "list" ) ) { QTextTableCell cell = table->cellAt( rowCounter, columnCounter ); // insert a list into the cell QTextCursor cellCursor = cell.lastCursorPosition(); if ( !convertList( &cellCursor, paragraphElement ) ) { return false; } } paragraphElement = paragraphElement.nextSiblingElement(); } columnCounter++; } columnElement = columnElement.nextSiblingElement(); } rowCounter++; } else if ( el.tagName() == QLatin1String( "table-column" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( el.attribute( QStringLiteral("style-name") ) ); const QString tableColumnNumColumnsRepeated = el.attribute( QStringLiteral("number-columns-repeated"), QStringLiteral("1") ); int numColumnsToApplyTo = tableColumnNumColumnsRepeated.toInt(); for (int i = 0; i < numColumnsToApplyTo; ++i) { property.applyTableColumn( &tableFormat ); } } } table->setFormat( tableFormat ); return true; } bool Converter::convertFrame( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "image" ) ) { const QString href = child.attribute( QStringLiteral("href") ); QTextImageFormat format; format.setWidth( StyleParser::convertUnit( element.attribute( QStringLiteral("width") ) ) ); format.setHeight( StyleParser::convertUnit( element.attribute( QStringLiteral("height") ) ) ); format.setName( href ); mCursor->insertImage( format ); } child = child.nextSiblingElement(); } return true; } bool Converter::convertLink( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ) { int startPosition = cursor->position(); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, format ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, format ) ) return false; } child = child.nextSibling(); } int endPosition = cursor->position(); Okular::Action *action = new Okular::BrowseAction( QUrl(element.attribute( QStringLiteral("href") )) ); emit addAction( action, startPosition, endPosition ); return true; } bool Converter::convertAnnotation( QTextCursor *cursor, const QDomElement &element ) { QStringList contents; QString creator; QDateTime dateTime; int position = cursor->position(); QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "creator" ) ) { creator = child.text(); } else if ( child.tagName() == QLatin1String( "date" ) ) { dateTime = QDateTime::fromString( child.text(), Qt::ISODate ); } else if ( child.tagName() == QLatin1String( "p" ) ) { contents.append( child.text() ); } child = child.nextSiblingElement(); } Okular::TextAnnotation *annotation = new Okular::TextAnnotation; annotation->setAuthor( creator ); annotation->setContents( contents.join( QStringLiteral("\n") ) ); annotation->setCreationDate( dateTime ); - annotation->style().setColor( QColor( "#ffff00" ) ); + annotation->style().setColor( QColor( 0xff, 0xff, 0x00 ) ); annotation->style().setOpacity( 0.5 ); emit addAnnotation( annotation, position, position + 3 ); return true; } diff --git a/ui/annotwindow.cpp b/ui/annotwindow.cpp index c6092aae9..3eacde023 100644 --- a/ui/annotwindow.cpp +++ b/ui/annotwindow.cpp @@ -1,444 +1,444 @@ /*************************************************************************** * Copyright (C) 2006 by Chu Xiaodong * * Copyright (C) 2006 by Pino Toscano * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "annotwindow.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "core/annotations.h" #include "core/document.h" #include "latexrenderer.h" #include #include class CloseButton : public QPushButton { Q_OBJECT public: CloseButton( QWidget * parent = Q_NULLPTR ) : QPushButton( parent ) { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); QSize size = QSize( 14, 14 ).expandedTo( QApplication::globalStrut() ); setFixedSize( size ); setIcon( style()->standardIcon( QStyle::SP_DockWidgetCloseButton ) ); setIconSize( size ); setToolTip( i18n( "Close this note" ) ); setCursor( Qt::ArrowCursor ); } }; class MovableTitle : public QWidget { Q_OBJECT public: MovableTitle( QWidget * parent ) : QWidget( parent ) { QVBoxLayout * mainlay = new QVBoxLayout( this ); mainlay->setContentsMargins( 0, 0, 0, 0 ); mainlay->setSpacing( 0 ); // close button row QHBoxLayout * buttonlay = new QHBoxLayout(); mainlay->addLayout( buttonlay ); titleLabel = new QLabel( this ); QFont f = titleLabel->font(); f.setBold( true ); titleLabel->setFont( f ); titleLabel->setCursor( Qt::SizeAllCursor ); buttonlay->addWidget( titleLabel ); dateLabel = new QLabel( this ); dateLabel->setAlignment( Qt::AlignTop | Qt::AlignRight ); f = dateLabel->font(); f.setPointSize( QFontInfo( f ).pointSize() - 2 ); dateLabel->setFont( f ); dateLabel->setCursor( Qt::SizeAllCursor ); buttonlay->addWidget( dateLabel ); CloseButton * close = new CloseButton( this ); connect( close, &QAbstractButton::clicked, parent, &QWidget::close ); buttonlay->addWidget( close ); // option button row QHBoxLayout * optionlay = new QHBoxLayout(); mainlay->addLayout( optionlay ); authorLabel = new QLabel( this ); authorLabel->setCursor( Qt::SizeAllCursor ); authorLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); optionlay->addWidget( authorLabel ); optionButton = new QToolButton( this ); QString opttext = i18n( "Options" ); optionButton->setText( opttext ); optionButton->setAutoRaise( true ); QSize s = QFontMetrics( optionButton->font() ).boundingRect( opttext ).size() + QSize( 8, 8 ); optionButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); optionButton->setFixedSize( s ); optionlay->addWidget( optionButton ); // ### disabled for now optionButton->hide(); latexButton = new QToolButton( this ); QHBoxLayout * latexlay = new QHBoxLayout(); QString latextext = i18n ( "This annotation may contain LaTeX code.\nClick here to render." ); latexButton->setText( latextext ); latexButton->setAutoRaise( true ); s = QFontMetrics( latexButton->font() ).boundingRect(0, 0, this->width(), this->height(), 0, latextext ).size() + QSize( 8, 8 ); latexButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); latexButton->setFixedSize( s ); latexButton->setCheckable( true ); latexButton->setVisible( false ); latexlay->addSpacing( 1 ); latexlay->addWidget( latexButton ); latexlay->addSpacing( 1 ); mainlay->addLayout( latexlay ); connect(latexButton, SIGNAL(clicked(bool)), parent, SLOT(renderLatex(bool))); connect(parent, SIGNAL(containsLatex(bool)), latexButton, SLOT(setVisible(bool))); titleLabel->installEventFilter( this ); dateLabel->installEventFilter( this ); authorLabel->installEventFilter( this ); } bool eventFilter( QObject * obj, QEvent * e ) override { if ( obj != titleLabel && obj != authorLabel && obj != dateLabel ) return false; QMouseEvent * me = nullptr; switch ( e->type() ) { case QEvent::MouseButtonPress: me = (QMouseEvent*)e; mousePressPos = me->pos(); parentWidget()->raise(); break; case QEvent::MouseButtonRelease: mousePressPos = QPoint(); break; case QEvent::MouseMove: me = (QMouseEvent*)e; parentWidget()->move( me->pos() - mousePressPos + parentWidget()->pos() ); break; default: return false; } return true; } void setTitle( const QString& title ) { titleLabel->setText( QStringLiteral( " " ) + title ); } void setDate( const QDateTime& dt ) { dateLabel->setText( QLocale().toString( dt.toTimeSpec(Qt::LocalTime), QLocale::ShortFormat ) + QLatin1Char(' ') ); } void setAuthor( const QString& author ) { authorLabel->setText( QStringLiteral( " " ) + author ); } void connectOptionButton( QObject * recv, const char* method ) { connect( optionButton, SIGNAL(clicked()), recv, method ); } void uncheckLatexButton() { latexButton->setChecked( false ); } private: QLabel * titleLabel; QLabel * dateLabel; QLabel * authorLabel; QPoint mousePressPos; QToolButton * optionButton; QToolButton * latexButton; }; // Qt::SubWindow is needed to make QSizeGrip work AnnotWindow::AnnotWindow( QWidget * parent, Okular::Annotation * annot, Okular::Document *document, int page ) : QFrame( parent, Qt::SubWindow ), m_annot( annot ), m_document( document ), m_page( page ) { setAutoFillBackground( true ); setFrameStyle( Panel | Raised ); setAttribute( Qt::WA_DeleteOnClose ); setObjectName(QStringLiteral("AnnotWindow")); const bool canEditAnnotation = m_document->canModifyPageAnnotation( annot ); textEdit = new KTextEdit( this ); textEdit->setAcceptRichText( false ); textEdit->setPlainText( m_annot->contents() ); textEdit->installEventFilter( this ); textEdit->setUndoRedoEnabled( false ); m_prevCursorPos = textEdit->textCursor().position(); m_prevAnchorPos = textEdit->textCursor().anchor(); connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); connect(textEdit, &KTextEdit::aboutToShowContextMenu, this, &AnnotWindow::slotUpdateUndoAndRedoInContextMenu); connect(m_document, &Okular::Document::annotationContentsChangedByUndoRedo, this, &AnnotWindow::slotHandleContentsChangedByUndoRedo); if (!canEditAnnotation) textEdit->setReadOnly(true); QVBoxLayout * mainlay = new QVBoxLayout( this ); mainlay->setContentsMargins( 2, 2, 2, 2 ); mainlay->setSpacing( 0 ); m_title = new MovableTitle( this ); mainlay->addWidget( m_title ); mainlay->addWidget( textEdit ); QHBoxLayout * lowerlay = new QHBoxLayout(); mainlay->addLayout( lowerlay ); lowerlay->addItem( new QSpacerItem( 5, 5, QSizePolicy::Expanding, QSizePolicy::Fixed ) ); QSizeGrip * sb = new QSizeGrip( this ); lowerlay->addWidget( sb ); m_latexRenderer = new GuiUtils::LatexRenderer(); // The emit below is not wrong even if emitting signals from the constructor it's usually wrong // in this case the signal it's connected to inside MovableTitle constructor a few lines above emit containsLatex( GuiUtils::LatexRenderer::mightContainLatex( m_annot->contents() ) ); // clazy:exclude=incorrect-emit m_title->setTitle( m_annot->window().summary() ); m_title->connectOptionButton( this, SLOT(slotOptionBtn()) ); setGeometry(10,10,300,300 ); reloadInfo(); } AnnotWindow::~AnnotWindow() { delete m_latexRenderer; } Okular::Annotation * AnnotWindow::annotation() const { return m_annot; } void AnnotWindow::updateAnnotation( Okular::Annotation * a ) { m_annot = a; } void AnnotWindow::reloadInfo() { QColor newcolor; if ( m_annot->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * textAnn = static_cast< Okular::TextAnnotation * >( m_annot ); if ( textAnn->textType() == Okular::TextAnnotation::InPlace && textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter ) - newcolor = QColor("#fdfd96"); + newcolor = QColor(0xfd, 0xfd, 0x96); } if ( !newcolor.isValid() ) newcolor = m_annot->style().color().isValid() ? QColor(m_annot->style().color().red(), m_annot->style().color().green(), m_annot->style().color().blue(), 255) : Qt::yellow; if ( newcolor != m_color ) { m_color = newcolor; setPalette( QPalette( m_color ) ); QPalette pl = textEdit->palette(); pl.setColor( QPalette::Base, m_color ); textEdit->setPalette( pl ); } m_title->setAuthor( m_annot->author() ); m_title->setDate( m_annot->modificationDate() ); } int AnnotWindow::pageNumber() const { return m_page; } void AnnotWindow::showEvent( QShowEvent * event ) { QFrame::showEvent( event ); // focus the content area by default textEdit->setFocus(); } bool AnnotWindow::eventFilter(QObject *, QEvent *e) { if ( e->type () == QEvent::ShortcutOverride ) { QKeyEvent * keyEvent = static_cast< QKeyEvent * >( e ); if ( keyEvent->key() == Qt::Key_Escape ) { close(); return true; } } else if (e->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(e); if (keyEvent == QKeySequence::Undo) { m_document->undo(); return true; } else if (keyEvent == QKeySequence::Redo) { m_document->redo(); return true; } } else if (e->type() == QEvent::FocusIn) { raise(); } return false; } void AnnotWindow::slotUpdateUndoAndRedoInContextMenu(QMenu* menu) { if (!menu) return; QList actionList = menu->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; QAction *kundo = KStandardAction::create( KStandardAction::Undo, m_document, SLOT(undo()), menu); QAction *kredo = KStandardAction::create( KStandardAction::Redo, m_document, SLOT(redo()), menu); connect(m_document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled); connect(m_document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled); kundo->setEnabled(m_document->canUndo()); kredo->setEnabled(m_document->canRedo()); QAction *oldUndo, *oldRedo; oldUndo = actionList[UndoAct]; oldRedo = actionList[RedoAct]; menu->insertAction(oldUndo, kundo); menu->insertAction(oldRedo, kredo); menu->removeAction(oldUndo); menu->removeAction(oldRedo); } void AnnotWindow::slotOptionBtn() { //TODO: call context menu in pageview //emit sig... } void AnnotWindow::slotsaveWindowText() { const QString contents = textEdit->toPlainText(); const int cursorPos = textEdit->textCursor().position(); if (contents != m_annot->contents()) { m_document->editPageAnnotationContents( m_page, m_annot, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos); emit containsLatex( GuiUtils::LatexRenderer::mightContainLatex( textEdit->toPlainText() ) ); } m_prevCursorPos = cursorPos; m_prevAnchorPos = textEdit->textCursor().anchor(); } void AnnotWindow::renderLatex( bool render ) { if (render) { textEdit->setReadOnly( true ); disconnect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); disconnect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); textEdit->setAcceptRichText( true ); QString contents = m_annot->contents(); contents = Qt::convertFromPlainText( contents ); QColor fontColor = textEdit->textColor(); int fontSize = textEdit->fontPointSize(); QString latexOutput; GuiUtils::LatexRenderer::Error errorCode = m_latexRenderer->renderLatexInHtml( contents, fontColor, fontSize, Okular::Utils::realDpi(nullptr).width(), latexOutput ); switch ( errorCode ) { case GuiUtils::LatexRenderer::LatexNotFound: KMessageBox::sorry( this, i18n( "Cannot find latex executable." ), i18n( "LaTeX rendering failed" ) ); m_title->uncheckLatexButton(); renderLatex( false ); break; case GuiUtils::LatexRenderer::DvipngNotFound: KMessageBox::sorry( this, i18n( "Cannot find dvipng executable." ), i18n( "LaTeX rendering failed" ) ); m_title->uncheckLatexButton(); renderLatex( false ); break; case GuiUtils::LatexRenderer::LatexFailed: KMessageBox::detailedSorry( this, i18n( "A problem occurred during the execution of the 'latex' command." ), latexOutput, i18n( "LaTeX rendering failed" ) ); m_title->uncheckLatexButton(); renderLatex( false ); break; case GuiUtils::LatexRenderer::DvipngFailed: KMessageBox::sorry( this, i18n( "A problem occurred during the execution of the 'dvipng' command." ), i18n( "LaTeX rendering failed" ) ); m_title->uncheckLatexButton(); renderLatex( false ); break; case GuiUtils::LatexRenderer::NoError: default: textEdit->setHtml( contents ); break; } } else { textEdit->setAcceptRichText( false ); textEdit->setPlainText( m_annot->contents() ); connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); textEdit->setReadOnly( false ); } } void AnnotWindow::slotHandleContentsChangedByUndoRedo(Okular::Annotation* annot, const QString &contents, int cursorPos, int anchorPos) { if ( annot != m_annot ) { return; } textEdit->setPlainText(contents); QTextCursor c = textEdit->textCursor(); c.setPosition(anchorPos); c.setPosition(cursorPos,QTextCursor::KeepAnchor); m_prevCursorPos = cursorPos; m_prevAnchorPos = anchorPos; textEdit->setTextCursor(c); textEdit->setFocus(); emit containsLatex( GuiUtils::LatexRenderer::mightContainLatex( m_annot->contents() ) ); } #include "annotwindow.moc"