// vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2011 Aurélien Gâteau 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "moc_birdeyeview.cpp" // Local #include #include // KDE #include // Qt #include #include #include #include #include #include namespace Gwenview { static qreal MIN_SIZE = 72; static qreal VIEW_OFFSET = MIN_SIZE / 4; static int AUTOHIDE_DELAY = 2000; /** * Returns a QRectF whose coordinates are rounded to completely contains rect */ inline QRectF alignedRectF(const QRectF& rect) { return QRectF(rect.toAlignedRect()); } struct BirdEyeViewPrivate { BirdEyeView* q; DocumentView* mDocView; QPropertyAnimation* mOpacityAnim; QTimer* mAutoHideTimer; QRectF mVisibleRect; QPointF mStartDragMousePos; QPointF mStartDragViewPos; void updateCursor(const QPointF& pos) { q->setCursor(mVisibleRect.contains(pos) ? Qt::OpenHandCursor : Qt::ArrowCursor); } void updateVisibility() { bool visible; if (!mDocView->canZoom() || mDocView->zoomToFit()) { // No need to show visible = false; } else if (mDocView->isAnimated()) { // Do not show while animated visible = false; } else if (mVisibleRect == q->boundingRect()) { // All of the image is visible visible = false; } else if (q->isUnderMouse() || !mStartDragMousePos.isNull()) { // User is interacting or about to interact with birdeyeview visible = true; } else if (mAutoHideTimer->isActive()) { // User triggered some activity recently (move mouse, scroll, zoom) visible = true; } else { // No recent activity visible = false; } qreal wantedOpacity = visible ? 1 : 0; if (!qFuzzyCompare(wantedOpacity, q->opacity())) { mOpacityAnim->setEndValue(wantedOpacity); mOpacityAnim->start(); } if (visible) { mAutoHideTimer->start(); } } }; BirdEyeView::BirdEyeView(DocumentView* docView) : QGraphicsWidget(docView) , d(new BirdEyeViewPrivate) { d->q = this; d->mDocView = docView; setFlag(ItemIsSelectable); setCursor(Qt::ArrowCursor); setAcceptHoverEvents(true); d->mOpacityAnim = new QPropertyAnimation(this, "opacity", this); d->mAutoHideTimer = new QTimer(this); d->mAutoHideTimer->setSingleShot(true); d->mAutoHideTimer->setInterval(AUTOHIDE_DELAY); connect(d->mAutoHideTimer, SIGNAL(timeout()), SLOT(slotAutoHideTimeout())); // Hide ourself by default, to avoid startup flashes (if we let updateOpacity // update opacity, it will do so through an animation) setOpacity(0); slotZoomOrSizeChanged(); connect(docView->document().data(), SIGNAL(metaInfoUpdated()), SLOT(slotZoomOrSizeChanged())); connect(docView, SIGNAL(zoomChanged(qreal)), SLOT(slotZoomOrSizeChanged())); connect(docView, SIGNAL(zoomToFitChanged(bool)), SLOT(slotZoomOrSizeChanged())); connect(docView, SIGNAL(positionChanged()), SLOT(slotPositionChanged())); } BirdEyeView::~BirdEyeView() { delete d; } void BirdEyeView::adjustGeometry() { if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) { return; } QSizeF size = d->mDocView->document()->size(); size.scale(MIN_SIZE, MIN_SIZE, Qt::KeepAspectRatioByExpanding); QRectF docViewRect = d->mDocView->boundingRect(); int maxBevHeight = docViewRect.height() - 2 * VIEW_OFFSET; int maxBevWidth = docViewRect.width() - 2 * VIEW_OFFSET; if (size.height() > maxBevHeight) { size.scale(MIN_SIZE, maxBevHeight, Qt::KeepAspectRatio); } if (size.width() > maxBevWidth) { size.scale(maxBevWidth, MIN_SIZE, Qt::KeepAspectRatio); } QRectF geom = QRectF( QApplication::isRightToLeft() ? docViewRect.left() + VIEW_OFFSET : docViewRect.right() - VIEW_OFFSET - size.width(), docViewRect.bottom() - VIEW_OFFSET - size.height(), size.width(), size.height() ); setGeometry(alignedRectF(geom)); adjustVisibleRect(); } void BirdEyeView::adjustVisibleRect() { QSizeF docSize = d->mDocView->document()->size(); qreal viewZoom = d->mDocView->zoom(); qreal bevZoom; if (docSize.height() > docSize.width()) { bevZoom = size().height() / docSize.height(); } else { bevZoom = size().width() / docSize.width(); } if (qFuzzyIsNull(viewZoom) || qFuzzyIsNull(bevZoom)) { // Prevent divide-by-zero crashes return; } QRectF rect = QRectF( QPointF(d->mDocView->position()) / viewZoom * bevZoom, (d->mDocView->size() / viewZoom).boundedTo(docSize) * bevZoom); d->mVisibleRect = rect; } void BirdEyeView::slotAutoHideTimeout() { d->updateVisibility(); } void BirdEyeView::slotZoomOrSizeChanged() { if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) { d->updateVisibility(); return; } adjustGeometry(); update(); d->mAutoHideTimer->start(); d->updateVisibility(); } void BirdEyeView::slotPositionChanged() { adjustVisibleRect(); update(); d->mAutoHideTimer->start(); d->updateVisibility(); } inline void drawTransparentRect(QPainter* painter, const QRectF& rect, const QColor& color) { QColor bg = color; bg.setAlphaF(.33); QColor fg = color; fg.setAlphaF(.66); painter->setPen(fg); painter->setBrush(bg); painter->drawRect(rect.adjusted(0, 0, -1, -1)); } void BirdEyeView::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { static const QColor bgColor = QColor::fromHsvF(0, 0, .33); drawTransparentRect(painter, boundingRect(), bgColor); drawTransparentRect(painter, d->mVisibleRect, Qt::white); } void BirdEyeView::onMouseMoved() { d->mAutoHideTimer->start(); d->updateVisibility(); } void BirdEyeView::slotIsAnimatedChanged() { d->updateVisibility(); } void BirdEyeView::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (d->mVisibleRect.contains(event->pos())) { setCursor(Qt::ClosedHandCursor); d->mStartDragMousePos = event->pos(); d->mStartDragViewPos = d->mDocView->position(); } } void BirdEyeView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseMoveEvent(event); if (d->mStartDragMousePos.isNull()) { // Do not drag if mouse was pressed outside visible rect return; } qreal ratio = qMin(d->mDocView->boundingRect().height() / d->mVisibleRect.height(), d->mDocView->boundingRect().width() / d->mVisibleRect.width()); QPointF mousePos = event->pos(); QPointF viewPos = d->mStartDragViewPos + (mousePos - d->mStartDragMousePos) * ratio; d->mDocView->setPosition(viewPos.toPoint()); } void BirdEyeView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseReleaseEvent(event); if (d->mStartDragMousePos.isNull()) { return; } d->updateCursor(event->pos()); d->mStartDragMousePos = QPointF(); d->mAutoHideTimer->start(); } void BirdEyeView::hoverEnterEvent(QGraphicsSceneHoverEvent* /*event*/) { d->mAutoHideTimer->stop(); d->updateVisibility(); } void BirdEyeView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { if (d->mStartDragMousePos.isNull()) { d->updateCursor(event->pos()); } } void BirdEyeView::hoverLeaveEvent(QGraphicsSceneHoverEvent* /*event*/) { d->mAutoHideTimer->start(); d->updateVisibility(); } } // namespace