/* ============================================================
 *
 * 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, TQ_SIGNAL(signalZoomChanged(double)),
            this, TQ_SLOT(slotZoomChanged(double)));

    connect(d->cornerButton, TQ_SIGNAL(pressed()),
            this, TQ_SLOT(slotCornerButtonPressed()));

    connect(d->im, TQ_SIGNAL(signalModified()),
            this, TQ_SLOT(slotModified()));

    connect(d->im, TQ_SIGNAL(signalUndoStateChanged(bool, bool, bool)),
            this, TQ_SIGNAL(signalUndoStateChanged(bool, bool, bool)));

    connect(d->im, TQ_SIGNAL(signalLoadingStarted(const TQString&)),
            this, TQ_SIGNAL(signalLoadingStarted(const TQString&)));

    connect(d->im, TQ_SIGNAL(signalImageLoaded(const TQString&, bool)),
            this, TQ_SLOT(slotImageLoaded(const TQString&, bool)));

    connect(d->im, TQ_SIGNAL(signalImageSaved(const TQString&, bool)),
            this, TQ_SLOT(slotImageSaved(const TQString&, bool)));

    connect(d->im, TQ_SIGNAL(signalLoadingProgress(const TQString&, float)),
            this, TQ_SIGNAL(signalLoadingProgress(const TQString&, float)));

    connect(d->im, TQ_SIGNAL(signalSavingProgress(const TQString&, float)),
            this, TQ_SIGNAL(signalSavingProgress(const TQString&, float)));

    connect(this, TQ_SIGNAL(signalSelected(bool)),
            this, TQ_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(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(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().drawPrimitive(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, TQ_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
            this, TQ_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
    
    connect(pan, TQ_SIGNAL(signalHiden()),
            this, TQ_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
