diff options
Diffstat (limited to 'src/libs/widgets/common/previewwidget.cpp')
-rw-r--r-- | src/libs/widgets/common/previewwidget.cpp | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/src/libs/widgets/common/previewwidget.cpp b/src/libs/widgets/common/previewwidget.cpp new file mode 100644 index 00000000..0fe5cb29 --- /dev/null +++ b/src/libs/widgets/common/previewwidget.cpp @@ -0,0 +1,640 @@ +/* ============================================================ + * + * 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 |