You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

415 lines
13 KiB

/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* 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 "pageviewutils.h"
// qt/kde includes
#include <qapplication.h>
#include <qmenu.h>
#include <qpainter.h>
#include <qtimer.h>
#include <kiconloader.h>
// local includes
#include "formwidgets.h"
#include "videowidget.h"
#include "core/page.h"
#include "core/form.h"
#include "settings.h"
/*********************/
/** PageViewItem */
/*********************/
PageViewItem::PageViewItem( const Okular::Page * page )
: m_page( page ), m_zoomFactor( 1.0 ), m_visible( true ),
m_formsVisible( false ), m_crop( 0., 0., 1., 1. )
{
}
PageViewItem::~PageViewItem()
{
qDeleteAll( m_formWidgets );
qDeleteAll( m_videoWidgets );
}
const Okular::Page * PageViewItem::page() const
{
return m_page;
}
int PageViewItem::pageNumber() const
{
return m_page->number();
}
const QRect& PageViewItem::croppedGeometry() const
{
return m_croppedGeometry;
}
int PageViewItem::croppedWidth() const
{
return m_croppedGeometry.width();
}
int PageViewItem::croppedHeight() const
{
return m_croppedGeometry.height();
}
const QRect& PageViewItem::uncroppedGeometry() const
{
return m_uncroppedGeometry;
}
int PageViewItem::uncroppedWidth() const
{
return m_uncroppedGeometry.width();
}
int PageViewItem::uncroppedHeight() const
{
return m_uncroppedGeometry.height();
}
const Okular::NormalizedRect & PageViewItem::crop() const
{
return m_crop;
}
double PageViewItem::zoomFactor() const
{
return m_zoomFactor;
}
double PageViewItem::absToPageX( double absX ) const
{
return ( absX - m_uncroppedGeometry.left() ) / m_uncroppedGeometry.width();
}
double PageViewItem::absToPageY( double absY ) const
{
return ( absY - m_uncroppedGeometry.top() ) / m_uncroppedGeometry.height();
}
bool PageViewItem::isVisible() const
{
return m_visible;
}
QSet<FormWidgetIface*>& PageViewItem::formWidgets()
{
return m_formWidgets;
}
QHash< Okular::Movie *, VideoWidget* >& PageViewItem::videoWidgets()
{
return m_videoWidgets;
}
void PageViewItem::setWHZC( int w, int h, double z, const Okular:: NormalizedRect & c )
{
m_croppedGeometry.setWidth( w );
m_croppedGeometry.setHeight( h );
m_zoomFactor = z;
m_crop = c;
m_uncroppedGeometry.setWidth( qRound( w / ( c.right - c.left ) ) );
m_uncroppedGeometry.setHeight( qRound( h / ( c.bottom - c.top ) ) );
for (FormWidgetIface *fwi : qAsConst(m_formWidgets))
{
Okular::NormalizedRect r = fwi->rect();
fwi->setWidthHeight(
qRound( fabs( r.right - r.left ) * m_uncroppedGeometry.width() ),
qRound( fabs( r.bottom - r.top ) * m_uncroppedGeometry.height() ) );
}
for ( VideoWidget *vw : qAsConst(m_videoWidgets) )
{
const Okular::NormalizedRect r = vw->normGeometry();
vw->resize(
qRound( fabs( r.right - r.left ) * m_uncroppedGeometry.width() ),
qRound( fabs( r.bottom - r.top ) * m_uncroppedGeometry.height() ) );
}
}
void PageViewItem::moveTo( int x, int y )
// Assumes setWHZC() has already been called
{
m_croppedGeometry.moveLeft( x );
m_croppedGeometry.moveTop( y );
m_uncroppedGeometry.moveLeft( qRound( x - m_crop.left * m_uncroppedGeometry.width() ) );
m_uncroppedGeometry.moveTop( qRound( y - m_crop.top * m_uncroppedGeometry.height() ) );
QSet<FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for ( ; it != itEnd; ++it )
{
Okular::NormalizedRect r = (*it)->rect();
(*it)->moveTo(
qRound( x + m_uncroppedGeometry.width() * r.left ) + 1,
qRound( y + m_uncroppedGeometry.height() * r.top ) + 1 );
}
for ( VideoWidget *vw : qAsConst(m_videoWidgets) )
{
const Okular::NormalizedRect r = vw->normGeometry();
vw->move(
qRound( x + m_uncroppedGeometry.width() * r.left ) + 1,
qRound( y + m_uncroppedGeometry.height() * r.top ) + 1 );
}
}
void PageViewItem::setVisible( bool visible )
{
setFormWidgetsVisible( visible && m_formsVisible );
m_visible = visible;
}
void PageViewItem::invalidate()
{
m_croppedGeometry.setRect( 0, 0, 0, 0 );
m_uncroppedGeometry.setRect( 0, 0, 0, 0 );
}
bool PageViewItem::setFormWidgetsVisible( bool visible )
{
m_formsVisible = visible;
if ( !m_visible )
return false;
bool somehadfocus = false;
QSet<FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for ( ; it != itEnd; ++it )
{
bool hadfocus = (*it)->setVisibility( visible && (*it)->formField()->isVisible() &&
FormWidgetsController::shouldFormWidgetBeShown((*it)->formField()) );
somehadfocus = somehadfocus || hadfocus;
}
return somehadfocus;
}
void PageViewItem::reloadFormWidgetsState()
{
for (FormWidgetIface *fwi : qAsConst(m_formWidgets))
{
fwi->setVisibility( fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField()));
}
}
/*********************/
/** PageViewMessage */
/*********************/
PageViewMessage::PageViewMessage( QWidget * parent )
: QWidget( parent ), m_timer( nullptr )
, m_lineSpacing( 0 )
{
setObjectName( QStringLiteral( "pageViewMessage" ) );
setFocusPolicy( Qt::NoFocus );
QPalette pal = palette();
pal.setColor( QPalette::Active, QPalette::Window, QApplication::palette().color( QPalette::Active, QPalette::Window ) );
setPalette( pal );
// if the layout is LtR, we can safely place it in the right position
if ( layoutDirection() == Qt::LeftToRight )
move( 10, 10 );
resize( 0, 0 );
hide();
}
void PageViewMessage::display( const QString & message, const QString & details, Icon icon, int durationMs )
// give Caesar what belongs to Caesar: code taken from Amarok's osd.h/.cpp
// "redde (reddite, pl.) cesari quae sunt cesaris", just btw. :)
// The code has been heavily modified since then.
{
if ( !Okular::Settings::showOSD() )
{
hide();
return;
}
// set text
m_message = message;
m_details = details;
// reset vars
m_lineSpacing = 0;
// load icon (if set)
m_symbol = QPixmap();
const auto symbolSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
if ( icon != None )
{
switch ( icon )
{
case Annotation:
m_symbol = QIcon::fromTheme( QStringLiteral("draw-freehand") ).pixmap( symbolSize );
break;
case Find:
m_symbol = QIcon::fromTheme( QStringLiteral("zoom-original") ).pixmap( symbolSize );
break;
case Error:
m_symbol = QIcon::fromTheme( QStringLiteral("dialog-error") ).pixmap( symbolSize );
break;
case Warning:
m_symbol = QIcon::fromTheme( QStringLiteral("dialog-warning") ).pixmap( symbolSize );
break;
default:
m_symbol = QIcon::fromTheme( QStringLiteral("dialog-information") ).pixmap( symbolSize );
break;
}
}
computeSizeAndResize();
// show widget and schedule a repaint
show();
update();
// close the message window after given mS
if ( durationMs > 0 )
{
if ( !m_timer )
{
m_timer = new QTimer( this );
m_timer->setSingleShot( true );
connect(m_timer, &QTimer::timeout, this, &PageViewMessage::hide);
}
m_timer->start( durationMs );
} else if ( m_timer )
m_timer->stop();
qobject_cast<QAbstractScrollArea*>(parentWidget())->viewport()->installEventFilter(this);
}
QRect PageViewMessage::computeTextRect( const QString & message, int extra_width ) const
// Return the QRect which embeds the text
{
int charSize = fontMetrics().averageCharWidth();
/* width of the viewport, minus 20 (~ size removed by further resizing),
minus the extra size (usually the icon width), minus (a bit empirical)
twice the mean width of a character to ensure that the bounding box is
really smaller than the container.
*/
const int boundingWidth = qobject_cast<QAbstractScrollArea*>(parentWidget())->viewport()->width() - 20 - ( extra_width > 0 ? 2 + extra_width : 0 ) - 2*charSize;
QRect textRect = fontMetrics().boundingRect( 0, 0, boundingWidth, 0,
Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message );
textRect.translate( -textRect.left(), -textRect.top() );
textRect.adjust( 0, 0, 2, 2 );
return textRect;
}
void PageViewMessage::computeSizeAndResize()
{
// determine text rectangle
const QRect textRect = computeTextRect( m_message, m_symbol.width() );
int width = textRect.width(),
height = textRect.height();
if ( !m_details.isEmpty() )
{
// determine details text rectangle
const QRect detailsRect = computeTextRect( m_details, m_symbol.width() );
width = qMax( width, detailsRect.width() );
height += detailsRect.height();
// plus add a ~60% line spacing
m_lineSpacing = static_cast< int >( fontMetrics().height() * 0.6 );
height += m_lineSpacing;
}
// update geometry with icon information
if ( ! m_symbol.isNull() )
{
width += 2 + m_symbol.width();
height = qMax( height, m_symbol.height() );
}
// resize widget
resize( QRect( 0, 0, width + 10, height + 8 ).size() );
// if the layout is RtL, we can move it to the right place only after we
// know how much size it will take
if ( layoutDirection() == Qt::RightToLeft )
move( parentWidget()->width() - geometry().width() - 10 - 1, 10 );
}
bool PageViewMessage::eventFilter(QObject * obj, QEvent * event )
{
/* if the parent object (scroll area) resizes, the message should
resize as well */
if (event->type() == QEvent::Resize)
{
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
if ( resizeEvent->oldSize() != resizeEvent->size() )
{
computeSizeAndResize();
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
void PageViewMessage::paintEvent( QPaintEvent * /* e */ )
{
const QRect textRect = computeTextRect( m_message, m_symbol.width() );
QRect detailsRect;
if ( !m_details.isEmpty() )
{
detailsRect = computeTextRect( m_details, m_symbol.width() );
}
int textXOffset = 0,
// add 2 to account for the reduced drawRoundRect later
textYOffset = ( geometry().height() - textRect.height() - detailsRect.height() - m_lineSpacing + 2 ) / 2,
iconXOffset = 0,
iconYOffset = !m_symbol.isNull() ? ( geometry().height() - m_symbol.height() ) / 2 : 0,
shadowOffset = 1;
if ( layoutDirection() == Qt::RightToLeft )
iconXOffset = 2 + textRect.width();
else
textXOffset = 2 + m_symbol.width();
// draw background
QPainter painter( this );
painter.setRenderHint( QPainter::Antialiasing, true );
painter.setPen( Qt::black );
painter.setBrush( palette().color( QPalette::Window ) );
painter.translate( 0.5, 0.5 );
painter.drawRoundRect( 1, 1, width()-2, height()-2, 1600 / width(), 1600 / height() );
// draw icon if present
if ( !m_symbol.isNull() )
painter.drawPixmap( 5 + iconXOffset, iconYOffset, m_symbol, 0, 0, m_symbol.width(), m_symbol.height() );
const int xStartPoint = 5 + textXOffset;
const int yStartPoint = textYOffset;
const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
// draw shadow and text
painter.setPen( palette().color( QPalette::Window ).darker( 115 ) );
painter.drawText( xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, m_message );
if ( !m_details.isEmpty() )
painter.drawText( xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing + shadowOffset, textRect.width(), detailsRect.height(), textDrawingFlags, m_details );
painter.setPen( palette().color( QPalette::WindowText ) );
painter.drawText( xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, m_message );
if ( !m_details.isEmpty() )
painter.drawText( xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing, textRect.width(), detailsRect.height(), textDrawingFlags, m_details );
}
void PageViewMessage::mousePressEvent( QMouseEvent * /*e*/ )
{
if ( m_timer )
m_timer->stop();
hide();
}