/* ============================================================ * * 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 * * 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 #include #include #include #include #include #include #include #include // KDE includes. #include #include #include #include #include #include #include #include #include #include #include // 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 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 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 albumIDs; TQValueList 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 albumIDs; TQValueList imageIDs; KURL::List urls; KURL::List kioURLs; ImageInfoList list; if (ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs)) { for (TQValueList::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 itemIDs = AlbumManager::instance()->albumDB()->getItemIDsInAlbum(albumID); for (TQValueList::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 itemIDs = man->albumDB()->getItemIDsInTag(tagID, true); ImageInfoList imageInfoList; for (TQValueList::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