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.
digikam/digikam/libs/widgets/common/previewwidget.cpp

641 lines
15 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-06-13
* Description : a widget to display an image preview
*
* Copyright (C) 2006-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
*
* 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, 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.
*
* ============================================================ */
// C++ includes.
#include <cmath>
// TQt includes.
#include <tqstring.h>
#include <tqcache.h>
#include <tqpainter.h>
#include <tqimage.h>
#include <tqpixmap.h>
#include <tqrect.h>
#include <tqtimer.h>
#include <tqguardedptr.h>
// KDE includes.
#include <kcursor.h>
#include <tdelocale.h>
// Local includes.
#include "ddebug.h"
#include "previewwidget.h"
#include "previewwidget.moc"
namespace Digikam
{
class PreviewWidgetPriv
{
public:
PreviewWidgetPriv() :
tileSize(128), zoomMultiplier(1.2)
{
midButtonX = 0;
midButtonY = 0;
autoZoom = false;
fullScreen = false;
zoom = 1.0;
minZoom = 0.1;
maxZoom = 12.0;
zoomWidth = 0;
zoomHeight = 0;
tileTmpPix = new TQPixmap(tileSize, tileSize);
tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
tileCache.setAutoDelete(true);
}
bool autoZoom;
bool fullScreen;
const int tileSize;
int midButtonX;
int midButtonY;
int zoomWidth;
int zoomHeight;
double zoom;
double minZoom;
double maxZoom;
const double zoomMultiplier;
TQPoint centerZoomPoint;
TQRect pixmapRect;
TQCache<TQPixmap> tileCache;
TQPixmap* tileTmpPix;
TQColor bgColor;
};
PreviewWidget::PreviewWidget(TQWidget *parent)
: TQScrollView(parent, 0, TQt::WDestructiveClose)
{
d = new PreviewWidgetPriv;
d->bgColor.setRgb(0, 0, 0);
m_movingInProgress = false;
viewport()->setBackgroundMode(TQt::NoBackground);
viewport()->setMouseTracking(false);
horizontalScrollBar()->setLineStep( 1 );
horizontalScrollBar()->setPageStep( 1 );
verticalScrollBar()->setLineStep( 1 );
verticalScrollBar()->setPageStep( 1 );
setFrameStyle(TQFrame::GroupBoxPanel|TQFrame::Plain);
setMargin(0);
setLineWidth(1);
}
PreviewWidget::~PreviewWidget()
{
delete d->tileTmpPix;
delete d;
}
void PreviewWidget::setBackgroundColor(const TQColor& color)
{
if (d->bgColor == color)
return;
d->bgColor = color;
viewport()->update();
}
void PreviewWidget::slotReset()
{
d->tileCache.clear();
resetPreview();
}
TQRect PreviewWidget::previewRect()
{
return d->pixmapRect;
}
int PreviewWidget::tileSize()
{
return d->tileSize;
}
int PreviewWidget::zoomWidth()
{
return d->zoomWidth;
}
int PreviewWidget::zoomHeight()
{
return d->zoomHeight;
}
double PreviewWidget::zoomMax()
{
return d->maxZoom;
}
double PreviewWidget::zoomMin()
{
return d->minZoom;
}
void PreviewWidget::setZoomMax(double z)
{
d->maxZoom = ceilf(z * 10000.0) / 10000.0;
}
void PreviewWidget::setZoomMin(double z)
{
d->minZoom = floor(z * 10000.0) / 10000.0;
}
bool PreviewWidget::maxZoom()
{
return (d->zoom >= d->maxZoom);
}
bool PreviewWidget::minZoom()
{
return (d->zoom <= d->minZoom);
}
double PreviewWidget::snapZoom(double zoom)
{
// If the zoom value gets changed from d->zoom to zoom
// across 50%, 100% or fit-to-window, then return the
// the corresponding special value. Otherwise zoom is returned unchanged.
double fit = calcAutoZoomFactor(ZoomInOrOut);
TQValueList<double> snapValues;
snapValues.append(0.5);
snapValues.append(1.0);
snapValues.append(fit);
qHeapSort(snapValues);
TQValueList<double>::const_iterator it;
if (d->zoom < zoom)
{
for(it = snapValues.constBegin(); it != snapValues.constEnd(); ++it)
{
double z = *it;
if ((d->zoom < z) && (zoom > z))
{
zoom = z;
break;
}
}
}
else
{
for(it = snapValues.constEnd(); it != snapValues.constBegin(); --it)
{
double z = *it;
if ((d->zoom > z) && (zoom < z))
{
zoom = z;
break;
}
}
}
return zoom;
}
void PreviewWidget::slotIncreaseZoom()
{
double zoom = d->zoom * d->zoomMultiplier;
zoom = snapZoom(zoom > zoomMax() ? zoomMax() : zoom);
setZoomFactor(zoom);
}
void PreviewWidget::slotDecreaseZoom()
{
double zoom = d->zoom / d->zoomMultiplier;
zoom = snapZoom(zoom < zoomMin() ? zoomMin() : zoom);
setZoomFactor(zoom);
}
void PreviewWidget::setZoomFactorSnapped(double zoom)
{
double fit = calcAutoZoomFactor(ZoomInOrOut);
if (fabs(zoom-1.0) < 0.05)
{
zoom = 1.0;
}
if (fabs(zoom-0.5) < 0.05)
{
zoom = 0.5;
}
if (fabs(zoom-fit) < 0.05)
{
zoom = fit;
}
setZoomFactor(zoom);
}
void PreviewWidget::setZoomFactor(double zoom)
{
setZoomFactor(zoom, false);
}
void PreviewWidget::setZoomFactor(double zoom, bool centerView)
{
// Zoom using center of canvas and given zoom factor.
double oldZoom = d->zoom;
double cpx, cpy;
if (d->centerZoomPoint.isNull())
{
// center on current center
// store old center pos
cpx = contentsX() + visibleWidth() / 2.0;
cpy = contentsY() + visibleHeight() / 2.0;
cpx = ( cpx / d->tileSize ) * floor(d->tileSize / d->zoom);
cpy = ( cpy / d->tileSize ) * floor(d->tileSize / d->zoom);
}
else
{
// keep mouse pointer position constant
// store old content pos
cpx = contentsX();
cpy = contentsY();
}
// To limit precision of zoom value and reduce error with check of max/min zoom.
d->zoom = floor(zoom * 10000.0) / 10000.0;
d->zoomWidth = (int)(previewWidth() * d->zoom);
d->zoomHeight = (int)(previewHeight() * d->zoom);
updateContentsSize();
// adapt step size to zoom factor. Overall, using a finer step size than scrollbar default.
int step = TQMAX(2, 2*lround(d->zoom));
horizontalScrollBar()->setLineStep( step );
horizontalScrollBar()->setPageStep( step * 10 );
verticalScrollBar()->setLineStep( step );
verticalScrollBar()->setPageStep( step * 10 );
viewport()->setUpdatesEnabled(false);
if (d->centerZoomPoint.isNull())
{
cpx = ( cpx * d->tileSize ) / floor(d->tileSize / d->zoom);
cpy = ( cpy * d->tileSize ) / floor(d->tileSize / d->zoom);
if (centerView)
{
cpx = d->zoomWidth/2.0;
cpy = d->zoomHeight/2.0;
}
center((int)cpx, (int)(cpy));
}
else
{
cpx = d->zoom * d->centerZoomPoint.x() / oldZoom - d->centerZoomPoint.x() + cpx;
cpy = d->zoom * d->centerZoomPoint.y() / oldZoom - d->centerZoomPoint.y() + cpy;
setContentsPos((int)cpx, (int)(cpy));
}
viewport()->setUpdatesEnabled(true);
viewport()->update();
zoomFactorChanged(d->zoom);
}
double PreviewWidget::zoomFactor()
{
return d->zoom;
}
bool PreviewWidget::isFitToWindow()
{
return d->autoZoom;
}
void PreviewWidget::fitToWindow()
{
updateAutoZoom();
updateContentsSize();
zoomFactorChanged(d->zoom);
viewport()->update();
}
void PreviewWidget::toggleFitToWindow()
{
d->autoZoom = !d->autoZoom;
if (d->autoZoom)
{
updateAutoZoom();
}
else
{
d->zoom = 1.0;
zoomFactorChanged(d->zoom);
}
updateContentsSize();
viewport()->update();
}
void PreviewWidget::toggleFitToWindowOr100()
{
// If the current zoom is 100%, then fit to window.
if (d->zoom == 1.0)
{
fitToWindow();
}
else
{
setZoomFactor(1.0, true);
}
}
void PreviewWidget::updateAutoZoom(AutoZoomMode mode)
{
d->zoom = calcAutoZoomFactor(mode);
d->zoomWidth = (int)(previewWidth() * d->zoom);
d->zoomHeight = (int)(previewHeight() * d->zoom);
zoomFactorChanged(d->zoom);
}
double PreviewWidget::calcAutoZoomFactor(AutoZoomMode mode)
{
if (previewIsNull()) return d->zoom;
double srcWidth = previewWidth();
double srcHeight = previewHeight();
double dstWidth = contentsRect().width();
double dstHeight = contentsRect().height();
double zoom = TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
// limit precision as above
zoom = floor(zoom * 10000.0) / 10000.0;
if (mode == ZoomInOrOut)
// fit to available space, scale up or down
return zoom;
else
// ZoomInOnly: accept that an image is smaller than available space, dont scale up
return TQMIN(1.0, zoom);
}
void PreviewWidget::updateContentsSize()
{
viewport()->setUpdatesEnabled(false);
if (visibleWidth() > d->zoomWidth || visibleHeight() > d->zoomHeight)
{
// Center the image
int centerx = contentsRect().width()/2;
int centery = contentsRect().height()/2;
int xoffset = int(centerx - d->zoomWidth/2);
int yoffset = int(centery - d->zoomHeight/2);
xoffset = TQMAX(xoffset, 0);
yoffset = TQMAX(yoffset, 0);
d->pixmapRect = TQRect(xoffset, yoffset, d->zoomWidth, d->zoomHeight);
}
else
{
d->pixmapRect = TQRect(0, 0, d->zoomWidth, d->zoomHeight);
}
d->tileCache.clear();
setContentsSize();
viewport()->setUpdatesEnabled(true);
}
void PreviewWidget::setContentsSize()
{
resizeContents(d->zoomWidth, d->zoomHeight);
}
void PreviewWidget::resizeEvent(TQResizeEvent* e)
{
if (!e) return;
TQScrollView::resizeEvent(e);
if (d->autoZoom)
updateAutoZoom();
updateContentsSize();
// No need to repaint. its called
// automatically after resize
// To be sure than corner widget used to pan image will be hide/show
// accordinly with resize event.
zoomFactorChanged(d->zoom);
}
void PreviewWidget::viewportPaintEvent(TQPaintEvent *e)
{
TQRect er(e->rect());
er = TQRect(TQMAX(er.x() - 1, 0),
TQMAX(er.y() - 1, 0),
TQMIN(er.width() + 2, contentsRect().width()),
TQMIN(er.height() + 2, contentsRect().height()));
bool antialias = (d->zoom <= 1.0) ? true : false;
TQRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
TQRect cr = o_cr;
TQRegion clipRegion(er);
cr = d->pixmapRect.intersect(cr);
if (!cr.isEmpty() && !previewIsNull())
{
clipRegion -= TQRect(contentsToViewport(cr.topLeft()), cr.size());
TQRect pr = TQRect(cr.x() - d->pixmapRect.x(), cr.y() - d->pixmapRect.y(),
cr.width(), cr.height());
int x1 = (int)floor((double)pr.x() / (double)d->tileSize) * d->tileSize;
int y1 = (int)floor((double)pr.y() / (double)d->tileSize) * d->tileSize;
int x2 = (int)ceilf((double)pr.right() / (double)d->tileSize) * d->tileSize;
int y2 = (int)ceilf((double)pr.bottom() / (double)d->tileSize) * d->tileSize;
TQPixmap pix(d->tileSize, d->tileSize);
int sx, sy, sw, sh;
int step = (int)floor(d->tileSize / d->zoom);
for (int j = y1 ; j < y2 ; j += d->tileSize)
{
for (int i = x1 ; i < x2 ; i += d->tileSize)
{
TQString key = TQString("%1,%2").arg(i).arg(j);
TQPixmap *pix = d->tileCache.find(key);
if (!pix)
{
if (antialias)
{
pix = new TQPixmap(d->tileSize, d->tileSize);
d->tileCache.insert(key, pix);
}
else
{
pix = d->tileTmpPix;
}
pix->fill(d->bgColor);
sx = (int)floor((double)i / d->tileSize ) * step;
sy = (int)floor((double)j / d->tileSize ) * step;
sw = step;
sh = step;
paintPreview(pix, sx, sy, sw, sh);
}
TQRect r(i, j, d->tileSize, d->tileSize);
TQRect ir = pr.intersect(r);
TQPoint pt(contentsToViewport(TQPoint(ir.x() + d->pixmapRect.x(),
ir.y() + d->pixmapRect.y())));
bitBlt(viewport(), pt.x(), pt.y(),
pix,
ir.x()-r.x(), ir.y()-r.y(),
ir.width(), ir.height());
}
}
}
TQPainter p(viewport());
p.setClipRegion(clipRegion);
p.fillRect(er, d->bgColor);
p.end();
viewportPaintExtraData();
}
void PreviewWidget::contentsMousePressEvent(TQMouseEvent *e)
{
if (!e || e->button() == TQt::RightButton)
return;
m_movingInProgress = false;
if (e->button() == TQt::LeftButton)
{
emit signalLeftButtonClicked();
}
else if (e->button() == TQt::MidButton)
{
if (visibleWidth() < d->zoomWidth ||
visibleHeight() < d->zoomHeight)
{
m_movingInProgress = true;
d->midButtonX = e->x();
d->midButtonY = e->y();
viewport()->repaint(false);
viewport()->setCursor(TQt::SizeAllCursor);
}
return;
}
viewport()->setMouseTracking(false);
}
void PreviewWidget::contentsMouseMoveEvent(TQMouseEvent *e)
{
if (!e) return;
if (e->state() & TQt::MidButton)
{
if (m_movingInProgress)
{
scrollBy(d->midButtonX - e->x(),
d->midButtonY - e->y());
emit signalContentsMovedEvent(false);
}
}
}
void PreviewWidget::contentsMouseReleaseEvent(TQMouseEvent *e)
{
if (!e) return;
m_movingInProgress = false;
if (e->button() == TQt::MidButton)
{
emit signalContentsMovedEvent(true);
viewport()->unsetCursor();
viewport()->repaint(false);
}
if (e->button() == TQt::RightButton)
{
emit signalRightButtonClicked();
}
}
void PreviewWidget::contentsWheelEvent(TQWheelEvent *e)
{
e->accept();
if (e->state() & TQt::ShiftButton)
{
if (e->delta() < 0)
emit signalShowNextImage();
else if (e->delta() > 0)
emit signalShowPrevImage();
return;
}
else if (e->state() & TQt::ControlButton)
{
// When zooming with the mouse-wheel, the image center is kept fixed.
d->centerZoomPoint = e->pos();
if (e->delta() < 0 && !minZoom())
slotDecreaseZoom();
else if (e->delta() > 0 && !maxZoom())
slotIncreaseZoom();
d->centerZoomPoint = TQPoint();
return;
}
TQScrollView::contentsWheelEvent(e);
}
void PreviewWidget::zoomFactorChanged(double zoom)
{
emit signalZoomFactorChanged(zoom);
}
} // NameSpace Digikam