/* ============================================================ * * 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 * * 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 // TQt includes. #include #include #include #include #include #include #include #include // KDE includes. #include #include // 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 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 snapValues; snapValues.append(0.5); snapValues.append(1.0); snapValues.append(fit); qHeapSort(snapValues); TQValueList::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