summaryrefslogtreecommitdiffstats
path: root/src/libs/widgets/common/previewwidget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/widgets/common/previewwidget.cpp')
-rw-r--r--src/libs/widgets/common/previewwidget.cpp640
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