/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2006-21-12
 * Description : digiKam light table preview item.
 *
 * Copyright (C) 2006-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.
 * 
 * ============================================================ */

// TQt includes.

#include <tqpainter.h>
#include <tqcursor.h>
#include <tqstring.h>
#include <tqvaluevector.h>
#include <tqfileinfo.h>
#include <tqtoolbutton.h>
#include <tqtooltip.h>
#include <tqpixmap.h>
#include <tqdrawutil.h>

// KDE includes.

#include <kdialogbase.h>
#include <tdelocale.h>
#include <kservice.h>
#include <krun.h>
#include <ktrader.h>
#include <kmimetype.h>
#include <kcursor.h>
#include <kdatetbl.h>
#include <kiconloader.h>
#include <kprocess.h>
#include <tdeapplication.h>

// Local includes.

#include "dimg.h"
#include "ddebug.h"
#include "albumdb.h"
#include "constants.h"
#include "albummanager.h"
#include "albumsettings.h"
#include "dragobjects.h"
#include "dmetadata.h"
#include "dpopupmenu.h"
#include "metadatahub.h"
#include "paniconwidget.h"
#include "previewloadthread.h"
#include "loadingdescription.h"
#include "tagspopupmenu.h"
#include "ratingpopupmenu.h"
#include "themeengine.h"
#include "lighttablepreview.h"
#include "lighttablepreview.moc"

namespace Digikam
{

class LightTablePreviewPriv
{
public:

    LightTablePreviewPriv()
    {
        panIconPopup         = 0;
        panIconWidget        = 0;
        cornerButton         = 0;
        previewThread        = 0;
        previewPreloadThread = 0;
        imageInfo            = 0;
        hasPrev              = false;
        hasNext              = false;
        selected             = false;
        dragAndDropEnabled   = true;
        loadFullImageSize    = false;
        currentFitWindowZoom = 0;
        previewSize          = 1024;
    }

    bool               hasPrev;
    bool               hasNext;
    bool               selected;
    bool               dragAndDropEnabled;
    bool               loadFullImageSize;

    int                previewSize;

    double             currentFitWindowZoom;

    TQString            path;
    TQString            nextPath;
    TQString            previousPath;

    TQToolButton       *cornerButton;

    TDEPopupFrame       *panIconPopup;

    PanIconWidget     *panIconWidget;

    DImg               preview;

    ImageInfo         *imageInfo;

    PreviewLoadThread *previewThread;
    PreviewLoadThread *previewPreloadThread;
};
    
LightTablePreview::LightTablePreview(TQWidget *parent)
                 : PreviewWidget(parent)
{
    d = new LightTablePreviewPriv;

    // get preview size from screen size, but limit from VGA to WTQXGA
    d->previewSize = TQMAX(TDEApplication::desktop()->height(),
                          TDEApplication::desktop()->width());
    if (d->previewSize < 640)
        d->previewSize = 640;
    if (d->previewSize > 2560)
        d->previewSize = 2560;
    
    viewport()->setAcceptDrops(true);
    setAcceptDrops(true); 

    slotThemeChanged();
    setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);

    d->cornerButton = new TQToolButton(this);
    d->cornerButton->setIconSet(SmallIcon("move"));
    d->cornerButton->hide();
    TQToolTip::add(d->cornerButton, i18n("Pan the image"));
    setCornerWidget(d->cornerButton);

    setLineWidth(5);
    setSelected(false);

    // ------------------------------------------------------------

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

    connect(this, TQ_SIGNAL(signalRightButtonClicked()),
            this, TQ_SLOT(slotContextMenu()));

    connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
            this, TQ_SLOT(slotThemeChanged()));

    // ------------------------------------------------------------

    slotReset(); 
}

LightTablePreview::~LightTablePreview()
{
    delete d->previewThread;
    delete d->previewPreloadThread;
    delete d;
}

void LightTablePreview::setLoadFullImageSize(bool b)
{
    d->loadFullImageSize = b;
    reload();
}

