• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

digikam

albumiconview.cpp

Go to the documentation of this file.
00001 /* ============================================================
00002  *
00003  * This file is a part of digiKam project
00004  * http://www.digikam.org
00005  *
00006  * Date        : 2002-16-10
00007  * Description : album icon view
00008  *
00009  * Copyright (C) 2002-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
00010  * Copyright (C) 2002-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
00011  * Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
00012  *
00013  * This program is free software; you can redistribute it
00014  * and/or modify it under the terms of the GNU General
00015  * Public License as published by the Free Software Foundation;
00016  * either version 2, or (at your option)
00017  * any later version.
00018  *
00019  * This program is distributed in the hope that it will be useful,
00020  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00021  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00022  * GNU General Public License for more details.
00023  *
00024  * ============================================================ */
00025 
00026 // C Ansi includes.
00027 
00028 extern "C"
00029 {
00030 #include <sys/types.h>
00031 #include <sys/stat.h>
00032 #include <unistd.h>
00033 }
00034 
00035 // C++ includes.
00036 
00037 #include <cstdio>
00038 
00039 // Qt includes.
00040 
00041 #include <Q3IntDict>
00042 #include <Q3Dict>
00043 #include <QImage>
00044 #include <QString>
00045 #include <QStringList>
00046 #include <QEvent>
00047 #include <QPainter>
00048 #include <QPoint>
00049 #include <QPolygon>
00050 #include <QDateTime>
00051 #include <QFileInfo>
00052 #include <QFile>
00053 #include <QCursor>
00054 #include <QDataStream>
00055 #include <QTimer>
00056 #include <QClipboard>
00057 #include <QPixmap>
00058 #include <QDragMoveEvent>
00059 #include <QDropEvent>
00060 #include <QResizeEvent>
00061 
00062 // KDE includes.
00063 
00064 #include <kdebug.h>
00065 #include <kurl.h>
00066 #include <kapplication.h>
00067 #include <klocale.h>
00068 #include <kglobal.h>
00069 #include <kmessagebox.h>
00070 #include <kiconloader.h>
00071 #include <kpropsdlg.h>
00072 #include <ktrader.h>
00073 #include <kservice.h>
00074 #include <krun.h>
00075 #include <kaction.h>
00076 #include <kmenu.h>
00077 #include <kstandarddirs.h>
00078 #include <kiconeffect.h>
00079 #include <kdeversion.h>
00080 #include <kcalendarsystem.h>
00081 #include <kinputdialog.h>
00082 #include <kio/jobuidelegate.h>
00083 #include <kmimetypetrader.h>
00084 #include <kstandardaction.h>
00085 
00086 // LibKipi includes.
00087 
00088 #include <libkipi/pluginloader.h>
00089 #include <libkipi/plugin.h>
00090 
00091 // LibKDcraw includes.
00092 
00093 #include <libkdcraw/version.h>
00094 #include <libkdcraw/kdcraw.h>
00095 
00096 #if KDCRAW_VERSION < 0x000400
00097 #include <libkdcraw/dcrawbinary.h>
00098 #endif
00099 
00100 // Local includes.
00101 
00102 #include "constants.h"
00103 #include "album.h"
00104 #include "albumdb.h"
00105 #include "albummanager.h"
00106 #include "dio.h"
00107 #include "albumlister.h"
00108 #include "albumfiletip.h"
00109 #include "albumsettings.h"
00110 #include "databasetransaction.h"
00111 #include "databaseaccess.h"
00112 #include "imagewindow.h"
00113 #include "thumbnailsize.h"
00114 #include "themeengine.h"
00115 #include "dpopupmenu.h"
00116 #include "tagspopupmenu.h"
00117 #include "ratingpopupmenu.h"
00118 #include "scancontroller.h"
00119 #include "thumbnailloadthread.h"
00120 #include "cameraui.h"
00121 #include "ddragobjects.h"
00122 #include "dmetadata.h"
00123 #include "albumdb.h"
00124 #include "imageattributeswatch.h"
00125 #include "deletedialog.h"
00126 #include "albumiconitem.h"
00127 #include "albumicongroupitem.h"
00128 #include "loadingcacheinterface.h"
00129 #include "lighttablewindow.h"
00130 #include "statusprogressbar.h"
00131 #include "metadatahub.h"
00132 #include "albumiconview.h"
00133 #include "albumiconview.moc"
00134 
00135 namespace Digikam
00136 {
00137 
00138 class AlbumIconViewPrivate
00139 {
00140 public:
00141 
00142     void init()
00143     {
00144         imageLister   = 0;
00145         currentAlbum  = 0;
00146         albumSettings = 0;
00147         toolTip       = 0;
00148 
00149         // Pre-computed star polygon for a 15x15 pixmap.
00150         starPolygon << QPoint(0,  6);
00151         starPolygon << QPoint(5,  5);
00152         starPolygon << QPoint(7,  0);
00153         starPolygon << QPoint(9,  5);
00154         starPolygon << QPoint(14, 6);
00155         starPolygon << QPoint(10, 9);
00156         starPolygon << QPoint(11, 14);
00157         starPolygon << QPoint(7,  11);
00158         starPolygon << QPoint(3,  14);
00159         starPolygon << QPoint(4,  9);
00160 
00161         starPolygonSize = QSize(15, 15);
00162 
00163         ratingPixmaps = QVector<QPixmap>(10);
00164     }
00165 
00166     QString                          albumTitle;
00167     QString                          albumDate;
00168     QString                          albumComments;
00169 
00170     QRect                            itemRect;
00171     QRect                            itemRatingRect;
00172     QRect                            itemDateRect;
00173     QRect                            itemModDateRect;
00174     QRect                            itemPixmapRect;
00175     QRect                            itemNameRect;
00176     QRect                            itemCommentsRect;
00177     QRect                            itemResolutionRect;
00178     QRect                            itemSizeRect;
00179     QRect                            itemTagRect;
00180     QRect                            bannerRect;
00181 
00182     QPixmap                          itemRegPixmap;
00183     QPixmap                          itemSelPixmap;
00184     QPixmap                          bannerPixmap;
00185     QVector<QPixmap>                 ratingPixmaps;
00186 
00187     QFont                            fnReg;
00188     QFont                            fnCom;
00189     QFont                            fnXtra;
00190 
00191     QPolygon                         starPolygon;
00192     QSize                            starPolygonSize;
00193 
00194     Q3Dict<AlbumIconItem>            itemDict;
00195     QHash<ImageInfo, AlbumIconItem*> itemInfoMap;
00196 
00197     KUrl                             itemUrlToFind;
00198 
00199     AlbumLister                     *imageLister;
00200     Album                           *currentAlbum;
00201     const AlbumSettings             *albumSettings;
00202     Q3IntDict<AlbumIconGroupItem>    albumDict;
00203 
00204     ThumbnailSize                    thumbSize;
00205 
00206     AlbumFileTip                    *toolTip;
00207 };
00208 
00209 AlbumIconView::AlbumIconView(QWidget* parent)
00210              : IconView(parent)
00211 {
00212     d = new AlbumIconViewPrivate;
00213     d->init();
00214     d->imageLister = AlbumLister::instance();
00215     d->toolTip     = new AlbumFileTip(this);
00216 
00217     setAcceptDrops(true);
00218     viewport()->setAcceptDrops(true);
00219 
00220     // -- ImageLister connections -------------------------------------
00221 
00222     connect(d->imageLister, SIGNAL(signalNewFilteredItems(const ImageInfoList&)),
00223             this, SLOT(slotImageListerNewItems(const ImageInfoList&)));
00224 
00225     connect(d->imageLister, SIGNAL(signalDeleteFilteredItem(const ImageInfo &)),
00226             this, SLOT(slotImageListerDeleteItem(const ImageInfo &)) );
00227 
00228     connect(d->imageLister, SIGNAL(signalClear()),
00229             this, SLOT(slotImageListerClear()));
00230 
00231     // -- Icon connections --------------------------------------------
00232 
00233     connect(this, SIGNAL(signalDoubleClicked(IconItem*)),
00234             this, SLOT(slotDoubleClicked(IconItem*)));
00235 
00236     connect(this, SIGNAL(signalReturnPressed(IconItem*)),
00237             this, SLOT(slotDoubleClicked(IconItem*)));
00238 
00239     connect(this, SIGNAL(signalRightButtonClicked(IconItem*, const QPoint &)),
00240             this, SLOT(slotRightButtonClicked(IconItem*, const QPoint &)));
00241 
00242     connect(this, SIGNAL(signalRightButtonClicked(const QPoint &)),
00243             this, SLOT(slotRightButtonClicked(const QPoint &)));
00244 
00245     connect(this, SIGNAL(signalSelectionChanged()),
00246             this, SLOT(slotSelectionChanged()));
00247 
00248     connect(this, SIGNAL(signalShowToolTip(IconItem*)),
00249             this, SLOT(slotShowToolTip(IconItem*)));
00250 
00251     // -- ThemeEngine connections ---------------------------------------
00252 
00253     connect(ThemeEngine::instance(), SIGNAL(signalThemeChanged()),
00254             this, SLOT(slotThemeChanged()));
00255 
00256     // -- Pixmap manager connections ------------------------------------
00257 
00258     connect(ThumbnailLoadThread::defaultIconViewThread(), SIGNAL(signalThumbnailLoaded(const LoadingDescription &, const QPixmap&)),
00259             this, SLOT(slotThumbnailLoaded(const LoadingDescription &, const QPixmap&)));
00260 
00261     // -- ImageAttributesWatch connections ------------------------------
00262 
00263     ImageAttributesWatch *watch = ImageAttributesWatch::instance();
00264 
00265     connect(watch, SIGNAL(signalImageTagsChanged(qlonglong)),
00266             this, SLOT(slotImageAttributesChanged(qlonglong)));
00267 
00268     connect(watch, SIGNAL(signalImagesChanged(int)),
00269             this, SLOT(slotAlbumImagesChanged(int)));
00270 
00271     connect(watch, SIGNAL(signalImageRatingChanged(qlonglong)),
00272             this, SLOT(slotImageAttributesChanged(qlonglong)));
00273 
00274     connect(watch, SIGNAL(signalImageDateChanged(qlonglong)),
00275             this, SLOT(slotImageAttributesChanged(qlonglong)));
00276 
00277     connect(watch, SIGNAL(signalImageCaptionChanged(qlonglong)),
00278             this, SLOT(slotImageAttributesChanged(qlonglong)));
00279 }
00280 
00281 AlbumIconView::~AlbumIconView()
00282 {
00283     delete d->toolTip;
00284     delete d;
00285 }
00286 
00287 void AlbumIconView::applySettings(const AlbumSettings* settings)
00288 {
00289     if (!settings)
00290         return;
00291 
00292     d->albumSettings = settings;
00293 
00294     d->thumbSize = (ThumbnailSize::Size)d->albumSettings->getDefaultIconSize();
00295 
00296     setEnableToolTips(d->albumSettings->getShowToolTips());
00297 
00298     updateRectsAndPixmaps();
00299 
00300     d->imageLister->stop();
00301     clear();
00302 
00303     ThumbnailLoadThread::defaultIconViewThread()->setThumbnailSize(d->thumbSize.size());
00304 
00305     if (d->currentAlbum)
00306     {
00307         d->imageLister->openAlbum(d->currentAlbum);
00308     }
00309 }
00310 
00311 void AlbumIconView::setThumbnailSize(const ThumbnailSize& thumbSize)
00312 {
00313     if ( d->thumbSize != thumbSize)
00314     {
00315         d->imageLister->stop();
00316         clear();
00317 
00318         d->thumbSize = thumbSize;
00319         ThumbnailLoadThread::defaultIconViewThread()->setThumbnailSize(d->thumbSize.size());
00320 
00321         updateRectsAndPixmaps();
00322 
00323         d->imageLister->openAlbum(d->currentAlbum);
00324     }
00325 }
00326 
00327 void AlbumIconView::setAlbum(Album* album)
00328 {
00329     if (!album)
00330     {
00331         d->currentAlbum = 0;
00332         d->imageLister->stop();
00333         clear();
00334 
00335         return;
00336     }
00337 
00338     if (d->currentAlbum == album)
00339         return;
00340 
00341     d->imageLister->stop();
00342     clear();
00343 
00344     d->currentAlbum = album;
00345     d->imageLister->openAlbum(d->currentAlbum);
00346 
00347     updateRectsAndPixmaps();
00348 }
00349 
00350 void AlbumIconView::setAlbumItemToFind(const KUrl& url)
00351 {
00352     d->itemUrlToFind = url;
00353 }
00354 
00355 void AlbumIconView::refreshIcon(AlbumIconItem* item)
00356 {
00357     if (!item)
00358         return;
00359 
00360     emit signalSelectionChanged();
00361 }
00362 
00363 void AlbumIconView::clear(bool update)
00364 {
00365     emit signalCleared();
00366 
00367     d->itemDict.clear();
00368     d->albumDict.clear();
00369     d->itemInfoMap.clear();
00370 
00371     IconView::clear(update);
00372 
00373     emit signalSelectionChanged();
00374 }
00375 
00376 void AlbumIconView::slotImageListerNewItems(const ImageInfoList& itemList)
00377 {
00378     if (!d->currentAlbum || d->currentAlbum->isRoot())
00379         return;
00380 
00381     for (ImageInfoList::const_iterator it = itemList.begin(); it != itemList.end(); ++it)
00382     {
00383         KUrl url( it->fileUrl() );
00384         url.cleanPath();
00385 
00386         if (d->itemInfoMap.contains(*it))
00387         {
00388             // TODO: Make sure replacing slotImageListerDeleteItem with continue here is not wrong
00389             //slotImageListerDeleteItem((*itMap)->imageInfo());
00390             continue;
00391         }
00392 
00393         AlbumIconGroupItem* group = d->albumDict.find(it->albumId());
00394         if (!group)
00395         {
00396             group = new AlbumIconGroupItem(this, it->albumId());
00397             d->albumDict.insert(it->albumId(), group);
00398         }
00399 
00400         PAlbum *album = AlbumManager::instance()->findPAlbum(it->albumId());
00401         if (!album)
00402         {
00403             kWarning(50003) << "No album for item: " << it->name()
00404                             << ", albumID: " << it->albumId() << endl;
00405             continue;
00406         }
00407 
00408         AlbumIconItem* iconItem = new AlbumIconItem(group, (*it));
00409 
00410         d->itemDict.insert(url.url(), iconItem);
00411         d->itemInfoMap.insert((*it), iconItem);
00412     }
00413 
00414     // Make the icon, specified by d->itemUrlToFind, the current one
00415     // in the album icon view and make it visible.
00416     // This is for example used after a "Go To",
00417     // e.g. from tags (or date) view to folder view.
00418     // Note that AlbumIconView::slotImageListerNewItems may
00419     // be called several times after another, because images get
00420     // listed in packages of 200.
00421     // Therefore the item might not always be available in the very
00422     // first call when there are sufficiently many images.
00423     // Also, because of this, we cannot reset the item which is to be found,
00424     // i.e. something like d->itemUrlToFind = 0, after the item was found,
00425     // as then the visibility of this item is lost in a subsequent call.
00426     if (!d->itemUrlToFind.isEmpty())
00427     {
00428         AlbumIconItem* icon = findItem(d->itemUrlToFind.url());
00429         if (icon)
00430         {
00431             clearSelection();
00432             updateContents();
00433             setCurrentItem(icon);
00434             ensureItemVisible(icon);
00435 
00436             // make the item really visible
00437             // (the previous ensureItemVisible does not work)
00438             setStoredVisibleItem(icon);
00439             triggerRearrangement();
00440         }
00441     }
00442 
00443     emit signalItemsAdded();
00444 }
00445 
00446 void AlbumIconView::slotImageListerDeleteItem(const ImageInfo &item)
00447 {
00448     QHash<ImageInfo, AlbumIconItem*>::iterator itMap = d->itemInfoMap.find(item);
00449     if (itMap == d->itemInfoMap.end())
00450         return;
00451 
00452     AlbumIconItem* iconItem = (*itMap);
00453 
00454     /*
00455     // ?? Necessary? For what situation?
00456     KUrl url(item->kurl());
00457     url.cleanPath();
00458 
00459     AlbumIconItem *oldItem = d->itemDict[url.url()];
00460 
00461     if( oldItem &&
00462        (oldItem->imageInfo()->id() != iconItem->imageInfo()->id()))
00463     {
00464         return;
00465     }
00466     */
00467 
00468     //d->pixMan->deleteThumbnail(item->kurl());
00469 
00470     emit signalItemDeleted(iconItem);
00471 
00472     delete iconItem;
00473 
00474     d->itemInfoMap.remove(item);
00475     d->itemDict.remove(item.fileUrl().url());
00476 
00477     IconGroupItem* group = firstGroup();
00478     IconGroupItem* tmp;
00479 
00480     while (group)
00481     {
00482         tmp = group->nextGroup();
00483 
00484         if (group->count() == 0)
00485         {
00486             d->albumDict.remove(((AlbumIconGroupItem*)group)->albumID());
00487             delete group;
00488         }
00489 
00490         group = tmp;
00491     }
00492 }
00493 
00494 void AlbumIconView::slotImageListerClear()
00495 {
00496     clear();
00497 }
00498 
00499 void AlbumIconView::slotDoubleClicked(IconItem *item)
00500 {
00501     if (!item) return;
00502 
00503     if (d->albumSettings->getItemRightClickAction() == AlbumSettings::ShowPreview)
00504     {
00505         // icon effect takes too much time
00506         //KIconEffect::visualActivate(viewport(), contentsRectToViewport(item->rect()));
00507         signalPreviewItem(static_cast<AlbumIconItem *>(item));
00508     }
00509     else
00510     {
00511         // FIXME: this method has diseapear from kdelibs4
00512         //KIconEffect::visualActivate(viewport(), contentsRectToViewport(item->rect()));
00513         slotDisplayItem(static_cast<AlbumIconItem *>(item));
00514     }
00515 }
00516 
00517 void AlbumIconView::slotRightButtonClicked(const QPoint& pos)
00518 {
00519     if (!d->currentAlbum)
00520         return;
00521 
00522     if (d->currentAlbum->isRoot() ||
00523          (   d->currentAlbum->type() != Album::PHYSICAL
00524           && d->currentAlbum->type() != Album::TAG))
00525     {
00526         return;
00527     }
00528 
00529     QMenu popmenu(this);
00530     KAction *paste        = KStandardAction::paste(this, SLOT(slotPaste()), 0);
00531     const QMimeData *data = kapp->clipboard()->mimeData(QClipboard::Clipboard);
00532 
00533     if(!data || !KUrl::List::canDecode(data))
00534         paste->setEnabled(false);
00535 
00536     popmenu.addAction(paste);
00537     popmenu.exec(pos);
00538     delete paste;
00539 }
00540 
00541 void AlbumIconView::slotRightButtonClicked(IconItem *item, const QPoint& pos)
00542 {
00543     if (!item)
00544         return;
00545 
00546     AlbumIconItem* iconItem = static_cast<AlbumIconItem *>(item);
00547 
00548     //-- Open With Actions ------------------------------------
00549 
00550     KMimeType::Ptr mimePtr = KMimeType::findByUrl(iconItem->imageInfo().fileUrl(), 0, true, true);
00551 
00552     QMap<QAction*, KService::Ptr> serviceMap;
00553 
00554     const KService::List offers = KMimeTypeTrader::self()->query(mimePtr->name());
00555     KService::List::ConstIterator iter;
00556     KService::Ptr ptr;
00557 
00558     KMenu openWithMenu;
00559 
00560     for( iter = offers.begin(); iter != offers.end(); ++iter )
00561     {
00562         ptr = *iter;
00563         QAction *serviceAction = openWithMenu.addAction(SmallIcon(ptr->icon()), ptr->name());
00564         serviceMap[serviceAction] = ptr;
00565     }
00566 
00567     if (openWithMenu.isEmpty())
00568         openWithMenu.menuAction()->setEnabled(false);
00569 
00570     // --------------------------------------------------------
00571 
00572     // Obtain a list of all selected images.
00573     // This is needed both for the goto tags submenu here and also
00574     // for the "move to trash" and further actions below.
00575     QList<qlonglong> selectedImageIDs;
00576 
00577     for (IconItem *it = firstItem(); it; it=it->nextItem())
00578     {
00579         if (it->isSelected())
00580         {
00581             AlbumIconItem *selItem = static_cast<AlbumIconItem *>(it);
00582             selectedImageIDs.append(selItem->imageInfo().id());
00583         }
00584     }
00585 
00586     // --------------------------------------------------------
00587     // Provide Goto folder and/or date pop-up menu
00588     QMenu gotoMenu;
00589 
00590     QAction *gotoAlbum = gotoMenu.addAction(SmallIcon("folder-image"),        i18n("Album"));
00591     QAction *gotoDate  = gotoMenu.addAction(SmallIcon("view-calendar-month"), i18n("Date"));
00592 
00593     TagsPopupMenu* gotoTagsPopup = new TagsPopupMenu(selectedImageIDs, TagsPopupMenu::DISPLAY);
00594     QAction *gotoTag             = gotoMenu.addMenu(gotoTagsPopup);
00595     gotoTag->setIcon(SmallIcon("tag"));
00596     gotoTag->setText(i18n("Tag"));
00597 
00598     // Disable the goto Tag popup menu, if there are no tags at all.
00599     if (!DatabaseAccess().db()->hasTags(selectedImageIDs))
00600         gotoTag->setEnabled(false);
00601 
00602     connect(gotoTagsPopup, SIGNAL(signalTagActivated(int)),
00603             this, SLOT(slotGotoTag(int)));
00604 
00605     if (d->currentAlbum->type() == Album::PHYSICAL )
00606     {
00607         // If the currently selected album is the same as album to
00608         // which the image belongs, then disable the "Go To" Album.
00609         // (Note that in recursive album view these can be different).
00610         if (iconItem->imageInfo().albumId() == d->currentAlbum->id())
00611             gotoAlbum->setEnabled(false);
00612     }
00613     else if (d->currentAlbum->type() == Album::DATE )
00614     {
00615         gotoDate->setEnabled(false);
00616     }
00617 
00618     // --------------------------------------------------------
00619 
00620     DPopupMenu popmenu(this);
00621     QAction *viewAction       = popmenu.addAction(SmallIcon("viewimage"),     i18n("View..."));
00622     QAction *editAction       = popmenu.addAction(SmallIcon("editimage"),     i18n("Edit..."));
00623     QAction *lighttableAction = popmenu.addAction(SmallIcon("lighttableadd"), i18n("Add to Light Table"));
00624     QAction *gotoAction       = popmenu.addMenu(&gotoMenu);
00625     gotoAction->setIcon(SmallIcon("go-jump"));
00626     gotoAction->setText(i18n("Go To"));
00627 
00628     // If there is more than one image selected, disable the goto menu entry.
00629     if (selectedImageIDs.count() > 1)
00630     {
00631         gotoAction->setEnabled(false);
00632     }
00633 
00634     popmenu.addMenu(&openWithMenu);
00635     openWithMenu.menuAction()->setText(i18n("Open With"));
00636 
00637     // Merge in the KIPI plugins actions ----------------------------
00638 
00639     KIPI::PluginLoader* kipiPluginLoader      = KIPI::PluginLoader::instance();
00640     KIPI::PluginLoader::PluginList pluginList = kipiPluginLoader->pluginList();
00641 
00642     for (KIPI::PluginLoader::PluginList::const_iterator it = pluginList.begin();
00643          it != pluginList.end(); ++it)
00644     {
00645         KIPI::Plugin* plugin = (*it)->plugin();
00646 
00647         if (plugin && (*it)->name() == "JPEGLossless")
00648         {
00649             kDebug(50003) << "Found JPEGLossless plugin" << endl;
00650 
00651             QList<KAction*> actionList = plugin->actions();
00652 
00653             for (QList<KAction*>::const_iterator iter = actionList.begin();
00654                 iter != actionList.end(); ++iter)
00655             {
00656                 KAction* action = *iter;
00657 
00658                 if (action->objectName().toLatin1() == QString::fromLatin1("jpeglossless_rotate"))
00659                 {
00660                     popmenu.addAction(action);
00661                 }
00662             }
00663         }
00664     }
00665 
00666     // --------------------------------------------------------
00667 
00668     QAction *renameAction = popmenu.addAction(SmallIcon("edit-rename"), i18n("Rename..."));
00669     popmenu.addSeparator();
00670 
00671     // --------------------------------------------------------
00672 
00673     QAction *thumbnailAction = 0;
00674     if (d->currentAlbum)
00675     {
00676         if (d->currentAlbum->type() == Album::PHYSICAL )
00677         {
00678             thumbnailAction = popmenu.addAction(i18n("Set as Album Thumbnail"));
00679             popmenu.addSeparator();
00680         }
00681         else if (d->currentAlbum->type() == Album::TAG )
00682         {
00683             thumbnailAction = popmenu.addAction(i18n("Set as Tag Thumbnail"));
00684             popmenu.addSeparator();
00685         }
00686     }
00687 
00688     // --------------------------------------------------------
00689 
00690     KAction *copy         = KStandardAction::copy(this, SLOT(slotCopy()), 0);
00691     KAction *paste        = KStandardAction::paste(this, SLOT(slotPaste()), 0);
00692     const QMimeData *data = kapp->clipboard()->mimeData(QClipboard::Clipboard);
00693 
00694     if(!data || !KUrl::List::canDecode(data))
00695         paste->setEnabled(false);
00696 
00697     popmenu.addAction(copy);
00698     popmenu.addAction(paste);
00699     popmenu.addSeparator();
00700 
00701     // --------------------------------------------------------
00702 
00703     QAction *trashAction = popmenu.addAction(SmallIcon("user-trash"),
00704                            i18np("Move to Trash", "Move %1 Files to Trash", selectedImageIDs.count()));
00705 
00706     popmenu.addSeparator();
00707 
00708     // Bulk assignment/removal of tags --------------------------
00709 
00710     TagsPopupMenu* assignTagsPopup = new TagsPopupMenu(selectedImageIDs, TagsPopupMenu::ASSIGN);
00711     TagsPopupMenu* removeTagsPopup = new TagsPopupMenu(selectedImageIDs, TagsPopupMenu::REMOVE);
00712 
00713     connect(assignTagsPopup, SIGNAL(signalTagActivated(int)),
00714             this, SLOT(slotAssignTag(int)));
00715 
00716     connect(removeTagsPopup, SIGNAL(signalTagActivated(int)),
00717             this, SLOT(slotRemoveTag(int)));
00718 
00719     popmenu.addMenu(assignTagsPopup);
00720     assignTagsPopup->menuAction()->setText(i18n("Assign Tag"));
00721 
00722     popmenu.addMenu(removeTagsPopup);
00723     removeTagsPopup->menuAction()->setText(i18n("Remove Tag"));
00724 
00725     // Performance: Only check for tags if there are <250 images selected
00726     // Also disable the remove Tag popup menu, if there are no tags at all.
00727     if (selectedImageIDs.count() > 250 ||
00728         !DatabaseAccess().db()->hasTags(selectedImageIDs))
00729         removeTagsPopup->menuAction()->setEnabled(false);
00730 
00731     popmenu.addSeparator();
00732 
00733     // Assign Star Rating -------------------------------------------
00734 
00735     RatingPopupMenu ratingMenu;
00736 
00737     connect(&ratingMenu, SIGNAL(signalRatingChanged(int)),
00738             this, SLOT(slotAssignRating(int)));
00739 
00740     popmenu.addMenu(&ratingMenu);
00741     ratingMenu.menuAction()->setText(i18n("Assign Rating"));
00742 
00743     // --------------------------------------------------------
00744 
00745     QAction *choice = popmenu.exec(pos);
00746 
00747     if (choice)
00748     {
00749         if (choice == editAction)
00750         {
00751             slotDisplayItem(iconItem);
00752         }
00753         else if (choice == renameAction)
00754         {
00755             slotRename(iconItem);
00756         }
00757         else if (choice == trashAction)
00758         {
00759             slotDeleteSelectedItems();
00760         }
00761         else if (choice == thumbnailAction)
00762         {
00763             slotSetAlbumThumbnail(iconItem);
00764         }
00765         else if (choice == viewAction)
00766         {
00767             signalPreviewItem(iconItem);
00768         }
00769         else if (choice == lighttableAction)
00770         {
00771             //  add images to existing images in the light table
00772             insertSelectionToLightTable(true);
00773         }
00774         else if (choice == gotoAlbum)
00775         {
00776             // send a signal to the parent widget (digikamview.cpp)
00777             emit signalGotoAlbumAndItem(iconItem);
00778         }
00779         else if (choice == gotoDate)
00780         {
00781             // send a signal to the parent widget (digikamview.cpp)
00782             emit signalGotoDateAndItem(iconItem);
00783         }
00784         else
00785         {
00786             if (serviceMap.contains(choice))
00787             {
00788                 KService::Ptr imageServicePtr = serviceMap[choice];
00789                 KUrl::List urlList;
00790                 for (IconItem *it = firstItem(); it; it=it->nextItem())
00791                 {
00792                     if (it->isSelected())
00793                     {
00794                         AlbumIconItem *selItem = static_cast<AlbumIconItem *>(it);
00795                         urlList.append(selItem->imageInfo().fileUrl());
00796                     }
00797                 }
00798                 if (urlList.count())
00799                     KRun::run(*imageServicePtr, urlList, this);
00800             }
00801         }
00802     }
00803 
00804     //---------------------------------------------------------------
00805 
00806     popmenu.deleteLater();
00807 }
00808 
00809 void AlbumIconView::slotCopy()
00810 {
00811     if (!d->currentAlbum)
00812         return;
00813 
00814     KUrl::List urls;
00815     KUrl::List kioURLs;
00816     QList<int> albumIDs;
00817     QList<int> imageIDs;
00818 
00819     for (IconItem *it = firstItem(); it; it=it->nextItem())
00820     {
00821         if (it->isSelected())
00822         {
00823             AlbumIconItem *albumItem = static_cast<AlbumIconItem *>(it);
00824             ImageInfo info = albumItem->imageInfo();
00825             urls.append(info.fileUrl());
00826             kioURLs.append(info.databaseUrl());
00827             imageIDs.append(info.id());
00828         }
00829     }
00830     albumIDs.append(d->currentAlbum->id());
00831 
00832     if (urls.isEmpty())
00833         return;
00834 
00835     kapp->clipboard()->setMimeData(new DItemDrag(urls, kioURLs, albumIDs, imageIDs));
00836 }
00837 
00838 void AlbumIconView::slotPaste()
00839 {
00840     const QMimeData *data = kapp->clipboard()->mimeData(QClipboard::Clipboard);
00841     if(!data)
00842         return;
00843 
00844     Album *album = 0;
00845 
00846     // Check if we working on grouped items view.
00847     if (groupCount() > 1)
00848     {
00849         AlbumIconGroupItem *grp = dynamic_cast<AlbumIconGroupItem*>(findGroup(QCursor::pos()));
00850         if (grp)
00851         {
00852             if(d->currentAlbum->type() == Album::PHYSICAL)
00853                 album = dynamic_cast<Album*>(AlbumManager::instance()->findPAlbum(grp->albumID()));
00854             else if(d->currentAlbum->type() == Album::TAG)
00855                 album = dynamic_cast<Album*>(AlbumManager::instance()->findTAlbum(grp->albumID()));
00856         }
00857     }
00858     if (!album)
00859         album = d->currentAlbum;
00860 
00861     if (d->currentAlbum->type() == Album::PHYSICAL)
00862     {
00863         if (DItemDrag::canDecode(data))
00864         {
00865             // Drag & drop inside of digiKam
00866 
00867             PAlbum* palbum = (PAlbum*)album;
00868 
00869             // B.K.O #119205: do not handle root album.
00870             if (palbum->isRoot())
00871                 return;
00872 
00873             KUrl::List urls;
00874             KUrl::List kioURLs;
00875             QList<int> albumIDs;
00876             QList<int> imageIDs;
00877 
00878             if (!DItemDrag::decode(data, urls, kioURLs, albumIDs, imageIDs))
00879                 return;
00880 
00881             if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
00882                 return;
00883 
00884             // Check if items dropped come from outside current album.
00885             KUrl::List extUrls;
00886             QList<qlonglong> extImageIDs;
00887             for (QList<int>::iterator it = imageIDs.begin(); it != imageIDs.end(); ++it)
00888             {
00889                 ImageInfo info(*it);
00890                 if (info.albumId() != album->id())
00891                 {
00892                     extUrls << info.databaseUrl();
00893                     extImageIDs << *it;
00894                 }
00895             }
00896 
00897             if(extUrls.isEmpty())
00898                 return;
00899 
00900             KIO::Job* job = DIO::copy(kioURLs, extImageIDs, palbum);
00901             connect(job, SIGNAL(result(KJob*)),
00902                     this, SLOT(slotDIOResult(KJob*)));
00903         }
00904         else if (KUrl::List::canDecode(data))
00905         {
00906             PAlbum* palbum = (PAlbum*)album;
00907 
00908             // B.K.O #119205: do not handle root album.
00909             if (palbum->isRoot())
00910                 return;
00911 
00912             KUrl::List srcURLs;
00913             srcURLs.fromMimeData(data);
00914 
00915             KIO::Job* job = DIO::copy(srcURLs, palbum);
00916             connect(job, SIGNAL(result(KJob*)),
00917                     this, SLOT(slotDIOResult(KJob*)));
00918         }
00919     }
00920     else if(d->currentAlbum->type() == Album::TAG && DItemDrag::canDecode(data))
00921     {
00922         TAlbum* talbum = (TAlbum*)album;
00923 
00924         // B.K.O #119205: do not handle root album.
00925         if (talbum->isRoot())
00926             return;
00927 
00928         KUrl::List urls;
00929         KUrl::List kioURLs;
00930         QList<int> albumIDs;
00931         QList<int> imageIDs;
00932 
00933         if (!DItemDrag::decode(data, urls, kioURLs, albumIDs, imageIDs))
00934             return;
00935 
00936         if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
00937             return;
00938 
00939         ImageInfoList list;
00940         for (QList<int>::const_iterator it = imageIDs.begin();
00941              it != imageIDs.end(); ++it)
00942         {
00943             ImageInfo info(*it);
00944             list.append(info);
00945         }
00946 
00947         changeTagOnImageInfos(list, QList<int>() << talbum->id(), true, true);
00948     }
00949 }
00950 
00951 void AlbumIconView::slotSetAlbumThumbnail(AlbumIconItem *iconItem)
00952 {
00953     if(!d->currentAlbum)
00954         return;
00955 
00956     if(d->currentAlbum->type() == Album::PHYSICAL)
00957     {
00958         PAlbum *album = static_cast<PAlbum*>(d->currentAlbum);
00959 
00960         QString err;
00961         AlbumManager::instance()->updatePAlbumIcon( album,
00962                                                     iconItem->imageInfo().id(),
00963                                                     err );
00964     }
00965     else if (d->currentAlbum->type() == Album::TAG)
00966     {
00967         TAlbum *album = static_cast<TAlbum*>(d->currentAlbum);
00968 
00969         QString err;
00970         AlbumManager::instance()->updateTAlbumIcon( album,
00971                                                     QString(),
00972                                                     iconItem->imageInfo().id(),
00973                                                     err );
00974     }
00975 }
00976 
00977 void AlbumIconView::slotRename(AlbumIconItem* item)
00978 {
00979     if (!item)
00980         return;
00981 
00982     // Create a copy of the item. After entering the event loop
00983     // in the dialog, we cannot be sure about the item's status.
00984     ImageInfo renameInfo = item->imageInfo();
00985 
00986     QFileInfo fi(renameInfo.name());
00987     QString ext  = QString(".") + fi.suffix();
00988     QString name = fi.fileName();
00989     name.truncate(fi.fileName().length() - ext.length());
00990 
00991     bool ok;
00992 
00993     QString newName = KInputDialog::getText(i18n("Rename Item (%1)",fi.fileName()),
00994                                             i18n("Enter new name (without extension):"),
00995                                             name, &ok, this);
00996     if (!ok)
00997         return;
00998 
00999     KIO::CopyJob* job = DIO::rename(renameInfo, newName + ext);
01000 
01001     connect(job, SIGNAL(result(KJob*)),
01002             this, SLOT(slotDIOResult(KJob*)));
01003 
01004     connect(job, SIGNAL(copyingDone(KIO::Job *, const KUrl &, const KUrl &, bool, bool)),
01005             this, SLOT(slotRenamed(KIO::Job*, const KUrl &, const KUrl&)));
01006 
01007     //TODO: The explanation is outdated. Check if we can safely remove.
01008     // The AlbumManager KDirWatch will trigger a DIO::scan.
01009     // When this is completed, DIO will call AlbumLister::instance()->refresh().
01010     // Usually the AlbumLister will ignore changes to already listed items.
01011     // So the renamed item need explicitly be invalidated.
01012     d->imageLister->invalidateItem(renameInfo);
01013 }
01014 
01015 void AlbumIconView::slotRenamed(KIO::Job*, const KUrl &, const KUrl&newURL)
01016 {
01017     // reconstruct file path from digikamalbums:// URL
01018     KUrl fileURL;
01019     fileURL.setPath(newURL.user());
01020     fileURL.addPath(newURL.path());
01021 
01022     // refresh thumbnail
01023     ThumbnailLoadThread::deleteThumbnail(fileURL.path());
01024     // clean LoadingCache as well - be pragmatic, do it here.
01025     LoadingCacheInterface::cleanFromCache(fileURL.path());
01026 }
01027 
01028 void AlbumIconView::slotDeleteSelectedItems(bool deletePermanently)
01029 {
01030     KUrl::List  urlList;
01031     KUrl::List  kioUrlList;
01032 
01033     for (IconItem *it = firstItem(); it; it=it->nextItem())
01034     {
01035         if (it->isSelected())
01036         {
01037             AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
01038             ImageInfo info = iconItem->imageInfo();
01039             urlList.append(info.fileUrl());
01040             kioUrlList.append(info.databaseUrl());
01041         }
01042     }
01043 
01044     if (urlList.count() <= 0)
01045         return;
01046 
01047     DeleteDialog dialog(this);
01048 
01049     if (!dialog.confirmDeleteList(urlList,
01050                                   DeleteDialogMode::Files,
01051                                   deletePermanently ?
01052                                   DeleteDialogMode::NoChoiceDeletePermanently :
01053                                   DeleteDialogMode::NoChoiceTrash))
01054         return;
01055 
01056     bool useTrash = !dialog.shouldDelete();
01057 
01058     // trash does not like non-local URLs, put is not implemented
01059     KIO::Job* job = DIO::del(useTrash ? urlList : kioUrlList, useTrash);
01060 
01061     connect(job, SIGNAL(result(KJob*)),
01062             this, SLOT(slotDIOResult(KJob*)));
01063 }
01064 
01065 void AlbumIconView::slotDeleteSelectedItemsDirectly(bool useTrash)
01066 {
01067     // This method deletes the selected items directly, without confirmation.
01068     // It is not used in the default setup.
01069 
01070     KUrl::List kioUrlList;
01071     KUrl::List urlList;
01072 
01073     for (IconItem *it = firstItem(); it; it=it->nextItem())
01074     {
01075         if (it->isSelected())
01076         {
01077             AlbumIconItem *iconItem = static_cast<AlbumIconItem *>(it);
01078             ImageInfo info = iconItem->imageInfo();
01079             kioUrlList.append(info.databaseUrl());
01080             urlList.append(info.fileUrl());
01081         }
01082     }
01083 
01084     if (kioUrlList.count() <= 0)
01085         return;
01086 
01087     // trash does not like non-local URLs, put is not implemented
01088     KIO::Job* job = DIO::del(useTrash ? urlList : kioUrlList , useTrash);
01089 
01090     connect(job, SIGNAL(result(KJob*)),
01091             this, SLOT(slotDIOResult(KJob*)));
01092 }
01093 
01094 void AlbumIconView::slotFilesModified()
01095 {
01096     d->imageLister->refresh();
01097 }
01098 
01099 void AlbumIconView::slotFilesModified(const KUrl& url)
01100 {
01101     refreshItems(url);
01102 }
01103 
01104 void AlbumIconView::slotImageWindowURLChanged(const KUrl &url)
01105 {
01106     IconItem* item = findItem(url.url());
01107     if (item)
01108         setCurrentItem(item);
01109 }
01110 
01111 void AlbumIconView::slotDisplayItem(AlbumIconItem *item)
01112 {
01113     if (!item) return;
01114 
01115     AlbumSettings *settings = AlbumSettings::instance();
01116 
01117     if (!settings) return;
01118 
01119     QString currentFileExtension = item->imageInfo().name().section( '.', -1 );
01120     QString imagefilter = settings->getImageFileFilter().toLower() +
01121                           settings->getImageFileFilter().toUpper();
01122 
01123 #if KDCRAW_VERSION < 0x000400
01124     if (KDcrawIface::DcrawBinary::instance()->versionIsRight())
01125     {
01126         // add raw files only if dcraw is available
01127         imagefilter += settings->getRawFileFilter().toLower() +
01128                        settings->getRawFileFilter().toUpper();
01129     }
01130 #else
01131     // add raw files only if dcraw is available
01132     imagefilter += settings->getRawFileFilter().toLower() +
01133                    settings->getRawFileFilter().toUpper();
01134 #endif
01135 
01136     // If the current item is not an image file.
01137     if ( !imagefilter.contains(currentFileExtension) )
01138     {
01139         KMimeType::Ptr mimePtr = KMimeType::findByUrl(item->imageInfo().fileUrl(), 0, true, true);
01140         const KService::List offers = KServiceTypeTrader::self()->query(mimePtr->name(), "Type == 'Application'");
01141 
01142         if (offers.isEmpty())
01143             return;
01144 
01145         KService::Ptr ptr = offers.first();
01146         // Run the dedicated app to show the item.
01147         KRun::run(*ptr, item->imageInfo().fileUrl(), this);
01148         return;
01149     }
01150 
01151     // Run digiKam ImageEditor with all image from current Album.
01152 
01153     ImageInfoList list;
01154     ImageInfo     current;
01155 
01156     for (IconItem *it = firstItem() ; it ; it = it->nextItem())
01157     {
01158         AlbumIconItem *iconItem = static_cast<