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/utilities/imageeditor/canvas/canvas.cpp

1422 lines
36 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2003-01-09
* Description : image editor canvas management class
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
* Copyright (C) 2004-2008 by 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 <cstdio>
#include <cmath>
// TQt includes.
#include <tqtooltip.h>
#include <tqfile.h>
#include <tqstring.h>
#include <tqevent.h>
#include <tqpoint.h>
#include <tqpainter.h>
#include <tqpen.h>
#include <tqpixmap.h>
#include <tqstyle.h>
#include <tqapplication.h>
#include <tqcursor.h>
#include <tqimage.h>
#include <tqregion.h>
#include <tqtimer.h>
#include <tqcache.h>
#include <tqcolor.h>
#include <tqdragobject.h>
#include <tqclipboard.h>
#include <tqtoolbutton.h>
// KDE includes.
#include <kcursor.h>
#include <tdelocale.h>
#include <kiconloader.h>
#include <kdatetbl.h>
#include <tdeglobalsettings.h>
// Local includes.
#include "ddebug.h"
#include "imagehistogram.h"
#include "imagepaniconwidget.h"
#include "dimginterface.h"
#include "iccsettingscontainer.h"
#include "exposurecontainer.h"
#include "iofilesettingscontainer.h"
#include "loadingcacheinterface.h"
#include "canvas.h"
#include "canvas.moc"
namespace Digikam
{
class CanvasPrivate
{
public:
CanvasPrivate() :
tileSize(128), minZoom(0.1), maxZoom(12.0), zoomMultiplier(1.2)
{
rubber = 0;
pressedMoved = false;
pressedMoving = false;
ltActive = false;
rtActive = false;
lbActive = false;
rbActive = false;
midButtonPressed = false;
midButtonX = 0;
midButtonY = 0;
panIconPopup = 0;
panIconWidget = 0;
cornerButton = 0;
parent = 0;
im = 0;
rubber = 0;
autoZoom = false;
fullScreen = false;
zoom = 1.0;
tileTmpPix = new TQPixmap(tileSize, tileSize);
tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
tileCache.setAutoDelete(true);
}
bool autoZoom;
bool fullScreen;
bool pressedMoved;
bool pressedMoving;
bool ltActive;
bool rtActive;
bool lbActive;
bool rbActive;
bool midButtonPressed;
const int tileSize;
int midButtonX;
int midButtonY;
double zoom;
const double minZoom;
const double maxZoom;
const double zoomMultiplier;
TQToolButton *cornerButton;
TQRect *rubber;
TQRect pixmapRect;
TQCache<TQPixmap> tileCache;
TQPixmap* tileTmpPix;
TQPixmap qcheck;
TQColor bgColor;
TQWidget *parent;
TDEPopupFrame *panIconPopup;
DImgInterface *im;
ImagePanIconWidget *panIconWidget;
};
Canvas::Canvas(TQWidget *parent)
: TQScrollView(parent)
{
d = new CanvasPrivate;
d->im = new DImgInterface();
d->parent = parent;
d->bgColor.setRgb(0, 0, 0);
d->qcheck.resize(16, 16);
TQPainter p(&d->qcheck);
p.fillRect(0, 0, 8, 8, TQColor(144, 144, 144));
p.fillRect(8, 8, 8, 8, TQColor(144, 144, 144));
p.fillRect(0, 8, 8, 8, TQColor(100, 100, 100));
p.fillRect(8, 0, 8, 8, TQColor(100, 100, 100));
p.end();
d->cornerButton = new TQToolButton(this);
d->cornerButton->setIconSet(SmallIcon("move"));
d->cornerButton->hide();
TQToolTip::add(d->cornerButton, i18n("Pan the image to a region"));
setCornerWidget(d->cornerButton);
viewport()->setBackgroundMode(TQt::NoBackground);
viewport()->setMouseTracking(false);
setFrameStyle( TQFrame::NoFrame );
// ------------------------------------------------------------
connect(this, TQT_SIGNAL(signalZoomChanged(double)),
this, TQT_SLOT(slotZoomChanged(double)));
connect(d->cornerButton, TQT_SIGNAL(pressed()),
this, TQT_SLOT(slotCornerButtonPressed()));
connect(d->im, TQT_SIGNAL(signalModified()),
this, TQT_SLOT(slotModified()));
connect(d->im, TQT_SIGNAL(signalUndoStateChanged(bool, bool, bool)),
this, TQT_SIGNAL(signalUndoStateChanged(bool, bool, bool)));
connect(d->im, TQT_SIGNAL(signalLoadingStarted(const TQString&)),
this, TQT_SIGNAL(signalLoadingStarted(const TQString&)));
connect(d->im, TQT_SIGNAL(signalImageLoaded(const TQString&, bool)),
this, TQT_SLOT(slotImageLoaded(const TQString&, bool)));
connect(d->im, TQT_SIGNAL(signalImageSaved(const TQString&, bool)),
this, TQT_SLOT(slotImageSaved(const TQString&, bool)));
connect(d->im, TQT_SIGNAL(signalLoadingProgress(const TQString&, float)),
this, TQT_SIGNAL(signalLoadingProgress(const TQString&, float)));
connect(d->im, TQT_SIGNAL(signalSavingProgress(const TQString&, float)),
this, TQT_SIGNAL(signalSavingProgress(const TQString&, float)));
connect(this, TQT_SIGNAL(signalSelected(bool)),
this, TQT_SLOT(slotSelected()));
}
Canvas::~Canvas()
{
delete d->tileTmpPix;
delete d->im;
if (d->rubber)
delete d->rubber;
delete d;
}
void Canvas::resetImage()
{
reset();
viewport()->setUpdatesEnabled(false);
d->im->resetImage();
}
void Canvas::reset()
{
if (d->rubber)
{
delete d->rubber;
d->rubber = 0;
if (d->im->imageValid())
emit signalSelected(false);
}
d->tileCache.clear();
}
void Canvas::load(const TQString& filename, IOFileSettingsContainer *IOFileSettings)
{
reset();
viewport()->setUpdatesEnabled(false);
d->im->load( filename, IOFileSettings, d->parent );
emit signalPrepareToLoad();
}
void Canvas::slotImageLoaded(const TQString& filePath, bool success)
{
d->zoom = 1.0;
d->im->zoom(d->zoom);
if (d->autoZoom)
updateAutoZoom();
updateContentsSize(true);
viewport()->setUpdatesEnabled(true);
viewport()->update();
emit signalZoomChanged(d->zoom);
emit signalLoadingFinished(filePath, success);
}
void Canvas::preload(const TQString& /*filename*/)
{
// d->im->preload(filename);
}
/*
These code part are unused and untested
void Canvas::save(const TQString& filename, IOFileSettingsContainer *IOFileSettings)
{
d->im->save(filename, IOFileSettings);
emit signalSavingStarted(filename);
}
void Canvas::saveAs(const TQString& filename,IOFileSettingsContainer *IOFileSettings,
const TQString& mimeType)
{
d->im->saveAs(filename, IOFileSettings, mimeType);
emit signalSavingStarted(filename);
}
*/
void Canvas::saveAs(const TQString& filename, IOFileSettingsContainer *IOFileSettings,
bool setExifOrientationTag, const TQString& mimeType)
{
d->im->saveAs(filename, IOFileSettings, setExifOrientationTag, mimeType);
emit signalSavingStarted(filename);
}
void Canvas::slotImageSaved(const TQString& filePath, bool success)
{
emit signalSavingFinished(filePath, success);
}
void Canvas::switchToLastSaved(const TQString& newFilename)
{
d->im->switchToLastSaved(newFilename);
}
void Canvas::abortSaving()
{
d->im->abortSaving();
}
void Canvas::setModified()
{
d->im->setModified();
}
void Canvas::readMetadataFromFile(const TQString &file)
{
d->im->readMetadataFromFile(file);
}
void Canvas::clearUndoHistory()
{
d->im->clearUndoManager();
}
void Canvas::setUndoHistoryOrigin()
{
d->im->setUndoManagerOrigin();
}
void Canvas::updateUndoState()
{
d->im->updateUndoState();
}
DImg Canvas::currentImage()
{
return DImg(*d->im->getImg());
}
TQString Canvas::currentImageFileFormat()
{
return d->im->getImageFormat();
}
TQString Canvas::currentImageFilePath()
{
return d->im->getImageFilePath();
}
int Canvas::imageWidth()
{
return d->im->origWidth();
}
int Canvas::imageHeight()
{
return d->im->origHeight();
}
bool Canvas::isReadOnly()
{
return d->im->isReadOnly();
}
TQRect Canvas::getSelectedArea()
{
int x, y, w, h;
d->im->getSelectedArea(x, y, w, h);
return ( TQRect(x, y, w, h) );
}
DImgInterface *Canvas::interface() const
{
return d->im;
}
void Canvas::makeDefaultEditingCanvas()
{
DImgInterface::setDefaultInterface(d->im);
}
double Canvas::calcAutoZoomFactor()
{
if (!d->im->imageValid()) return d->zoom;
double srcWidth = d->im->origWidth();
double srcHeight = d->im->origHeight();
double dstWidth = contentsRect().width();
double dstHeight = contentsRect().height();
return TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
}
void Canvas::updateAutoZoom()
{
d->zoom = calcAutoZoomFactor();
d->im->zoom(d->zoom);
emit signalZoomChanged(d->zoom);
}
void Canvas::updateContentsSize(bool deleteRubber)
{
viewport()->setUpdatesEnabled(false);
if (deleteRubber && d->rubber)
{
delete d->rubber;
d->rubber = 0;
d->ltActive = false;
d->rtActive = false;
d->lbActive = false;
d->rbActive = false;
d->pressedMoved = false;
viewport()->unsetCursor();
viewport()->setMouseTracking(false);
if (d->im->imageValid())
emit signalSelected(false);
}
int wZ = d->im->width();
int hZ = d->im->height();
if (visibleWidth() > wZ || visibleHeight() > hZ)
{
// Center the image
int centerx = contentsRect().width()/2;
int centery = contentsRect().height()/2;
int xoffset = int(centerx - wZ/2);
int yoffset = int(centery - hZ/2);
xoffset = TQMAX(xoffset, 0);
yoffset = TQMAX(yoffset, 0);
d->pixmapRect = TQRect(xoffset, yoffset, wZ, hZ);
}
else
{
d->pixmapRect = TQRect(0, 0, wZ, hZ);
}
if (!deleteRubber && d->rubber)
{
int xSel, ySel, wSel, hSel;
d->im->getSelectedArea(xSel, ySel, wSel, hSel);
xSel = (int)((xSel * d->tileSize) / floor(d->tileSize / d->zoom));
ySel = (int)((ySel * d->tileSize) / floor(d->tileSize / d->zoom));
wSel = (int)((wSel * d->tileSize) / floor(d->tileSize / d->zoom));
hSel = (int)((hSel * d->tileSize) / floor(d->tileSize / d->zoom));
d->rubber->setX(xSel);
d->rubber->setY(ySel);
d->rubber->setWidth(wSel);
d->rubber->setHeight(hSel);
d->rubber->moveBy(d->pixmapRect.x(), d->pixmapRect.y());
}
d->tileCache.clear();
resizeContents(wZ, hZ);
viewport()->setUpdatesEnabled(true);
}
void Canvas::resizeEvent(TQResizeEvent* e)
{
if (!e)
return;
TQScrollView::resizeEvent(e);
if (d->autoZoom)
updateAutoZoom();
updateContentsSize(false);
// 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.
slotZoomChanged(d->zoom);
}
void Canvas::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()));
paintViewport(er, (d->zoom <= 1.0) ? true : false);
}
void Canvas::paintViewport(const TQRect& er, bool antialias)
{
TQRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
TQRect cr = o_cr;
TQRegion clipRegion(er);
cr = d->pixmapRect.intersect(cr);
if (!cr.isEmpty() && d->im->imageValid())
{
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);
bool hasRubber = (d->rubber && d->pressedMoved && d->pressedMoving && d->rubber->intersects(pr));
if (hasRubber)
{
// remove rubber
drawRubber();
}
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;
}
if (d->im->hasAlpha())
{
TQPainter p(pix);
p.drawTiledPixmap(0, 0, d->tileSize, d->tileSize,
d->qcheck, 0, 0);
p.end();
}
else
{
pix->fill(d->bgColor);
}
// NOTE : with implementations <= 0.9.1, the canvas doesn't work properly using high zoom level (> 500).
// The sx, sy, sw, sh values haven't be computed properly and "tile" artefacts been appears
// over the image. Look the example here:
// http://digikam3rdparty.free.fr/Screenshots/editorhighzoomartefact.png
// Note than these "tile" artifacts are not the real tiles of canvas.
// The new implementation below fix this problem to handle properly the areas to
// use from the source image to generate the canvas pixmap tiles.
sx = (int)floor((double)i / d->tileSize) * step;
sy = (int)floor((double)j / d->tileSize) * step;
sw = step;
sh = step;
if (d->rubber && d->pressedMoved && !d->pressedMoving)
{
TQRect rr(d->rubber->normalize());
TQRect r(i, j, d->tileSize, d->tileSize);
d->im->paintOnDevice(TQT_TQPAINTDEVICE(pix), sx, sy, sw, sh,
0, 0, d->tileSize, d->tileSize,
rr.x() - i - d->pixmapRect.x(),
rr.y() - j - d->pixmapRect.y(),
rr.width(), rr.height(),
antialias);
rr.moveBy(-i -d->pixmapRect.x(), -j -d->pixmapRect.y());
TQPainter p(pix);
p.setPen(TQPen(TQColor(250, 250, 255), 1));
p.drawRect(rr);
if (rr.width() >= 10 && rr.height() >= 10)
{
p.drawRect(rr.x(), rr.y(), 5, 5);
p.drawRect(rr.x(), rr.y()+rr.height()-5, 5, 5);
p.drawRect(rr.x()+rr.width()-5, rr.y()+rr.height()-5, 5, 5);
p.drawRect(rr.x()+rr.width()-5, rr.y(), 5, 5);
}
p.end();
}
else
{
d->im->paintOnDevice(TQT_TQPAINTDEVICE(pix), sx, sy, sw, sh,
0, 0, d->tileSize, d->tileSize,
antialias);
}
}
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());
}
}
if (hasRubber)
{
// restore rubber
drawRubber();
}
}
TQPainter painter(viewport());
painter.setClipRegion(clipRegion);
painter.fillRect(er, d->bgColor);
painter.end();
}
void Canvas::drawRubber()
{
if (!d->rubber || !d->im->imageValid())
return;
TQPainter p(viewport());
p.setRasterOp(TQt::NotROP );
p.setPen(TQPen(TQt::color0, 1));
p.setBrush(NoBrush);
TQRect r(d->rubber->normalize());
r = TQRect(contentsToViewport(TQPoint(r.x(), r.y())), r.size());
TQPoint pnt(r.x(), r.y());
style().tqdrawPrimitive(TQStyle::PE_FocusRect, &p,
TQRect(pnt.x(), pnt.y(), r.width(), r.height()),
colorGroup(), TQStyle::Style_Default,
TQStyleOption(colorGroup().base()));
p.end();
}
void Canvas::contentsMousePressEvent(TQMouseEvent *e)
{
if (!e || e->button() == TQt::RightButton)
return;
d->midButtonPressed = false;
if (e->button() == TQt::LeftButton)
{
if (d->ltActive || d->rtActive ||
d->lbActive || d->rbActive)
{
Q_ASSERT( d->rubber );
if (!d->rubber)
return;
// Set diagonally opposite corner as anchor
TQRect r(d->rubber->normalize());
if (d->ltActive)
{
d->rubber->setTopLeft(r.bottomRight());
d->rubber->setBottomRight(r.topLeft());
}
else if (d->rtActive)
{
d->rubber->setTopLeft(r.bottomLeft());
d->rubber->setBottomRight(r.topRight());
}
else if (d->lbActive)
{
d->rubber->setTopLeft(r.topRight());
d->rubber->setBottomRight(r.bottomLeft());
}
else if (d->rbActive)
{
d->rubber->setTopLeft(r.topLeft());
d->rubber->setBottomRight(r.bottomLeft());
}
viewport()->setMouseTracking(false);
d->pressedMoved = false;
d->pressedMoving = true;
d->tileCache.clear();
viewport()->repaint(false);
return;
}
}
else if (e->button() == TQt::MidButton)
{
if (visibleWidth() < d->im->width() ||
visibleHeight() < d->im->height())
{
viewport()->setCursor(TQt::SizeAllCursor);
d->midButtonPressed = true;
d->midButtonX = e->x();
d->midButtonY = e->y();
}
return;
}
if (d->rubber)
{
delete d->rubber;
d->rubber = 0;
}
d->rubber = new TQRect(e->x(), e->y(), 0, 0);
if (d->pressedMoved)
{
d->tileCache.clear();
viewport()->update();
}
d->pressedMoved = false;
d->pressedMoving = true;
viewport()->setMouseTracking(false);
}
void Canvas::contentsMouseMoveEvent(TQMouseEvent *e)
{
if (!e)
return;
if (e->state() & TQt::MidButton)
{
if (d->midButtonPressed)
{
scrollBy(d->midButtonX - e->x(),
d->midButtonY - e->y());
}
}
else if (!viewport()->hasMouseTracking())
{
if (!d->rubber)
return;
if (e->state() != TQt::LeftButton &&
!(d->ltActive || d->rtActive ||
d->lbActive || d->rbActive))
return;
// Clear old rubber.
if (d->pressedMoved)
drawRubber();
// Move content if necessary.
blockSignals(true);
setUpdatesEnabled(false);
ensureVisible(e->x(), e->y(), 10, 10);
setUpdatesEnabled(true);
blockSignals(false);
// draw the new rubber position.
int r, b;
r = (e->x() > d->pixmapRect.left()) ? e->x() : d->pixmapRect.left();
r = (r < d->pixmapRect.right()) ? r : d->pixmapRect.right();
b = (e->y() > d->pixmapRect.top()) ? e->y() : d->pixmapRect.top();
b = (b < d->pixmapRect.bottom()) ? b : d->pixmapRect.bottom();
d->rubber->setRight(r);
d->rubber->setBottom(b);
drawRubber();
d->pressedMoved = true;
d->pressedMoving = true;
// To refresh editor status bar with current selection.
emit signalSelectionChanged(calcSeletedArea());
}
else
{
if (!d->rubber)
return;
TQRect r(d->rubber->normalize());
TQRect lt(r.x()-5, r.y()-5, 10, 10);
TQRect rt(r.x()+r.width()-5, r.y()-5, 10, 10);
TQRect lb(r.x()-5, r.y()+r.height()-5, 10, 10);
TQRect rb(r.x()+r.width()-5, r.y()+r.height()-5, 10, 10);
d->ltActive = false;
d->rtActive = false;
d->lbActive = false;
d->rbActive = false;
if (lt.contains(e->x(), e->y()))
{
viewport()->setCursor(TQt::SizeFDiagCursor);
d->ltActive = true;
}
else if (rb.contains(e->x(), e->y()))
{
viewport()->setCursor(TQt::SizeFDiagCursor);
d->rbActive = true;
}
else if (lb.contains(e->x(), e->y()))
{
viewport()->setCursor(TQt::SizeBDiagCursor);
d->lbActive = true;
}
else if (rt.contains(e->x(), e->y()))
{
viewport()->setCursor(TQt::SizeBDiagCursor);
d->rtActive = true;
}
else
viewport()->unsetCursor();
}
}
void Canvas::contentsMouseReleaseEvent(TQMouseEvent *e)
{
if (!e)
return;
d->midButtonPressed = false;
if (d->pressedMoving)
{
d->pressedMoving = false;
viewport()->update();
}
if (d->pressedMoved && d->rubber)
{
// Normalize rubber rectangle to always have the selection into the image
TQRect rec = d->rubber->normalize();
if (rec.left() < d->pixmapRect.left()) rec.setLeft(d->pixmapRect.left());
if (rec.right() > d->pixmapRect.right()) rec.setRight(d->pixmapRect.right());
if (rec.top() < d->pixmapRect.top()) rec.setTop(d->pixmapRect.top());
if (rec.bottom() > d->pixmapRect.bottom()) rec.setBottom(d->pixmapRect.bottom());
d->rubber->setLeft(rec.left());
d->rubber->setRight(rec.right());
d->rubber->setTop(rec.top());
d->rubber->setBottom(rec.bottom());
d->tileCache.clear();
viewport()->setMouseTracking(true);
if (d->im->imageValid())
emit signalSelected(true);
}
else
{
d->ltActive = false;
d->rtActive = false;
d->lbActive = false;
d->rbActive = false;
viewport()->setMouseTracking(false);
viewport()->unsetCursor();
if (d->im->imageValid())
emit signalSelected(false);
}
if (e->button() != TQt::LeftButton)
{
viewport()->unsetCursor();
}
if (e->button() == TQt::RightButton)
{
emit signalRightButtonClicked();
}
}
void Canvas::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)
{
if (e->delta() < 0)
slotDecreaseZoom();
else if (e->delta() > 0)
slotIncreaseZoom();
return;
}
TQScrollView::contentsWheelEvent(e);
}
bool Canvas::maxZoom()
{
return ((d->zoom * d->zoomMultiplier) >= d->maxZoom);
}
bool Canvas::minZoom()
{
return ((d->zoom / d->zoomMultiplier) <= d->minZoom);
}
bool Canvas::exifRotated()
{
return d->im->exifRotated();
}
double Canvas::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();
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
{
// We need to go through the list in reverse order,
// however, tqCopyBackward does not seem to work here, so
// a simple for loop over integers is used instead.
for(int i=snapValues.size()-1; i>=0; i--)
{
double z = snapValues[i];
if ((d->zoom > z) && (zoom < z))
{
zoom = z;
break;
}
}
}
return zoom;
}
void Canvas::slotIncreaseZoom()
{
if (maxZoom())
return;
double zoom = d->zoom * d->zoomMultiplier;
zoom = snapZoom(zoom);
setZoomFactor(zoom);
}
void Canvas::slotDecreaseZoom()
{
if (minZoom())
return;
double zoom = d->zoom / d->zoomMultiplier;
zoom = snapZoom(zoom);
setZoomFactor(zoom);
}
void Canvas::setZoomFactorSnapped(double zoom)
{
double fit = calcAutoZoomFactor();
if (fabs(zoom-fit) < 0.05)
{
// If 1.0 or 0.5 are even closer to zoom than fit, then choose these.
if (fabs(zoom-fit) > fabs(zoom-1.0) )
{
zoom = 1.0;
}
else if (fabs(zoom-fit) > fabs(zoom-0.5) )
{
zoom = 0.5;
}
else
{
zoom = fit;
}
}
else
{
if (fabs(zoom-1.0) < 0.05)
{
zoom = 1.0;
}
if (fabs(zoom-0.5) < 0.05)
{
zoom = 0.5;
}
}
setZoomFactor(zoom);
}
double Canvas::zoomFactor()
{
return d->zoom;
}
void Canvas::setZoomFactor(double zoom)
{
if (d->autoZoom)
{
d->autoZoom = false;
emit signalToggleOffFitToWindow();
}
// Zoom using center of canvas and given zoom factor.
double cpx = contentsX() + visibleWidth() / 2.0;
double cpy = contentsY() + visibleHeight() / 2.0;
cpx = (cpx / d->tileSize) * floor(d->tileSize / d->zoom);
cpy = (cpy / d->tileSize) * floor(d->tileSize / d->zoom);
d->zoom = zoom;
d->im->zoom(d->zoom);
updateContentsSize(false);
viewport()->setUpdatesEnabled(false);
center((int)((cpx * d->tileSize) / floor(d->tileSize / d->zoom)),
(int)((cpy * d->tileSize) / floor(d->tileSize / d->zoom)));
viewport()->setUpdatesEnabled(true);
viewport()->update();
emit signalZoomChanged(d->zoom);
}
void Canvas::fitToSelect()
{
int xSel, ySel, wSel, hSel;
d->im->getSelectedArea(xSel, ySel, wSel, hSel);
if (wSel && hSel )
{
// If selected area, use center of selection
// and recompute zoom factor accordinly.
double cpx = xSel + wSel / 2.0;
double cpy = ySel + hSel / 2.0;
double srcWidth = wSel;
double srcHeight = hSel;
double dstWidth = contentsRect().width();
double dstHeight = contentsRect().height();
d->zoom = TQMIN(dstWidth/srcWidth, dstHeight/srcHeight);
d->autoZoom = false;
emit signalToggleOffFitToWindow();
d->im->zoom(d->zoom);
updateContentsSize(true);
viewport()->setUpdatesEnabled(false);
center((int)((cpx * d->tileSize) / floor(d->tileSize / d->zoom)),
(int)((cpy * d->tileSize) / floor(d->tileSize / d->zoom)));
viewport()->setUpdatesEnabled(true);
viewport()->update();
emit signalZoomChanged(d->zoom);
}
}
bool Canvas::fitToWindow()
{
return d->autoZoom;
}
void Canvas::toggleFitToWindow()
{
d->autoZoom = !d->autoZoom;
if (d->autoZoom)
updateAutoZoom();
else
{
d->zoom = 1.0;
emit signalZoomChanged(d->zoom);
}
d->im->zoom(d->zoom);
updateContentsSize(false);
slotZoomChanged(d->zoom);
viewport()->update();
}
void Canvas::slotRotate90()
{
d->im->rotate90();
}
void Canvas::slotRotate180()
{
d->im->rotate180();
}
void Canvas::slotRotate270()
{
d->im->rotate270();
}
void Canvas::slotFlipHoriz()
{
d->im->flipHoriz();
}
void Canvas::slotFlipVert()
{
d->im->flipVert();
}
void Canvas::slotCrop()
{
int x, y, w, h;
d->im->getSelectedArea(x, y, w, h);
if (!w && !h ) // No current selection.
return;
d->im->crop(x, y, w, h);
}
void Canvas::resizeImage(int w, int h)
{
d->im->resize(w, h);
}
void Canvas::setBackgroundColor(const TQColor& color)
{
if (d->bgColor == color)
return;
d->bgColor = color;
viewport()->update();
}
void Canvas::setICCSettings(ICCSettingsContainer *cmSettings)
{
d->im->setICCSettings(cmSettings);
d->tileCache.clear();
viewport()->update();
}
void Canvas::setExposureSettings(ExposureSettingsContainer *expoSettings)
{
d->im->setExposureSettings(expoSettings);
d->tileCache.clear();
viewport()->update();
}
void Canvas::setExifOrient(bool exifOrient)
{
d->im->setExifOrient(exifOrient);
viewport()->update();
}
void Canvas::increaseGamma()
{
d->im->changeGamma(1);
d->tileCache.clear();
viewport()->update();
}
void Canvas::decreaseGamma()
{
d->im->changeGamma(-1);
d->tileCache.clear();
viewport()->update();
}
void Canvas::increaseBrightness()
{
d->im->changeBrightness(1);
d->tileCache.clear();
viewport()->update();
}
void Canvas::decreaseBrightness()
{
d->im->changeBrightness(-1);
d->tileCache.clear();
viewport()->update();
}
void Canvas::increaseContrast()
{
d->im->changeContrast(5);
d->tileCache.clear();
viewport()->update();
}
void Canvas::decreaseContrast()
{
d->im->changeContrast(-5);
d->tileCache.clear();
viewport()->update();
}
void Canvas::slotRestore()
{
d->im->restore();
}
void Canvas::slotUndo(int steps)
{
while(steps > 0)
{
d->im->undo();
--steps;
}
}
void Canvas::getUndoHistory(TQStringList &titles)
{
d->im->getUndoHistory(titles);
}
void Canvas::getRedoHistory(TQStringList &titles)
{
d->im->getRedoHistory(titles);
}
void Canvas::slotRedo(int steps)
{
while(steps > 0)
{
d->im->redo();
--steps;
}
}
void Canvas::slotCopy()
{
int x, y, w, h;
d->im->getSelectedArea(x, y, w, h);
if (!w && !h ) // No current selection.
return;
TQApplication::setOverrideCursor (TQt::waitCursor);
uchar* data = d->im->getImageSelection();
DImg selDImg = DImg(w, h, d->im->sixteenBit(), d->im->hasAlpha(), data);
delete [] data;
TQImage selImg = selDImg.copyTQImage();
TQApplication::clipboard()->setData(new TQImageDrag(selImg), TQClipboard::Clipboard);
TQApplication::restoreOverrideCursor ();
}
void Canvas::slotSelected()
{
int x=0, y=0, w=0, h=0;
if (d->rubber && d->pressedMoved)
{
TQRect sel = calcSeletedArea();
x = sel.x();
y = sel.y();
w = sel.width();
h = sel.height();
}
d->im->setSelectedArea(x, y, w, h);
}
TQRect Canvas::calcSeletedArea()
{
int x=0, y=0, w=0, h=0;
TQRect r(d->rubber->normalize());
if (r.isValid())
{
r.moveBy(- d->pixmapRect.x(), - d->pixmapRect.y());
x = (int)(((double)r.x() / d->tileSize) * floor(d->tileSize / d->zoom));
y = (int)(((double)r.y() / d->tileSize) * floor(d->tileSize / d->zoom));
w = (int)(((double)r.width() / d->tileSize) * floor(d->tileSize / d->zoom));
h = (int)(((double)r.height() / d->tileSize) * floor(d->tileSize / d->zoom));
x = TQMIN(imageWidth(), TQMAX(x, 0));
y = TQMIN(imageHeight(), TQMAX(y, 0));
w = TQMIN(imageWidth(), TQMAX(w, 0));
h = TQMIN(imageHeight(), TQMAX(h, 0));
// Avoid empty selection by rubberband - at least mark one pixel
// At high zoom factors, the rubberband may operate at subpixel level!
if (w == 0)
w = 1;
if (h == 0)
h = 1;
}
return TQRect(x, y, w, h);
}
void Canvas::slotModified()
{
if (d->autoZoom)
updateAutoZoom();
d->im->zoom(d->zoom);
updateContentsSize(true);
viewport()->update();
// To be sure than corner widget used to pan image will be hide/show
// accordinly with new image size (if changed).
slotZoomChanged(d->zoom);
emit signalChanged();
}
void Canvas::slotCornerButtonPressed()
{
if (d->panIconPopup)
{
d->panIconPopup->hide();
delete d->panIconPopup;
d->panIconPopup = 0;
}
d->panIconPopup = new TDEPopupFrame(this);
ImagePanIconWidget *pan = new ImagePanIconWidget(180, 120, d->panIconPopup);
d->panIconPopup->setMainWidget(pan);
TQRect r((int)(contentsX() / d->zoom), (int)(contentsY() / d->zoom),
(int)(visibleWidth() / d->zoom), (int)(visibleHeight() / d->zoom));
pan->setRegionSelection(r);
pan->setMouseFocus();
connect(pan, TQT_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
this, TQT_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
connect(pan, TQT_SIGNAL(signalHiden()),
this, TQT_SLOT(slotPanIconHiden()));
TQPoint g = mapToGlobal(viewport()->pos());
g.setX(g.x()+ viewport()->size().width());
g.setY(g.y()+ viewport()->size().height());
d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
g.y() - d->panIconPopup->height()));
pan->setCursorToLocalRegionSelectionCenter();
}
void Canvas::slotPanIconHiden()
{
d->cornerButton->blockSignals(true);
d->cornerButton->animateClick();
d->cornerButton->blockSignals(false);
}
void Canvas::slotPanIconSelectionMoved(const TQRect& r, bool b)
{
setContentsPos((int)(r.x()*d->zoom), (int)(r.y()*d->zoom));
if (b)
{
d->panIconPopup->hide();
delete d->panIconPopup;
d->panIconPopup = 0;
slotPanIconHiden();
}
}
void Canvas::slotZoomChanged(double /*zoom*/)
{
updateScrollBars();
if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
d->cornerButton->show();
else
d->cornerButton->hide();
}
void Canvas::slotSelectAll()
{
if (d->rubber)
{
delete d->rubber;
d->rubber = 0;
}
d->rubber = new TQRect(d->pixmapRect);
d->pressedMoved = true;
d->tileCache.clear();
viewport()->setMouseTracking(true);
viewport()->update();
if (d->im->imageValid())
emit signalSelected(true);
}
void Canvas::slotSelectNone()
{
reset();
viewport()->update();
}
} // namespace Digikam