void LightTablePreview::setDragAndDropEnabled(bool b)
{
    d->dragAndDropEnabled = b;
}

void LightTablePreview::setDragAndDropMessage()
{
    if (d->dragAndDropEnabled)
    {
        TQPixmap pix(visibleWidth(), visibleHeight());
        pix.fill(ThemeEngine::instance()->baseColor());
        TQPainter p(&pix);
        p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
        p.drawText(0, 0, pix.width(), pix.height(),
                   TQt::AlignCenter|TQt::WordBreak, 
                   i18n("Drag and drop an image here"));
        p.end();
        setImage(pix.convertToImage());
    }
}

void LightTablePreview::setImage(const DImg& image)
{   
    d->preview = image;

    updateZoomAndSize(true);

    viewport()->setUpdatesEnabled(true);
    viewport()->update();
}

DImg& LightTablePreview::getImage() const
{
    return d->preview;
}

TQSize LightTablePreview::getImageSize()
{
    return d->preview.size();
}

void LightTablePreview::reload()
{
    // cache is cleaned from AlbumIconView::refreshItems
    setImagePath(d->path);
}

void LightTablePreview::setPreviousNextPaths(const TQString& previous, const TQString &next)
{
    d->nextPath     = next;
    d->previousPath = previous;
}

void LightTablePreview::setImagePath(const TQString& path)
{
    setCursor( KCursor::waitCursor() );

    d->path         = path;
    d->nextPath     = TQString();
    d->previousPath = TQString();

    if (d->path.isEmpty())
    {
        slotReset();
        unsetCursor();
        return;
    }

    if (!d->previewThread)
    {
        d->previewThread = new PreviewLoadThread();
        connect(d->previewThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
                this, TQ_SLOT(slotGotImagePreview(const LoadingDescription &, const DImg&)));
    }
    if (!d->previewPreloadThread)
    {
        d->previewPreloadThread = new PreviewLoadThread();
        connect(d->previewPreloadThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
                this, TQ_SLOT(slotNextPreload()));
    }

    if (d->loadFullImageSize)
        d->previewThread->loadHighQuality(LoadingDescription(path, 0, AlbumSettings::instance()->getExifRotate()));
    else
        d->previewThread->load(LoadingDescription(path, d->previewSize, AlbumSettings::instance()->getExifRotate()));
}

void LightTablePreview::slotGotImagePreview(const LoadingDescription &description, const DImg& preview)
{
    if (description.filePath != d->path)
        return;   

    if (preview.isNull())
    {
        TQPixmap pix(visibleWidth(), visibleHeight());
        pix.fill(ThemeEngine::instance()->baseColor());
        TQPainter p(&pix);
        TQFileInfo info(d->path);
        p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
        p.drawText(0, 0, pix.width(), pix.height(),
                   TQt::AlignCenter|TQt::WordBreak, 
                   i18n("Unable to display preview for\n\"%1\"")
                   .arg(info.fileName()));
        p.end();
        setImage(DImg(pix.convertToImage()));

        emit signalPreviewLoaded(false);
    }
    else
    {
        DImg img(preview);
        if (AlbumSettings::instance()->getExifRotate())
            d->previewThread->exifRotate(img, description.filePath);
        setImage(img);
        emit signalPreviewLoaded(true);
    }

    unsetCursor();
    slotNextPreload();
}

void LightTablePreview::slotNextPreload()
{
    TQString loadPath;
    if (!d->nextPath.isNull())
    {
        loadPath    = d->nextPath;
        d->nextPath = TQString();
    }
    else if (!d->previousPath.isNull())
    {
        loadPath        = d->previousPath;
        d->previousPath = TQString();
    }
    else
        return;

    d->previewPreloadThread->load(LoadingDescription(loadPath, d->previewSize,
                                  AlbumSettings::instance()->getExifRotate()));
}

