diff options
Diffstat (limited to 'src/utilities/lighttable/lighttablepreview.cpp')
-rw-r--r-- | src/utilities/lighttable/lighttablepreview.cpp | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/src/utilities/lighttable/lighttablepreview.cpp b/src/utilities/lighttable/lighttablepreview.cpp new file mode 100644 index 00000000..100427ec --- /dev/null +++ b/src/utilities/lighttable/lighttablepreview.cpp @@ -0,0 +1,777 @@ +/* ============================================================ + * + * 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 |