void LightTablePreview::setImageInfo(ImageInfo* info, ImageInfo *previous, ImageInfo *next)
{
    d->imageInfo = info;
    d->hasPrev   = previous;
    d->hasNext   = next;

    if (d->imageInfo)
        setImagePath(info->filePath());
    else
    {
        setImagePath();
        setSelected(false);
    }

    setPreviousNextPaths(previous ? previous->filePath() : TQString(),
                         next     ? next->filePath()     : TQString());
}

ImageInfo* LightTablePreview::getImageInfo() const
{
    return d->imageInfo;
}

void LightTablePreview::slotContextMenu()
{
    RatingPopupMenu *ratingMenu     = 0;
    TagsPopupMenu   *assignTagsMenu = 0;
    TagsPopupMenu   *removeTagsMenu = 0;

    if (!d->imageInfo)
        return;

    //-- Open With Actions ------------------------------------

    KURL url(d->imageInfo->kurl().path());
    KMimeType::Ptr mimePtr = KMimeType::findByURL(url, 0, true, true);

    TQValueVector<KService::Ptr> serviceVector;
    TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(), "Type == 'Application'");

    TQPopupMenu openWithMenu;

    TDETrader::OfferList::Iterator iter;
    KService::Ptr ptr;
    int index = 100;

    for( iter = offers.begin(); iter != offers.end(); ++iter )
    {
        ptr = *iter;
        openWithMenu.insertItem( ptr->pixmap(TDEIcon::Small), ptr->name(), index++);
        serviceVector.push_back(ptr);
    }

    DPopupMenu popmenu(this);
    
    //-- Zoom actions -----------------------------------------------

    popmenu.insertItem(SmallIcon("viewmag"), i18n("Zoom in"), 17);
    popmenu.insertItem(SmallIcon("zoom-out"), i18n("Zoom out"), 18);
    popmenu.insertItem(SmallIcon("view_fit_window"), i18n("Fit to &Window"), 19);

    //-- Edit actions -----------------------------------------------

    popmenu.insertSeparator();
    popmenu.insertItem(SmallIcon("slideshow"), i18n("SlideShow"), 16);
    popmenu.insertItem(SmallIcon("editimage"), i18n("Edit..."), 12);
    popmenu.insertItem(i18n("Open With"), &openWithMenu, 13);

    //-- Trash action -------------------------------------------

    popmenu.insertSeparator();
    popmenu.insertItem(SmallIcon("edittrash"), i18n("Move to Trash"), 14);

    // Bulk assignment/removal of tags --------------------------

    TQ_LLONG id = d->imageInfo->id();
    TQValueList<TQ_LLONG> idList;
    idList.append(id);

    assignTagsMenu = new TagsPopupMenu(idList, 1000, TagsPopupMenu::ASSIGN);
    removeTagsMenu = new TagsPopupMenu(idList, 2000, TagsPopupMenu::REMOVE);

    popmenu.insertSeparator();

    popmenu.insertItem(i18n("Assign Tag"), assignTagsMenu);
    int i = popmenu.insertItem(i18n("Remove Tag"), removeTagsMenu);

    connect(assignTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
            this, TQ_SLOT(slotAssignTag(int)));

    connect(removeTagsMenu, TQ_SIGNAL(signalTagActivated(int)),
            this, TQ_SLOT(slotRemoveTag(int)));

    AlbumDB* db = AlbumManager::instance()->albumDB();
    if (!db->hasTags( idList ))
        popmenu.setItemEnabled(i, false);

    popmenu.insertSeparator();

    // Assign Star Rating -------------------------------------------

    ratingMenu = new RatingPopupMenu();
    
    connect(ratingMenu, TQ_SIGNAL(activated(int)),
            this, TQ_SLOT(slotAssignRating(int)));

    popmenu.insertItem(i18n("Assign Rating"), ratingMenu);

    // --------------------------------------------------------

    int idm = popmenu.exec(TQCursor::pos());

    switch(idm) 
    {
        case 12:     // Edit...
        {
            emit signalEditItem(d->imageInfo);
            break;
        }

        case 14:     // Move to trash
        {
            emit signalDeleteItem(d->imageInfo);
            break;
        }

        case 16:     // SlideShow
        {
            emit signalSlideShow();
            break;
        }

        case 17:     // Zoom in
        {
            slotIncreaseZoom();
            break;
        }

        case 18:     // Zoom out
        {
            slotDecreaseZoom();
            break;
        }

        case 19:     // Fit to window
        {
            fitToWindow();
            break;
        }

        default:
            break;
    }

    // Open With...
    if (idm >= 100 && idm < 1000) 
    {
        KService::Ptr imageServicePtr = serviceVector[idm-100];
        KRun::run(*imageServicePtr, url);
    }

    serviceVector.clear();
    delete assignTagsMenu;
    delete removeTagsMenu;
    delete ratingMenu;
}

void LightTablePreview::slotAssignTag(int tagID)
{
    if (d->imageInfo)
    {
        MetadataHub hub;
        hub.load(d->imageInfo);
        hub.setTag(tagID, true);
        hub.write(d->imageInfo, MetadataHub::PartialWrite);
        hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
    }
}

void LightTablePreview::slotRemoveTag(int tagID)
{
    if (d->imageInfo)
    {
        MetadataHub hub;
        hub.load(d->imageInfo);
        hub.setTag(tagID, false);
        hub.write(d->imageInfo, MetadataHub::PartialWrite);
        hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
    }
}

void LightTablePreview::slotAssignRating(int rating)
{
    rating = TQMIN(RatingMax, TQMAX(RatingMin, rating));
    if (d->imageInfo)
    {
        MetadataHub hub;
        hub.load(d->imageInfo);
        hub.setRating(rating);
        hub.write(d->imageInfo, MetadataHub::PartialWrite);
        hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
    }
}

void LightTablePreview::slotThemeChanged()
{
    setBackgroundColor(ThemeEngine::instance()->baseColor());
    frameChanged();
}

void LightTablePreview::slotCornerButtonPressed()
{    
    if (d->panIconPopup)
    {
        d->panIconPopup->hide();
        delete d->panIconPopup;
        d->panIconPopup = 0;
    }

    d->panIconPopup    = new TDEPopupFrame(this);
    PanIconWidget *pan = new PanIconWidget(d->panIconPopup);
    pan->setImage(180, 120, getImage());
    d->panIconPopup->setMainWidget(pan);

    TQRect r((int)(contentsX()    / zoomFactor()), (int)(contentsY()     / zoomFactor()),
            (int)(visibleWidth() / zoomFactor()), (int)(visibleHeight() / zoomFactor()));
    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 LightTablePreview::slotPanIconHiden()
{
    d->cornerButton->blockSignals(true);
    d->cornerButton->animateClick();
    d->cornerButton->blockSignals(false);
}

void LightTablePreview::slotPanIconSelectionMoved(const TQRect& r, bool b)
{
    setContentsPos((int)(r.x()*zoomFactor()), (int)(r.y()*zoomFactor()));

    if (b)
    {
        d->panIconPopup->hide();
        delete d->panIconPopup;
        d->panIconPopup = 0;
        slotPanIconHiden();
    }
}

void LightTablePreview::zoomFactorChanged(double zoom)
{
    updateScrollBars();

    if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
        d->cornerButton->show();
    else
        d->cornerButton->hide();        

    PreviewWidget::zoomFactorChanged(zoom);
}

void LightTablePreview::resizeEvent(TQResizeEvent* e)
{
    if (!e) return;

    TQScrollView::resizeEvent(e);

    if (!d->imageInfo)
    {
        d->cornerButton->hide();
        setDragAndDropMessage();
    }

    updateZoomAndSize(false);
}

void LightTablePreview::updateZoomAndSize(bool alwaysFitToWindow)
{
    // Set zoom for fit-in-window as minimum, but dont scale up images
    // that are smaller than the available space, only scale down.
    double zoom = calcAutoZoomFactor(ZoomInOnly);
    setZoomMin(zoom);
    setZoomMax(zoom*12.0);

    // Is currently the zoom factor set to fit to window? Then set it again to fit the new size.
    if (zoomFactor() < zoom || alwaysFitToWindow || zoomFactor() == d->currentFitWindowZoom)
    {
        setZoomFactor(zoom);
    }

    // store which zoom factor means it is fit to window
    d->currentFitWindowZoom = zoom;

    updateContentsSize();
}

int LightTablePreview::previewWidth()
{
    return d->preview.width();
}

int LightTablePreview::previewHeight()
{
    return d->preview.height();
}

bool LightTablePreview::previewIsNull()
{
    return d->preview.isNull();
}

void LightTablePreview::resetPreview()
{
    d->preview   = DImg();
    d->path      = TQString(); 
    d->imageInfo = 0;

    setDragAndDropMessage();
    updateZoomAndSize(true);
    viewport()->setUpdatesEnabled(true);
    viewport()->update();
    emit signalPreviewLoaded(false);
}

void LightTablePreview::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
{
    DImg img     = d->preview.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());    
    TQPixmap pix2 = img.convertToPixmap();
    bitBlt(pix, 0, 0, &pix2, 0, 0);
}

void LightTablePreview::contentsDragMoveEvent(TQDragMoveEvent *e)
{
    if (d->dragAndDropEnabled)
    {
        int             albumID;
        TQValueList<int> albumIDs;
        TQValueList<int> imageIDs;
        KURL::List      urls;
        KURL::List      kioURLs;        
    
        if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs) ||
            AlbumDrag::decode(e, urls, albumID) ||
            TagDrag::canDecode(e))
        {
            e->accept();
            return;
        }
    }

    e->ignore();
}

void LightTablePreview::contentsDropEvent(TQDropEvent *e)
{
    if (d->dragAndDropEnabled)
    {
        int             albumID;
        TQValueList<int> albumIDs;
        TQValueList<int> imageIDs;
        KURL::List      urls;
        KURL::List      kioURLs;  
        ImageInfoList   list;
    
        if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs))
        {
            for (TQValueList<int>::const_iterator it = imageIDs.begin();
                 it != imageIDs.end(); ++it)
            {
                list.append(new ImageInfo(*it));
            }

            emit signalDroppedItems(list);
            e->accept();
            return;
        }
        else if (AlbumDrag::decode(e, urls, albumID))
        {
            TQValueList<TQ_LLONG> itemIDs = AlbumManager::instance()->albumDB()->getItemIDsInAlbum(albumID);
    
            for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
                it != itemIDs.end(); ++it)
            {
                list.append(new ImageInfo(*it));
            }

            emit signalDroppedItems(list);
            e->accept();
            return;
        }
        else if(TagDrag::canDecode(e))
        {
            TQByteArray ba = e->encodedData("digikam/tag-id");
            TQDataStream ds(ba, IO_ReadOnly);
            int tagID;
            ds >> tagID;
    
            AlbumManager* man           = AlbumManager::instance();
            TQValueList<TQ_LLONG> itemIDs = man->albumDB()->getItemIDsInTag(tagID, true);
            ImageInfoList imageInfoList;
    
            for (TQValueList<TQ_LLONG>::const_iterator it = itemIDs.begin();
                it != itemIDs.end(); ++it)
            {
                list.append(new ImageInfo(*it));
            }
        
            emit signalDroppedItems(list);
            e->accept();
            return;
        }   
    }

    e->ignore();
}

void LightTablePreview::setSelected(bool sel)
{
    if (d->selected != sel)
    {
        d->selected = sel;
        frameChanged();
    }
}

bool LightTablePreview::isSelected()
{
    return d->selected;
}

void LightTablePreview::drawFrame(TQPainter *p)
{
    if (d->selected)
    {
        qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->thumbSelColor(), lineWidth());
        qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->textSelColor(), 2);
    }
    else 
        qDrawPlainRect(p, frameRect(), ThemeEngine::instance()->baseColor(), lineWidth());
}

}  // NameSpace Digikam
