diff --git a/NEWS b/NEWS index 380c665e50..f5ef51dc98 100644 --- a/NEWS +++ b/NEWS @@ -1,15 +1,16 @@ ***************************************************************************************************** digiKam 4.4.0 - Release date: 2014-10-12 NEW FEATURES: BUGFIXES FROM KDE BUGZILLA (alias B.K.O | http://bugs.kde.org): 001 ==> 147175 - CANVAS : fit only large images to window. 002 ==> 282900 - Control click on thumb to deselect resets focus to first photo. 003 ==> 303179 - Customize status bar. 004 ==> 229930 - Delete photos with the mouse action not an action icon. 005 ==> 322053 - Filtered Icon-View and change rating changes all items from non filtered Icon-View. 006 ==> 137320 - Thumbnails generation process should continue for images that are off-screen [patch]. 007 ==> 110658 - Thumbnails generation process should have low priority. -008 ==> +008 ==> 224806 - ICONVIEW : use keyboard modifier with right mouse click to run default "Open With..." action +009 ==> diff --git a/digikam/items/digikamimageview.cpp b/digikam/items/digikamimageview.cpp index 4d9b5d7531..e842470048 100644 --- a/digikam/items/digikamimageview.cpp +++ b/digikam/items/digikamimageview.cpp @@ -1,696 +1,703 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-04-24 * Description : Qt item view for images * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2009-2014 by Gilles Caulier * Copyright (C) 2011 by Andi Clemens * Copyright (C) 2013 by Michael G. Hansen * Copyright (C) 2014 by Mohamed Anwer * * 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. * * ============================================================ */ #include "digikamimageview_p.h" #include "digikamimageview.moc" // Qt includes #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "albummanager.h" #include "albumdb.h" #include "advancedrenamedialog.h" #include "advancedrenameprocessdialog.h" #include "applicationsettings.h" #include "assignnameoverlay.h" #include "contextmenuhelper.h" #include "databaseaccess.h" #include "ddragobjects.h" #include "digikamapp.h" #include "digikamimagedelegate.h" #include "digikamimagefacedelegate.h" #include "dio.h" #include "facerejectionoverlay.h" #include "groupindicatoroverlay.h" #include "imagealbumfiltermodel.h" #include "imagealbummodel.h" #include "imagedragdrop.h" #include "imageratingoverlay.h" #include "imagecoordinatesoverlay.h" #include "tagslineeditoverlay.h" #include "imageviewutilities.h" #include "imagewindow.h" #include "fileactionmngr.h" #include "fileactionprogress.h" #include "thumbnailloadthread.h" #include "tagregion.h" #include "addtagslineedit.h" namespace Digikam { DigikamImageView::DigikamImageView(QWidget* const parent) : ImageCategorizedView(parent), d(new Private(this)) { installDefaultModels(); d->editPipeline.plugDatabaseEditor(); d->editPipeline.plugTrainer(); d->editPipeline.construct(); connect(&d->editPipeline, SIGNAL(scheduled()), this, SLOT(slotInitProgressIndicator())); d->normalDelegate = new DigikamImageDelegate(this); d->faceDelegate = new DigikamImageFaceDelegate(this); setItemDelegate(d->normalDelegate); setSpacing(10); ApplicationSettings* const settings = ApplicationSettings::instance(); imageFilterModel()->setCategorizationMode(ImageSortSettings::CategoryByAlbum); imageAlbumModel()->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread()); setThumbnailSize((ThumbnailSize::Size)settings->getDefaultIconSize()); imageAlbumModel()->setPreloadThumbnails(true); imageModel()->setDragDropHandler(new ImageDragDropHandler(imageModel())); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(false); setToolTipEnabled(settings->showToolTipsIsValid()); imageFilterModel()->setSortRole((ImageSortSettings::SortRole)settings->getImageSortOrder()); imageFilterModel()->setSortOrder((ImageSortSettings::SortOrder)settings->getImageSorting()); imageFilterModel()->setCategorizationMode((ImageSortSettings::CategorizationMode)settings->getImageGroupMode()); imageFilterModel()->setCategorizationSortOrder((ImageSortSettings::SortOrder) settings->getImageGroupSortOrder()); // selection overlay addSelectionOverlay(d->normalDelegate); addSelectionOverlay(d->faceDelegate); // rotation overlays d->rotateLeftOverlay = ImageRotateOverlay::left(this); d->rotateRightOverlay = ImageRotateOverlay::right(this); d->updateOverlays(); // rating overlay ImageRatingOverlay* const ratingOverlay = new ImageRatingOverlay(this); addOverlay(ratingOverlay); // face overlays // NOTE: order to plug this overlay is important, else rejection can be suitable (see B.K.O #324759). addAssignNameOverlay(d->faceDelegate); addRejectionOverlay(d->faceDelegate); GroupIndicatorOverlay* const groupOverlay = new GroupIndicatorOverlay(this); addOverlay(groupOverlay); addOverlay(new ImageCoordinatesOverlay(this)); connect(ratingOverlay, SIGNAL(ratingEdited(QList,int)), this, SLOT(assignRating(QList,int))); connect(groupOverlay, SIGNAL(toggleGroupOpen(QModelIndex)), this, SLOT(groupIndicatorClicked(QModelIndex))); connect(groupOverlay, SIGNAL(showButtonContextMenu(QModelIndex,QContextMenuEvent*)), this, SLOT(showGroupContextMenu(QModelIndex,QContextMenuEvent*))); d->utilities = new ImageViewUtilities(this); connect(d->utilities, SIGNAL(editorCurrentUrlChanged(KUrl)), this, SLOT(setCurrentUrl(KUrl))); connect(imageModel()->dragDropHandler(), SIGNAL(assignTags(QList,QList)), FileActionMngr::instance(), SLOT(assignTags(QList,QList))); connect(imageModel()->dragDropHandler(), SIGNAL(addToGroup(ImageInfo,QList)), FileActionMngr::instance(), SLOT(addToGroup(ImageInfo,QList))); connect(settings, SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); slotSetupChanged(); } DigikamImageView::~DigikamImageView() { delete d; } ImageViewUtilities* DigikamImageView::utilities() const { return d->utilities; } void DigikamImageView::setThumbnailSize(const ThumbnailSize& size) { imageThumbnailModel()->setPreloadThumbnailSize(size); ImageCategorizedView::setThumbnailSize(size); } int DigikamImageView::fitToWidthIcons() { return delegate()->calculatethumbSizeToFit(viewport()->size().width()); } void DigikamImageView::slotSetupChanged() { setToolTipEnabled(ApplicationSettings::instance()->showToolTipsIsValid()); setFont(ApplicationSettings::instance()->getIconViewFont()); d->updateOverlays(); ImageCategorizedView::slotSetupChanged(); } void DigikamImageView::setFaceMode(bool on) { d->faceMode = on; if (on) { // See ImageLister, which creates a search the implements listing tag in the ioslave imageAlbumModel()->setSpecialTagListing("faces"); setItemDelegate(d->faceDelegate); // grouping is not very much compatible with faces imageFilterModel()->setAllGroupsOpen(true); } else { imageAlbumModel()->setSpecialTagListing(QString()); setItemDelegate(d->normalDelegate); imageFilterModel()->setAllGroupsOpen(false); } } void DigikamImageView::addRejectionOverlay(ImageDelegate* delegate) { FaceRejectionOverlay* const rejectionOverlay = new FaceRejectionOverlay(this); connect(rejectionOverlay, SIGNAL(rejectFaces(QList)), this, SLOT(removeFaces(QList))); addOverlay(rejectionOverlay, delegate); } /* void DigikamImageView::addTagEditOverlay(ImageDelegate* delegate) { TagsLineEditOverlay* tagOverlay = new TagsLineEditOverlay(this); connect(tagOverlay, SIGNAL(tagEdited(QModelIndex,QString)), this, SLOT(assignTag(QModelIndex,QString))); addOverlay(tagOverlay, delegate); } */ void DigikamImageView::addAssignNameOverlay(ImageDelegate* delegate) { AssignNameOverlay* const nameOverlay = new AssignNameOverlay(this); addOverlay(nameOverlay, delegate); connect(nameOverlay, SIGNAL(confirmFaces(QList,int)), this, SLOT(confirmFaces(QList,int))); connect(nameOverlay, SIGNAL(removeFaces(QList)), this, SLOT(removeFaces(QList))); } void DigikamImageView::confirmFaces(const QList& indexes, int tagId) { QList infos; QList faces; QList sourceIndexes; // fast-remove in the "unknown person" view bool needFastRemove = false; if(imageAlbumModel()->currentAlbums().size() == 1) { needFastRemove = d->faceMode && (tagId != imageAlbumModel()->currentAlbums().first()->id()); } foreach(const QModelIndex& index, indexes) { infos << ImageModel::retrieveImageInfo(index); faces << d->faceDelegate->face(index); if (needFastRemove) { sourceIndexes << imageSortFilterModel()->mapToSourceImageModel(index); } } imageAlbumModel()->removeIndexes(sourceIndexes); for (int i=0; ieditPipeline.confirm(infos[i], faces[i], tagId); } } void DigikamImageView::removeFaces(const QList& indexes) { QList infos; QList faces; QList sourceIndexes; foreach(const QModelIndex& index, indexes) { infos << ImageModel::retrieveImageInfo(index); faces << d->faceDelegate->face(index); sourceIndexes << imageSortFilterModel()->mapToSourceImageModel(index); } imageAlbumModel()->removeIndexes(sourceIndexes); for (int i=0; ieditPipeline.remove(infos[i], faces[i]); } } -void DigikamImageView::activated(const ImageInfo& info) +void DigikamImageView::activated(const ImageInfo& info, Qt::KeyboardModifiers modifiers) { if (info.isNull()) { return; } - if (ApplicationSettings::instance()->getItemLeftClickAction() == ApplicationSettings::ShowPreview) + if (modifiers != Qt::MetaModifier) { - emit previewRequested(info); + if (ApplicationSettings::instance()->getItemLeftClickAction() == ApplicationSettings::ShowPreview) + { + emit previewRequested(info); + } + else + { + openFile(info); + } } else { - openFile(info); + d->utilities->openInfosWithDefaultApplication(QList() << info); } } void DigikamImageView::showContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info) { QList selectedInfos = selectedImageInfosCurrentFirst(); QList selectedImageIDs; foreach(const ImageInfo& info, selectedInfos) { selectedImageIDs << info.id(); } // Temporary actions -------------------------------------- QAction* const viewAction = new QAction(SmallIcon("viewimage"), i18nc("View the selected image", "Preview"), this); viewAction->setEnabled(selectedImageIDs.count() == 1); // -------------------------------------------------------- KMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); cmhelper.setImageFilterModel(imageFilterModel()); cmhelper.addAction("full_screen"); cmhelper.addAction("options_show_menubar"); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction("move_selection_to_album"); cmhelper.addAction(viewAction); cmhelper.addAction("image_edit"); cmhelper.addServicesMenu(selectedUrls()); cmhelper.addGotoMenu(selectedImageIDs); cmhelper.addAction("image_rotate"); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction("image_find_similar"); cmhelper.addStandardActionLightTable(); cmhelper.addQueueManagerMenu(); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction("image_rename"); cmhelper.addAction("cut_album_selection"); cmhelper.addAction("copy_album_selection"); cmhelper.addAction("paste_album_selection"); cmhelper.addStandardActionItemDelete(this, SLOT(deleteSelected()), selectedImageIDs.count()); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addStandardActionThumbnail(selectedImageIDs, currentAlbum()); // -------------------------------------------------------- cmhelper.addAssignTagsMenu(selectedImageIDs); cmhelper.addRemoveTagsMenu(selectedImageIDs); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addLabelsAction(); if (!d->faceMode) { cmhelper.addGroupMenu(selectedImageIDs); } // special action handling -------------------------------- connect(&cmhelper, SIGNAL(signalAssignTag(int)), this, SLOT(assignTagToSelected(int))); connect(&cmhelper, SIGNAL(signalPopupTagsView()), this, SIGNAL(signalPopupTagsView())); connect(&cmhelper, SIGNAL(signalRemoveTag(int)), this, SLOT(removeTagFromSelected(int))); connect(&cmhelper, SIGNAL(signalGotoTag(int)), this, SIGNAL(gotoTagAndImageRequested(int))); connect(&cmhelper, SIGNAL(signalGotoAlbum(ImageInfo)), this, SIGNAL(gotoAlbumAndImageRequested(ImageInfo))); connect(&cmhelper, SIGNAL(signalGotoDate(ImageInfo)), this, SIGNAL(gotoDateAndImageRequested(ImageInfo))); connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)), this, SLOT(assignPickLabelToSelected(int))); connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)), this, SLOT(assignColorLabelToSelected(int))); connect(&cmhelper, SIGNAL(signalAssignRating(int)), this, SLOT(assignRatingToSelected(int))); connect(&cmhelper, SIGNAL(signalSetThumbnail(ImageInfo)), this, SLOT(setAsAlbumThumbnail(ImageInfo))); connect(&cmhelper, SIGNAL(signalAddToExistingQueue(int)), this, SLOT(insertSelectedToExistingQueue(int))); connect(&cmhelper, SIGNAL(signalCreateGroup()), this, SLOT(createGroupFromSelection())); connect(&cmhelper, SIGNAL(signalCreateGroupByTime()), this, SLOT(createGroupByTimeFromSelection())); connect(&cmhelper, SIGNAL(signalUngroup()), this, SLOT(ungroupSelected())); connect(&cmhelper, SIGNAL(signalRemoveFromGroup()), this, SLOT(removeSelectedFromGroup())); // -------------------------------------------------------- QAction* const choice = cmhelper.exec(event->globalPos()); if (choice && (choice == viewAction)) { emit previewRequested(info); } } void DigikamImageView::showGroupContextMenu(const QModelIndex& index, QContextMenuEvent* event) { Q_UNUSED(index); QList selectedInfos = selectedImageInfosCurrentFirst(); QList selectedImageIDs; foreach(const ImageInfo& info, selectedInfos) { selectedImageIDs << info.id(); } KMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); cmhelper.setImageFilterModel(imageFilterModel()); cmhelper.addGroupActions(selectedImageIDs); // special action handling -------------------------------- connect(&cmhelper, SIGNAL(signalCreateGroup()), this, SLOT(createGroupFromSelection())); connect(&cmhelper, SIGNAL(signalCreateGroupByTime()), this, SLOT(createGroupByTimeFromSelection())); connect(&cmhelper, SIGNAL(signalUngroup()), this, SLOT(ungroupSelected())); connect(&cmhelper, SIGNAL(signalRemoveFromGroup()), this, SLOT(removeSelectedFromGroup())); cmhelper.exec(event->globalPos()); } void DigikamImageView::showContextMenu(QContextMenuEvent* event) { Album* const album = currentAlbum(); if (!album || album->isRoot() || (album->type() != Album::PHYSICAL && album->type() != Album::TAG) ) { return; } KMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); cmhelper.setImageFilterModel(imageFilterModel()); cmhelper.addAction("full_screen"); cmhelper.addAction("options_show_menubar"); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addStandardActionPaste(this, SLOT(paste())); // -------------------------------------------------------- cmhelper.exec(event->globalPos()); } void DigikamImageView::openFile(const ImageInfo& info) { d->utilities->openInfos(info, imageInfos(), currentAlbum()); } void DigikamImageView::insertSelectedToCurrentQueue() { ImageInfoList imageInfoList = selectedImageInfos(); if (!imageInfoList.isEmpty()) { d->utilities->insertToQueueManager(imageInfoList, imageInfoList.first(), false); } } void DigikamImageView::insertSelectedToNewQueue() { ImageInfoList imageInfoList = selectedImageInfos(); if (!imageInfoList.isEmpty()) { d->utilities->insertToQueueManager(imageInfoList, imageInfoList.first(), true); } } void DigikamImageView::insertSelectedToExistingQueue(int queueid) { ImageInfoList imageInfoList = selectedImageInfos(); if (!imageInfoList.isEmpty()) { d->utilities->insertSilentToQueueManager(imageInfoList, imageInfoList.first(), queueid); } } void DigikamImageView::deleteSelected(const ImageViewUtilities::DeleteMode deleteMode) { ImageInfoList imageInfoList = selectedImageInfos(); if (d->utilities->deleteImages(imageInfoList, deleteMode)) { awayFromSelection(); } } void DigikamImageView::deleteSelectedDirectly(const ImageViewUtilities::DeleteMode deleteMode) { ImageInfoList imageInfoList = selectedImageInfos(); d->utilities->deleteImagesDirectly(imageInfoList, deleteMode); awayFromSelection(); } void DigikamImageView::assignTagToSelected(int tagID) { FileActionMngr::instance()->assignTags(selectedImageInfos(), QList() << tagID); } void DigikamImageView::removeTagFromSelected(int tagID) { FileActionMngr::instance()->removeTags(selectedImageInfos(), QList() << tagID); } void DigikamImageView::assignPickLabelToSelected(int pickId) { FileActionMngr::instance()->assignPickLabel(selectedImageInfos(), pickId); } void DigikamImageView::assignPickLabel(const QModelIndex& index, int pickId) { FileActionMngr::instance()->assignPickLabel(QList() << imageFilterModel()->imageInfo(index), pickId); } void DigikamImageView::assignColorLabelToSelected(int colorId) { FileActionMngr::instance()->assignColorLabel(selectedImageInfos(), colorId); } void DigikamImageView::assignColorLabel(const QModelIndex& index, int colorId) { FileActionMngr::instance()->assignColorLabel(QList() << imageFilterModel()->imageInfo(index), colorId); } void DigikamImageView::assignRatingToSelected(int rating) { FileActionMngr::instance()->assignRating(selectedImageInfos(), rating); } void DigikamImageView::assignRating(const QList& indexes, int rating) { FileActionMngr::instance()->assignRating(imageFilterModel()->imageInfos(indexes), rating); } void DigikamImageView::setAsAlbumThumbnail(const ImageInfo& setAsThumbnail) { d->utilities->setAsAlbumThumbnail(currentAlbum(), setAsThumbnail); } void DigikamImageView::createNewAlbumForSelected() { d->utilities->createNewAlbumForInfos(selectedImageInfos(), currentAlbum()); } void DigikamImageView::groupIndicatorClicked(const QModelIndex& index) { ImageInfo info = imageFilterModel()->imageInfo(index); if (info.isNull()) { return; } setCurrentIndex(index); imageFilterModel()->toggleGroupOpen(info.id()); imageAlbumModel()->ensureHasGroupedImages(info); } void DigikamImageView::createGroupFromSelection() { QList selectedInfos = selectedImageInfosCurrentFirst(); ImageInfo groupLeader = selectedInfos.takeFirst(); FileActionMngr::instance()->addToGroup(groupLeader, selectedInfos); } void DigikamImageView::createGroupByTimeFromSelection() { const QList selectedInfos = selectedImageInfos(); d->utilities->createGroupByTimeFromInfoList(selectedInfos); } void DigikamImageView::ungroupSelected() { FileActionMngr::instance()->ungroup(selectedImageInfos()); } void DigikamImageView::removeSelectedFromGroup() { FileActionMngr::instance()->removeFromGroup(selectedImageInfos()); } void DigikamImageView::rename() { KUrl::List urls = selectedUrls(); NewNamesList newNamesList; QPointer dlg = new AdvancedRenameDialog(this); dlg->slotAddImages(urls); if (dlg->exec() == KDialog::Accepted) { newNamesList = dlg->newNames(); } delete dlg; if (!newNamesList.isEmpty()) { QPointer dlg = new AdvancedRenameProcessDialog(newNamesList); dlg->exec(); delete dlg; } } void DigikamImageView::slotRotateLeft(const QList& indexes) { FileActionMngr::instance()->transform(QList() << imageFilterModel()->imageInfos(indexes), KExiv2Iface::RotationMatrix::Rotate270); } void DigikamImageView::slotRotateRight(const QList& indexes) { FileActionMngr::instance()->transform(QList() << imageFilterModel()->imageInfos(indexes), KExiv2Iface::RotationMatrix::Rotate90); } void DigikamImageView::slotInitProgressIndicator() { if (!ProgressManager::instance()->findItembyId("FaceActionProgress")) { FileActionProgress* const item = new FileActionProgress("FaceActionProgress"); connect(&d->editPipeline, SIGNAL(started(QString)), item, SLOT(slotProgressStatus(QString))); connect(&d->editPipeline, SIGNAL(progressValueChanged(float)), item, SLOT(slotProgressValue(float))); connect(&d->editPipeline, SIGNAL(finished()), item, SLOT(slotCompleted())); } } } // namespace Digikam diff --git a/digikam/items/digikamimageview.h b/digikam/items/digikamimageview.h index 99f6a46db9..8f134801b5 100644 --- a/digikam/items/digikamimageview.h +++ b/digikam/items/digikamimageview.h @@ -1,123 +1,123 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-04-24 * Description : Qt item view for images * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2009-2014 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. * * ============================================================ */ #ifndef DIGIKAMIMAGEVIEW_H #define DIGIKAMIMAGEVIEW_H // Local includes #include "imagecategorizedview.h" #include "imageviewutilities.h" namespace Digikam { class ImageViewUtilities; class DigikamImageView : public ImageCategorizedView { Q_OBJECT public: explicit DigikamImageView(QWidget* const parent = 0); ~DigikamImageView(); ImageViewUtilities* utilities() const; int fitToWidthIcons(); virtual void setThumbnailSize(const ThumbnailSize& size); public Q_SLOTS: void openFile(const ImageInfo& info); void insertSelectedToCurrentQueue(); void insertSelectedToNewQueue(); void insertSelectedToExistingQueue(int queueid); void deleteSelected(const ImageViewUtilities::DeleteMode deleteMode = ImageViewUtilities::DeleteUseTrash); void deleteSelectedDirectly(const ImageViewUtilities::DeleteMode deleteMode = ImageViewUtilities::DeleteUseTrash); void assignTagToSelected(int tagID); void removeTagFromSelected(int tagID); void assignPickLabelToSelected(int pickId); void assignColorLabelToSelected(int colorId); void assignRatingToSelected(int rating); void setAsAlbumThumbnail(const ImageInfo& setAsThumbnail); void createNewAlbumForSelected(); void rename(); void assignPickLabel(const QModelIndex& index, int pickId); void assignColorLabel(const QModelIndex& index, int colorId); void assignRating(const QList& index, int rating); void createGroupFromSelection(); void createGroupByTimeFromSelection(); void ungroupSelected(); void removeSelectedFromGroup(); void setFaceMode(bool on); void confirmFaces(const QList& indexes, int tagId); void removeFaces(const QList& indexes); Q_SIGNALS: void previewRequested(const ImageInfo& info); void gotoAlbumAndImageRequested(const ImageInfo& info); void gotoTagAndImageRequested(int tagId); void gotoDateAndImageRequested(const ImageInfo& info); void signalPopupTagsView(); protected Q_SLOTS: void groupIndicatorClicked(const QModelIndex& index); void showGroupContextMenu(const QModelIndex& index, QContextMenuEvent* event); protected: void addRejectionOverlay(ImageDelegate* delegate = 0); void addAssignNameOverlay(ImageDelegate* delegate = 0); - virtual void activated(const ImageInfo& info); + virtual void activated(const ImageInfo& info, Qt::KeyboardModifiers modifiers); virtual void showContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info); virtual void showContextMenu(QContextMenuEvent* event); virtual void slotSetupChanged(); private Q_SLOTS: void slotRotateLeft(const QList&); void slotRotateRight(const QList&); void slotInitProgressIndicator(); private: class Private; Private* const d; }; } // namespace Digikam #endif /* DIGIKAMIMAGEVIEW_H */ diff --git a/digikam/items/imagecategorizedview.cpp b/digikam/items/imagecategorizedview.cpp index a887ab93e5..58ac80ca9b 100644 --- a/digikam/items/imagecategorizedview.cpp +++ b/digikam/items/imagecategorizedview.cpp @@ -1,713 +1,713 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-04-22 * Description : Qt item view for images * * Copyright (C) 2009-2012 by Marcel Wiesweg * * 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. * * ============================================================ */ #include "imagecategorizedview.moc" // Qt includes #include #include #include #include // KDE includes #include #include // Local includes #include "album.h" #include "albummanager.h" #include "applicationsettings.h" #include "databasefields.h" #include "imagealbummodel.h" #include "imagealbumfiltermodel.h" #include "imagecategorydrawer.h" #include "imagedelegate.h" #include "imagedelegateoverlay.h" #include "imagethumbnailmodel.h" #include "imageselectionoverlay.h" #include "itemviewtooltip.h" #include "loadingcacheinterface.h" #include "thumbnailloadthread.h" #include "tooltipfiller.h" #include "digikamimagefacedelegate.h" namespace Digikam { class ImageItemViewToolTip : public ItemViewToolTip { public: explicit ImageItemViewToolTip(ImageCategorizedView* const view) : ItemViewToolTip(view) { } ImageCategorizedView* view() const { return static_cast(ItemViewToolTip::view()); } protected: virtual QString tipContents() { ImageInfo info = ImageModel::retrieveImageInfo(currentIndex()); return ToolTipFiller::imageInfoTipContents(info); } }; // ------------------------------------------------------------------------------- class ImageCategorizedView::Private { public: Private() : model(0), filterModel(0), delegate(0), showToolTip(false), scrollToItemId(0), delayedEnterTimer(0), currentMouseEvent(0) { } ImageModel* model; ImageSortFilterModel* filterModel; ImageDelegate* delegate; bool showToolTip; qlonglong scrollToItemId; QTimer* delayedEnterTimer; QMouseEvent* currentMouseEvent; }; // ------------------------------------------------------------------------------- ImageCategorizedView::ImageCategorizedView(QWidget* const parent) : DCategorizedView(parent), d(new Private) { setToolTip(new ImageItemViewToolTip(this)); LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); d->delayedEnterTimer = new QTimer(this); d->delayedEnterTimer->setInterval(10); d->delayedEnterTimer->setSingleShot(true); connect(d->delayedEnterTimer, SIGNAL(timeout()), this, SLOT(slotDelayedEnter())); } ImageCategorizedView::~ImageCategorizedView() { d->delegate->removeAllOverlays(); delete d; } void ImageCategorizedView::installDefaultModels() { ImageAlbumModel* model = new ImageAlbumModel(this); ImageAlbumFilterModel* filterModel = new ImageAlbumFilterModel(this); filterModel->setSourceImageModel(model); filterModel->setSortRole(ImageSortSettings::SortByFileName); filterModel->setCategorizationMode(ImageSortSettings::CategoryByAlbum); filterModel->sort(0); // an initial sorting is necessary // set flags that we want to get dataChanged() signals for model->setWatchFlags(filterModel->suggestedWatchFlags()); setModels(model, filterModel); } void ImageCategorizedView::setModels(ImageModel* model, ImageSortFilterModel* filterModel) { if (d->delegate) { d->delegate->setAllOverlaysActive(false); } if (d->filterModel) { disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); disconnect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged())); } if (d->model) { disconnect(d->model, SIGNAL(imageInfosAdded(QList)), this, SLOT(slotImageInfosAdded())); } d->model = model; d->filterModel = filterModel; setModel(d->filterModel); connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged()), Qt::QueuedConnection); connect(d->model, SIGNAL(imageInfosAdded(QList)), this, SLOT(slotImageInfosAdded())); emit modelChanged(); if (d->delegate) { d->delegate->setAllOverlaysActive(true); } } ImageModel* ImageCategorizedView::imageModel() const { return d->model; } ImageSortFilterModel* ImageCategorizedView::imageSortFilterModel() const { return d->filterModel; } ImageFilterModel* ImageCategorizedView::imageFilterModel() const { return d->filterModel->imageFilterModel(); } ImageThumbnailModel* ImageCategorizedView::imageThumbnailModel() const { return qobject_cast(d->model); } ImageAlbumModel* ImageCategorizedView::imageAlbumModel() const { return qobject_cast(d->model); } ImageAlbumFilterModel* ImageCategorizedView::imageAlbumFilterModel() const { return qobject_cast(d->filterModel->imageFilterModel()); } QSortFilterProxyModel* ImageCategorizedView::filterModel() const { return d->filterModel; } ImageDelegate* ImageCategorizedView::delegate() const { return d->delegate; } void ImageCategorizedView::setItemDelegate(ImageDelegate* delegate) { ThumbnailSize oldSize = thumbnailSize(); ImageDelegate* oldDelegate = d->delegate; if (oldDelegate) { hideIndexNotification(); d->delegate->setAllOverlaysActive(false); d->delegate->setViewOnAllOverlays(0); // Note: Be precise, no wildcard disconnect! disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); disconnect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } d->delegate = delegate; d->delegate->setThumbnailSize(oldSize); if (oldDelegate) { d->delegate->setSpacing(oldDelegate->spacing()); } DCategorizedView::setItemDelegate(d->delegate); setCategoryDrawer(d->delegate->categoryDrawer()); updateDelegateSizes(); d->delegate->setViewOnAllOverlays(this); d->delegate->setAllOverlaysActive(true); connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); connect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } Album* ImageCategorizedView::currentAlbum() const { ImageAlbumModel* albumModel = imageAlbumModel(); /** TODO: Change to QList return type **/ if (albumModel && !(albumModel->currentAlbums().isEmpty())) { return albumModel->currentAlbums().first(); } return 0; } ImageInfo ImageCategorizedView::currentInfo() const { return d->filterModel->imageInfo(currentIndex()); } KUrl ImageCategorizedView::currentUrl() const { return currentInfo().fileUrl(); } QList ImageCategorizedView::selectedImageInfos() const { return d->filterModel->imageInfos(selectedIndexes()); } QList ImageCategorizedView::selectedImageInfosCurrentFirst() const { QList indexes = selectedIndexes(); QModelIndex current = currentIndex(); QList infos; foreach(const QModelIndex& index, indexes) { ImageInfo info = d->filterModel->imageInfo(index); if (index == current) { infos.prepend(info); } else { infos.append(info); } } return infos; } QList ImageCategorizedView::imageInfos() const { return d->filterModel->imageInfosSorted(); } KUrl::List ImageCategorizedView::urls() const { QList infos = imageInfos(); KUrl::List urls; foreach(const ImageInfo& info, infos) { urls << info.fileUrl(); } return urls; } KUrl::List ImageCategorizedView::selectedUrls() const { QList infos = selectedImageInfos(); KUrl::List urls; foreach(const ImageInfo& info, infos) { urls << info.fileUrl(); } return urls; } void ImageCategorizedView::toIndex(const KUrl& url) { DCategorizedView::toIndex(d->filterModel->indexForPath(url.toLocalFile())); } ImageInfo ImageCategorizedView::nextInOrder(const ImageInfo& startingPoint, int nth) { QModelIndex index = d->filterModel->indexForImageInfo(startingPoint); if (!index.isValid()) { return ImageInfo(); } return d->filterModel->imageInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex())); } QModelIndex ImageCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const { QModelIndex hint = DCategorizedView::nextIndexHint(anchor, removed); ImageInfo info = d->filterModel->imageInfo(anchor); //kDebug() << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForImageInfo(info); // Fixes a special case of multiple (face) entries for the same image. // If one is removed, any entry of the same image shall be preferred. if (d->model->numberOfIndexesForImageInfo(info) > 1) { // The hint is for a different info, but we may have a hint for the same info if (info != d->filterModel->imageInfo(hint)) { int minDiff = d->filterModel->rowCount(); QList indexesForImageInfo = d->filterModel->mapListFromSource(d->model->indexesForImageInfo(info)); foreach(const QModelIndex& index, indexesForImageInfo) { if (index == anchor || !index.isValid() || removed.contains(index)) { continue; } int distance = qAbs(index.row() - anchor.row()); if (distance < minDiff) { minDiff = distance; hint = index; //kDebug() << "Chose index" << hint << "at distance" << minDiff << "to" << anchor; } } } } return hint; } void ImageCategorizedView::openAlbum(QList albums) { ImageAlbumModel* albumModel = imageAlbumModel(); if (albumModel) { albumModel->openAlbum(albums); } } ThumbnailSize ImageCategorizedView::thumbnailSize() const { /* ImageThumbnailModel *thumbModel = imageThumbnailModel(); if (thumbModel) return thumbModel->thumbnailSize(); */ if (d->delegate) { return ThumbnailSize(imageThumbnailModel()->thumbnailLoadThread()->pixmapToThumbnailSize(d->delegate->thumbnailSize().size())); } return ThumbnailSize(); } void ImageCategorizedView::setThumbnailSize(int size) { setThumbnailSize(ThumbnailSize(size)); } void ImageCategorizedView::setThumbnailSize(const ThumbnailSize& s) { // we abuse this pair of method calls to restore scroll position layoutAboutToBeChanged(); ThumbnailSize size(imageThumbnailModel()->thumbnailLoadThread()->thumbnailToPixmapSize(s.size())); d->delegate->setThumbnailSize(size); layoutWasChanged(); } void ImageCategorizedView::setCurrentWhenAvailable(qlonglong imageId) { d->scrollToItemId = imageId; } void ImageCategorizedView::setCurrentUrl(const KUrl& url) { if (url.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); return; } QString path = url.toLocalFile(); QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); } void ImageCategorizedView::setCurrentInfo(const ImageInfo& info) { QModelIndex index = d->filterModel->indexForImageInfo(info); clearSelection(); setCurrentIndex(index); } void ImageCategorizedView::setSelectedUrls(const KUrl::List& urlList) { QItemSelection mySelection; for (KUrl::List::const_iterator it = urlList.constBegin(); it!=urlList.constEnd(); ++it) { const QString path = it->path(); const QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { kWarning() << "no QModelIndex found for" << *it; } else { // TODO: is there a better way? mySelection.select(index, index); } } clearSelection(); selectionModel()->select(mySelection, QItemSelectionModel::Select); } void ImageCategorizedView::setSelectedImageInfos(const QList& infos) { QItemSelection mySelection; foreach(const ImageInfo& info, infos) { QModelIndex index = d->filterModel->indexForImageInfo(info); mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void ImageCategorizedView::hintAt(const ImageInfo& info) { if (info.isNull()) { return; } QModelIndex index = d->filterModel->indexForImageInfo(info); if (!index.isValid()) { return; } selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); scrollTo(index); } void ImageCategorizedView::addOverlay(ImageDelegateOverlay* overlay, ImageDelegate* delegate) { if (!delegate) { delegate = d->delegate; } delegate->installOverlay(overlay); if (delegate == d->delegate) { overlay->setView(this); overlay->setActive(true); } } void ImageCategorizedView::removeOverlay(ImageDelegateOverlay* overlay) { ImageDelegate* delegate = dynamic_cast(overlay->delegate()); if (delegate) { delegate->removeOverlay(overlay); } overlay->setView(0); } void ImageCategorizedView::updateGeometries() { DCategorizedView::updateGeometries(); d->delayedEnterTimer->start(); } void ImageCategorizedView::slotDelayedEnter() { // re-emit entered() for index under mouse (after layout). QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); if (mouseIndex.isValid()) { emit DigikamKCategorizedView::entered(mouseIndex); } } void ImageCategorizedView::addSelectionOverlay(ImageDelegate* delegate) { addOverlay(new ImageSelectionOverlay(this), delegate); } void ImageCategorizedView::scrollToStoredItem() { if (d->scrollToItemId) { if (d->model->hasImage(d->scrollToItemId)) { QModelIndex index = d->filterModel->indexForImageId(d->scrollToItemId); setCurrentIndex(index); scrollToRelaxed(index, QAbstractItemView::PositionAtCenter); d->scrollToItemId = 0; } } } void ImageCategorizedView::slotImageInfosAdded() { if (d->scrollToItemId) { scrollToStoredItem(); } } void ImageCategorizedView::slotFileChanged(const QString& filePath) { QModelIndex index = d->filterModel->indexForPath(filePath); if (index.isValid()) { update(index); } } -void ImageCategorizedView::indexActivated(const QModelIndex& index) +void ImageCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers) { ImageInfo info = d->filterModel->imageInfo(index); if (!info.isNull()) { - activated(info); + activated(info, modifiers); emit imageActivated(info); } } void ImageCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous) { DCategorizedView::currentChanged(index, previous); emit currentChanged(d->filterModel->imageInfo(index)); } void ImageCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { DCategorizedView::selectionChanged(selectedItems, deselectedItems); if (!selectedItems.isEmpty()) { emit selected(d->filterModel->imageInfos(selectedItems.indexes())); } if (!deselectedItems.isEmpty()) { emit deselected(d->filterModel->imageInfos(deselectedItems.indexes())); } } Album* ImageCategorizedView::albumAt(const QPoint& pos) const { if (imageFilterModel()->imageSortSettings().categorizationMode == ImageSortSettings::CategoryByAlbum) { QModelIndex categoryIndex = indexForCategoryAt(pos); if (categoryIndex.isValid()) { int albumId = categoryIndex.data(ImageFilterModel::CategoryAlbumIdRole).toInt(); return AlbumManager::instance()->findPAlbum(albumId); } } return currentAlbum(); } -void ImageCategorizedView::activated(const ImageInfo&) +void ImageCategorizedView::activated(const ImageInfo&, Qt::KeyboardModifiers) { // implemented in subclass } void ImageCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index) { ImageInfo info = d->filterModel->imageInfo(index); showContextMenuOnInfo(event, info); } void ImageCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const ImageInfo&) { // implemented in subclass } void ImageCategorizedView::paintEvent(QPaintEvent* e) { // We want the thumbnails to be loaded in order. ImageThumbnailModel* thumbModel = imageThumbnailModel(); if (thumbModel) { QModelIndexList indexesToThumbnail = imageFilterModel()->mapListToSource(categorizedIndexesIn(viewport()->rect())); d->delegate->prepareThumbnails(thumbModel, indexesToThumbnail); } DCategorizedView::paintEvent(e); } QItemSelectionModel* ImageCategorizedView::getSelectionModel() const { return selectionModel(); } AbstractItemDragDropHandler* ImageCategorizedView::dragDropHandler() const { return d->model->dragDropHandler(); } } // namespace Digikam diff --git a/digikam/items/imagecategorizedview.h b/digikam/items/imagecategorizedview.h index 4a4b61e8e5..1d6657fa0f 100644 --- a/digikam/items/imagecategorizedview.h +++ b/digikam/items/imagecategorizedview.h @@ -1,217 +1,217 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-04-22 * Description : Qt item view for images * * Copyright (C) 2009-2012 by Marcel Wiesweg * * 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. * * ============================================================ */ #ifndef IMAGECATEGORIZEDVIEW_H #define IMAGECATEGORIZEDVIEW_H // Local includes #include "imageinfo.h" #include "dcategorizedview.h" #include "thumbnailsize.h" namespace Digikam { class Album; class ImageAlbumModel; class ImageAlbumFilterModel; class ImageModel; class ImageFilterModel; class ImageSortFilterModel; class ImageDelegate; class ImageDelegateOverlay; class ImageThumbnailModel; class ImageCategorizedView : public DCategorizedView { Q_OBJECT public: explicit ImageCategorizedView(QWidget* const parent = 0); ~ImageCategorizedView(); void setModels(ImageModel* model, ImageSortFilterModel* filterModel); ImageModel* imageModel() const; ImageSortFilterModel* imageSortFilterModel() const; QItemSelectionModel* getSelectionModel() const; /// Returns any ImageFilterMode in chain. May not be sourceModel() ImageFilterModel* imageFilterModel() const; /// Returns 0 if the ImageModel is not an ImageThumbnailModel ImageThumbnailModel* imageThumbnailModel() const; /// Returns 0 if the ImageModel is not an ImageAlbumModel ImageAlbumModel* imageAlbumModel() const; ImageAlbumFilterModel* imageAlbumFilterModel() const; ImageDelegate* delegate() const; Album* currentAlbum() const; ImageInfo currentInfo() const; KUrl currentUrl() const; QList selectedImageInfos() const; QList selectedImageInfosCurrentFirst() const; KUrl::List selectedUrls() const; QList imageInfos() const; KUrl::List urls() const; /** Selects the index as current and scrolls to it. */ void toIndex(const KUrl& url); /** Returns the n-th info after the given one. * Specifically, return the previous info for nth = -1 * and the next info for n = 1. * Returns a null info if either startingPoint or the nth info are * not contained in the model. */ ImageInfo nextInOrder(const ImageInfo& startingPoint, int nth); ImageInfo previousInfo(const ImageInfo& info) { return nextInOrder(info, -1); } ImageInfo nextInfo(const ImageInfo& info) { return nextInOrder(info, 1); } ThumbnailSize thumbnailSize() const; virtual void setThumbnailSize(const ThumbnailSize& size); /** If the model is categorized by an album, returns the album of the category * that contains the position. * If this is not applicable, return the current album. May return 0. */ Album* albumAt(const QPoint& pos) const; /// Add and remove an overlay. It will as well be removed automatically when destroyed. /// Unless you pass a different delegate, the current delegate will be used. void addOverlay(ImageDelegateOverlay* overlay, ImageDelegate* delegate = 0); void removeOverlay(ImageDelegateOverlay* overlay); void addSelectionOverlay(ImageDelegate* delegate = 0); public Q_SLOTS: void openAlbum(QList album); void setThumbnailSize(int size); /** Scroll the view to the given item when it becomes available. */ void setCurrentWhenAvailable(qlonglong imageId); /** Set as current item the item identified by its file url. */ void setCurrentUrl(const KUrl& url); /** Set as current item the item identified by the imageinfo. */ void setCurrentInfo(const ImageInfo& info); /** Set selected items identified by their file urls. */ void setSelectedUrls(const KUrl::List& urlList); /** Set selected items. */ void setSelectedImageInfos(const QList& infos); /** Does something to gain attention for info, but not changing current selection. */ void hintAt(const ImageInfo& info); Q_SIGNALS: void currentChanged(const ImageInfo& info); /// Emitted when new items are selected. The parameter includes only the newly selected infos, /// there may be other already selected infos. void selected(const QList& newSelectedInfos); /// Emitted when items are deselected. There may be other selected infos left. /// This signal is not emitted when the model is reset; then only selectionCleared is emitted. void deselected(const QList& nowDeselectedInfos); /// Emitted when the given image is activated. Info is never null. void imageActivated(const ImageInfo& info); /// Emitted when a new model is set void modelChanged(); protected Q_SLOTS: void slotImageInfosAdded(); protected: /// install default ImageAlbumModel and filter model, ready for use void installDefaultModels(); // reimplemented from parent class QSortFilterProxyModel* filterModel() const; AbstractItemDragDropHandler* dragDropHandler() const; QModelIndex nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const; void setItemDelegate(ImageDelegate* delegate); - void indexActivated(const QModelIndex& index); + void indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers); void currentChanged(const QModelIndex& index, const QModelIndex& previous); void paintEvent(QPaintEvent* e); void selectionChanged(const QItemSelection&, const QItemSelection&); void updateGeometries(); /// Reimplement these in a subclass - virtual void activated(const ImageInfo& info); + virtual void activated(const ImageInfo& info, Qt::KeyboardModifiers modifiers); virtual void showContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info); virtual void showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index); private Q_SLOTS: void slotFileChanged(const QString& filePath); void slotDelayedEnter(); private: void scrollToStoredItem(); private: class Private; Private* const d; }; } // namespace Digikam #endif /* IMAGECATEGORIZEDVIEW_H */ diff --git a/libs/widgets/itemview/dcategorizedview.cpp b/libs/widgets/itemview/dcategorizedview.cpp index 7db482c1dc..1f1b40659a 100644 --- a/libs/widgets/itemview/dcategorizedview.cpp +++ b/libs/widgets/itemview/dcategorizedview.cpp @@ -1,1004 +1,1007 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-01-16 * Description : Qt item view for images * * Copyright (C) 2009-2012 by Marcel Wiesweg - * Copyright (C) 2011-2012 by Gilles Caulier + * Copyright (C) 2011-2014 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. * * ============================================================ */ #include "dcategorizedview.moc" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "thememanager.h" #include "ditemdelegate.h" #include "abstractitemdragdrophandler.h" #include "itemviewtooltip.h" namespace Digikam { // ------------------------------------------------------------------------------- class DCategorizedView::Private { public: explicit Private(DCategorizedView* const q) : delegate(0), toolTip(0), notificationToolTip(0), showToolTip(false), usePointingHand(true), scrollStepFactor(10), currentMouseEvent(0), ensureOneSelectedItem(false), ensureInitialSelectedItem(false), hintAtSelectionRow(-1), q(q) { } QModelIndex scrollPositionHint() const; public: DItemDelegate* delegate; ItemViewToolTip* toolTip; ItemViewToolTip* notificationToolTip; bool showToolTip; bool usePointingHand; int scrollStepFactor; QMouseEvent* currentMouseEvent; bool ensureOneSelectedItem; bool ensureInitialSelectedItem; QPersistentModelIndex hintAtSelectionIndex; int hintAtSelectionRow; QPersistentModelIndex hintAtScrollPosition; DCategorizedView* const q; }; QModelIndex DCategorizedView::Private::scrollPositionHint() const { if (q->verticalScrollBar()->value() == q->verticalScrollBar()->minimum()) { return QModelIndex(); } QModelIndex hint = q->currentIndex(); // If the user scrolled, do not take current item, but first visible if (!hint.isValid() || !q->viewport()->rect().intersects(q->visualRect(hint))) { QList visibleIndexes = q->categorizedIndexesIn(q->viewport()->rect()); + if (!visibleIndexes.isEmpty()) { hint = visibleIndexes.first(); } } return hint; } // ------------------------------------------------------------------------------- DCategorizedView::DCategorizedView(QWidget* const parent) : DigikamKCategorizedView(parent), d(new Private(this)) { setViewMode(QListView::IconMode); setLayoutDirection(Qt::LeftToRight); setFlow(QListView::LeftToRight); setResizeMode(QListView::Adjust); setMovement(QListView::Static); setWrapping(true); // important optimization for layouting setUniformItemSizes(true); // disable "feature" from DigikamKCategorizedView setDrawDraggedItems(false); setSelectionMode(QAbstractItemView::ExtendedSelection); setDragEnabled(true); setEditTriggers(QAbstractItemView::NoEditTriggers); viewport()->setAcceptDrops(true); setMouseTracking(true); connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex))); connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(slotClicked(QModelIndex))); connect(this, SIGNAL(entered(QModelIndex)), this, SLOT(slotEntered(QModelIndex))); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); } DCategorizedView::~DCategorizedView() { delete d; } void DCategorizedView::setToolTip(ItemViewToolTip* tip) { d->toolTip = tip; } void DCategorizedView::setItemDelegate(DItemDelegate* delegate) { if (d->delegate == delegate) { return; } if (d->delegate) { disconnect(d->delegate, SIGNAL(gridSizeChanged(QSize)), this, SLOT(slotGridSizeChanged(QSize))); } d->delegate = delegate; DigikamKCategorizedView::setItemDelegate(d->delegate); connect(d->delegate, SIGNAL(gridSizeChanged(QSize)), this, SLOT(slotGridSizeChanged(QSize))); } void DCategorizedView::setSpacing(int spacing) { d->delegate->setSpacing(spacing); } void DCategorizedView::setUsePointingHandCursor(bool useCursor) { d->usePointingHand = useCursor; } void DCategorizedView::setScrollStepGranularity(int factor) { d->scrollStepFactor = qMax(1, factor); } DItemDelegate* DCategorizedView::delegate() const { return d->delegate; } int DCategorizedView::numberOfSelectedIndexes() const { return selectedIndexes().size(); } void DCategorizedView::toFirstIndex() { QModelIndex index = moveCursor(MoveHome, Qt::NoModifier); clearSelection(); setCurrentIndex(index); scrollToTop(); } void DCategorizedView::toLastIndex() { QModelIndex index = moveCursor(MoveEnd, Qt::NoModifier); clearSelection(); setCurrentIndex(index); scrollToBottom(); } void DCategorizedView::toNextIndex() { toIndex(moveCursor(MoveNext, Qt::NoModifier)); } void DCategorizedView::toPreviousIndex() { toIndex(moveCursor(MovePrevious, Qt::NoModifier)); } void DCategorizedView::toIndex(const QModelIndex& index) { if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); scrollTo(index); } void DCategorizedView::awayFromSelection() { QItemSelection selection = selectionModel()->selection(); if (selection.isEmpty()) { return; } const QModelIndex first = model()->index(0, 0); const QModelIndex last = model()->index(model()->rowCount() - 1, 0); if (selection.contains(first) && selection.contains(last)) { QItemSelection remaining(first, last); remaining.merge(selection, QItemSelectionModel::Toggle); QList indexes = remaining.indexes(); if (indexes.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); } else { toIndex(remaining.indexes().first()); } } else if (selection.contains(last)) { setCurrentIndex(selection.indexes().first()); toPreviousIndex(); } else { setCurrentIndex(selection.indexes().last()); toNextIndex(); } } void DCategorizedView::scrollToRelaxed(const QModelIndex& index, QAbstractItemView::ScrollHint hint) { if (viewport()->rect().intersects(visualRect(index))) { return; } scrollTo(index, hint); } void DCategorizedView::invertSelection() { const QModelIndex topLeft = model()->index(0, 0); const QModelIndex bottomRight = model()->index(model()->rowCount() - 1, 0); const QItemSelection selection(topLeft, bottomRight); selectionModel()->select(selection, QItemSelectionModel::Toggle); } void DCategorizedView::setSelectedIndexes(const QList& indexes) { if (selectedIndexes() == indexes) { return; } QItemSelection mySelection; foreach(const QModelIndex& index, indexes) { mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void DCategorizedView::setToolTipEnabled(bool enable) { d->showToolTip = enable; } bool DCategorizedView::isToolTipEnabled() const { return d->showToolTip; } void DCategorizedView::slotThemeChanged() { viewport()->update(); } void DCategorizedView::slotSetupChanged() { viewport()->update(); } void DCategorizedView::slotGridSizeChanged(const QSize& gridSize) { setGridSize(gridSize); if (!gridSize.isNull()) { horizontalScrollBar()->setSingleStep(gridSize.width() / d->scrollStepFactor); verticalScrollBar()->setSingleStep(gridSize.height() / d->scrollStepFactor); } } void DCategorizedView::updateDelegateSizes() { QStyleOptionViewItem option = viewOptions(); /* int frameAroundContents = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; } const int contentWidth = viewport()->width() - 1 - frameAroundContents - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar()); const int contentHeight = viewport()->height() - 1 - frameAroundContents - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar()); option.rect = QRect(0, 0, contentWidth, contentHeight); */ - option.rect = QRect(QPoint(0,0), viewport()->size()); + option.rect = QRect(QPoint(0, 0), viewport()->size()); d->delegate->setDefaultViewOptions(option); } void DCategorizedView::slotActivated(const QModelIndex& index) { + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + if (d->currentMouseEvent) { - // ignore activation if Ctrl or Shift is pressed (for selection) - Qt::KeyboardModifiers modifiers = d->currentMouseEvent->modifiers(); + // Ignore activation if Ctrl or Shift is pressed (for selection) + modifiers = d->currentMouseEvent->modifiers(); const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; const bool controlKeyPressed = modifiers & Qt::ControlModifier; if (shiftKeyPressed || controlKeyPressed) { return; } const bool rightClick = d->currentMouseEvent->button() & Qt::RightButton; if (rightClick) { return; } // if the activation is caused by mouse click (not keyboard) // we need to check the hot area if (!d->delegate->acceptsActivation(d->currentMouseEvent->pos(), visualRect(index), index)) { return; } } - indexActivated(index); + d->currentMouseEvent = 0; + indexActivated(index, modifiers); } void DCategorizedView::slotClicked(const QModelIndex& index) { if (d->currentMouseEvent) { emit clicked(d->currentMouseEvent, index); } } void DCategorizedView::slotEntered(const QModelIndex& index) { if (d->currentMouseEvent) { emit entered(d->currentMouseEvent, index); } } void DCategorizedView::reset() { DigikamKCategorizedView::reset(); // FIXME emiting this causes a crash importstackedview, because the model is not yet set. atm there's a check agaisnt null models though.. emit selectionChanged(); emit selectionCleared(); d->ensureInitialSelectedItem = true; d->hintAtScrollPosition = QModelIndex(); d->hintAtSelectionIndex = QModelIndex(); d->hintAtSelectionRow = -1; verticalScrollBar()->setValue(verticalScrollBar()->minimum()); horizontalScrollBar()->setValue(horizontalScrollBar()->minimum()); } void DCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { DigikamKCategorizedView::selectionChanged(selectedItems, deselectedItems); emit selectionChanged(); if (!selectionModel()->hasSelection()) { emit selectionCleared(); } userInteraction(); } void DCategorizedView::rowsInserted(const QModelIndex& parent, int start, int end) { DigikamKCategorizedView::rowsInserted(parent, start, end); if (start == 0) { ensureSelectionAfterChanges(); } } void DCategorizedView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { DigikamKCategorizedView::rowsAboutToBeRemoved(parent, start, end); // Ensure one selected item int totalToRemove = end - start + 1; bool remainingRows = model()->rowCount(parent) > totalToRemove; if (!remainingRows) { return; } QItemSelection removed(model()->index(start, 0), model()->index(end, 0)); if (selectionModel()->hasSelection()) { // find out which selected indexes are left after rows are removed QItemSelection selected = selectionModel()->selection(); QModelIndex current = currentIndex(); QModelIndex indexToAnchor; if (selected.contains(current)) { indexToAnchor = current; } else if (!selected.isEmpty()) { indexToAnchor = selected.first().topLeft(); } selected.merge(removed, QItemSelectionModel::Deselect); if (selected.isEmpty()) { QModelIndex newCurrent = nextIndexHint(indexToAnchor, removed.first() /*a range*/); setCurrentIndex(newCurrent); } } QModelIndex hint = d->scrollPositionHint(); if (removed.contains(hint)) { d->hintAtScrollPosition = nextIndexHint(hint, removed.first() /*a range*/); } } void DCategorizedView::layoutAboutToBeChanged() { - if(selectionModel()) { + if(selectionModel()) + { d->ensureOneSelectedItem = selectionModel()->hasSelection(); - } else { + } + else + { kWarning() << "Called without selection model, check whether the models are ok.."; } + QModelIndex current = currentIndex(); // store some hints so that if all selected items were removed do not need to default to 0,0. if (d->ensureOneSelectedItem) { QItemSelection currentSelection = selectionModel()->selection(); QModelIndex indexToAnchor; if (currentSelection.contains(current)) { indexToAnchor = current; } else if (!currentSelection.isEmpty()) { indexToAnchor = currentSelection.first().topLeft(); } if (indexToAnchor.isValid()) { d->hintAtSelectionRow = indexToAnchor.row(); d->hintAtSelectionIndex = nextIndexHint(indexToAnchor, QItemSelectionRange(indexToAnchor)); } } // some precautions to keep current scroll position d->hintAtScrollPosition = d->scrollPositionHint(); } QModelIndex DCategorizedView::nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const { Q_UNUSED(indexToAnchor); if (removed.bottomRight().row() == model()->rowCount() - 1) { if (removed.topLeft().row() == 0) { return QModelIndex(); } + return model()->index(removed.topLeft().row() - 1, 0); // last remaining, no next one left } else { return model()->index(removed.bottomRight().row() + 1, 0); // next remaining } } void DCategorizedView::layoutWasChanged() { // connected queued to layoutChanged() ensureSelectionAfterChanges(); if (d->hintAtScrollPosition.isValid()) { scrollToRelaxed(d->hintAtScrollPosition); d->hintAtScrollPosition = QModelIndex(); } else { scrollToRelaxed(currentIndex()); } } void DCategorizedView::userInteraction() { // as soon as the user did anything affecting selection, we don't interfere anymore d->ensureInitialSelectedItem = false; d->hintAtSelectionIndex = QModelIndex(); } void DCategorizedView::ensureSelectionAfterChanges() { if (d->ensureInitialSelectedItem && model()->rowCount()) { // Ensure the item (0,0) is selected, if the model was reset previously // and the user did not change the selection since reset. // Caveat: Item at (0,0) may have changed. bool hadInitial = d->ensureInitialSelectedItem; d->ensureInitialSelectedItem = false; d->ensureOneSelectedItem = false; QModelIndex index = model()->index(0,0); if (index.isValid()) { selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear); setCurrentIndex(index); // we want ensureInitial set to false if and only if the selection // is done from any other place than the previous line (i.e., by user action) // Effect: we select whatever is the current index(0,0) if (hadInitial) { d->ensureInitialSelectedItem = true; } } } else if (d->ensureOneSelectedItem) { // ensure we have a selection if there was one before d->ensureOneSelectedItem = false; if (model()->rowCount() && selectionModel()->selection().isEmpty()) { QModelIndex index; if (d->hintAtSelectionIndex.isValid()) { index = d->hintAtSelectionIndex; } else if (d->hintAtSelectionRow != -1) { index = model()->index(qMin(model()->rowCount(), d->hintAtSelectionRow), 0); } else { index = currentIndex(); } if (!index.isValid()) { index = model()->index(0,0); } d->hintAtSelectionRow = -1; d->hintAtSelectionIndex = QModelIndex(); if (index.isValid()) { setCurrentIndex(index); selectionModel()->select(index, QItemSelectionModel::SelectCurrent); } } } } QModelIndex DCategorizedView::indexForCategoryAt(const QPoint& pos) const { return categoryAt(pos); } QModelIndex DCategorizedView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { QModelIndex current = currentIndex(); if (!current.isValid()) { return DigikamKCategorizedView::moveCursor(cursorAction, modifiers); } // We want a simple wrapping navigation. // Default behavior we do not want: right/left does never change row; Next/Previous is equivalent to Down/Up switch (cursorAction) { case MoveNext: case MoveRight: { QModelIndex next = model()->index(current.row() + 1, 0); if (next.isValid()) { return next; } else { return current; } break; } case MovePrevious: case MoveLeft: { QModelIndex previous = model()->index(current.row() - 1, 0); if (previous.isValid()) { return previous; } else { return current; } break; } default: break; } return DigikamKCategorizedView::moveCursor(cursorAction, modifiers); } void DCategorizedView::showContextMenuOnIndex(QContextMenuEvent*, const QModelIndex&) { // implemented in subclass } void DCategorizedView::showContextMenu(QContextMenuEvent*) { // implemented in subclass } -void DCategorizedView::indexActivated(const QModelIndex&) +void DCategorizedView::indexActivated(const QModelIndex&, Qt::KeyboardModifiers) { } bool DCategorizedView::showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* he) { QRect innerRect; QPoint pos; if (he) { pos = he->pos(); } else { pos = option.rect.center(); } if (d->delegate->acceptsToolTip(he->pos(), option.rect, index, &innerRect)) { if (!innerRect.isNull()) { option.rect = innerRect; } d->toolTip->show(option, index); return true; } return false; } void DCategorizedView::contextMenuEvent(QContextMenuEvent* event) { userInteraction(); QModelIndex index = indexAt(event->pos()); if (index.isValid()) { showContextMenuOnIndex(event, index); } else { showContextMenu(event); } } void DCategorizedView::mousePressEvent(QMouseEvent* event) { userInteraction(); const QModelIndex index = indexAt(event->pos()); // Clear selection on click on empty area. Standard behavior, but not done by QAbstractItemView for some reason. Qt::KeyboardModifiers modifiers = event->modifiers(); const Qt::MouseButton button = event->button(); const bool rightButtonPressed = button & Qt::RightButton; const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; const bool controlKeyPressed = modifiers & Qt::ControlModifier; if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) { clearSelection(); } // store event for entered(), clicked(), activated() signal handlers if (!rightButtonPressed) { d->currentMouseEvent = event; } else { d->currentMouseEvent = 0; } DigikamKCategorizedView::mousePressEvent(event); if (!index.isValid()) { emit viewportClicked(event); } - - d->currentMouseEvent = 0; } void DCategorizedView::mouseReleaseEvent(QMouseEvent* event) { userInteraction(); - d->currentMouseEvent = event; DigikamKCategorizedView::mouseReleaseEvent(event); - d->currentMouseEvent = 0; } void DCategorizedView::mouseMoveEvent(QMouseEvent* event) { QModelIndex index = indexAt(event->pos()); QRect indexVisualRect; if (index.isValid()) { indexVisualRect = visualRect(index); if (d->usePointingHand && KGlobalSettings::changeCursorOverIcon() && d->delegate->acceptsActivation(event->pos(), indexVisualRect, index)) { setCursor(Qt::PointingHandCursor); } else { unsetCursor(); } } else { unsetCursor(); } - d->currentMouseEvent = event; DigikamKCategorizedView::mouseMoveEvent(event); - d->currentMouseEvent = 0; d->delegate->mouseMoved(event, indexVisualRect, index); } void DCategorizedView::wheelEvent(QWheelEvent* event) { // DigikamKCategorizedView updates the single step at some occasions in a private methody horizontalScrollBar()->setSingleStep(d->delegate->gridSize().height() / d->scrollStepFactor); verticalScrollBar()->setSingleStep(d->delegate->gridSize().width() / d->scrollStepFactor); if (event->modifiers() & Qt::ControlModifier) { const int delta = event->delta(); if (delta > 0) { emit zoomInStep(); } else if (delta < 0) { emit zoomOutStep(); } event->accept(); return; } if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && event->orientation() == Qt::Vertical) { QWheelEvent n(event->pos(), event->globalPos(), event->delta(), event->buttons(), event->modifiers(), Qt::Horizontal); QApplication::sendEvent(horizontalScrollBar(), &n); event->setAccepted(n.isAccepted()); } else { DigikamKCategorizedView::wheelEvent(event); } } void DCategorizedView::keyPressEvent(QKeyEvent* event) { userInteraction(); if (event == QKeySequence::Copy) { copy(); event->accept(); return; } else if (event == QKeySequence::Paste) { paste(); event->accept(); return; } /* // from dolphincontroller.cpp const QItemSelectionModel* selModel = m_itemView->selectionModel(); const QModelIndex currentIndex = selModel->currentIndex(); const bool trigger = currentIndex.isValid() && ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) && (selModel->selectedIndexes().count() > 0); if (trigger) { const QModelIndexList indexList = selModel->selectedIndexes(); foreach(const QModelIndex& index, indexList) { emit itemTriggered(itemForIndex(index)); } } */ DigikamKCategorizedView::keyPressEvent(event); emit keyPressed(event); } void DCategorizedView::resizeEvent(QResizeEvent* e) { QModelIndex oldPosition = d->scrollPositionHint(); DigikamKCategorizedView::resizeEvent(e); updateDelegateSizes(); scrollToRelaxed(oldPosition, QAbstractItemView::PositionAtTop); } bool DCategorizedView::viewportEvent(QEvent* event) { switch (event->type()) { case QEvent::FontChange: { updateDelegateSizes(); break; } case QEvent::ToolTip: { if (!d->showToolTip) { return true; } QHelpEvent* he = static_cast(event); const QModelIndex index = indexAt(he->pos()); if (!index.isValid()) { break; } QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); showToolTip(index, option, he); return true; } default: break; } return DigikamKCategorizedView::viewportEvent(event); } void DCategorizedView::showIndexNotification(const QModelIndex& index, const QString& message) { hideIndexNotification(); if (!index.isValid()) { return; } if (!d->notificationToolTip) { d->notificationToolTip = new ItemViewToolTip(this); } d->notificationToolTip->setTipContents(message); QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); d->notificationToolTip->show(option, index); } void DCategorizedView::hideIndexNotification() { if (d->notificationToolTip) { d->notificationToolTip->hide(); } } /** * cut(), copy(), paste(), dragEnterEvent(), dragMoveEvent(), dropEvent(), startDrag() * are implemented by DragDropViewImplementation */ QModelIndex DCategorizedView::mapIndexForDragDrop(const QModelIndex& index) const { return filterModel()->mapToSource(index); } QPixmap DCategorizedView::pixmapForDrag(const QList& indexes) const { QStyleOptionViewItem option = viewOptions(); option.rect = viewport()->rect(); return d->delegate->pixmapForDrag(option, indexes); } } // namespace Digikam diff --git a/libs/widgets/itemview/dcategorizedview.h b/libs/widgets/itemview/dcategorizedview.h index 066194a11a..32771379d4 100644 --- a/libs/widgets/itemview/dcategorizedview.h +++ b/libs/widgets/itemview/dcategorizedview.h @@ -1,198 +1,198 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-01-16 * Description : Qt item view for images * * Copyright (C) 2009-2012 by Marcel Wiesweg - * Copyright (C) 2011-2012 by Gilles Caulier + * Copyright (C) 2011-2014 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. * * ============================================================ */ #ifndef DCATEGORIZEDVIEW_H #define DCATEGORIZEDVIEW_H // Local includes #include "digikam_export.h" #include "digikamkcategorizedview.h" #include "dragdropimplementations.h" class QSortFilterProxyModel; namespace Digikam { class DItemDelegate; class AbstractItemDragDropHandler; class ItemViewToolTip; class DIGIKAM_EXPORT DCategorizedView : public DigikamKCategorizedView, public DragDropViewImplementation { Q_OBJECT public: explicit DCategorizedView(QWidget* const parent = 0); ~DCategorizedView(); DItemDelegate* delegate() const; int numberOfSelectedIndexes() const; /** Selects the index as current and scrolls to it */ void toFirstIndex(); void toLastIndex(); void toNextIndex(); void toPreviousIndex(); void toIndex(const QModelIndex& index); void awayFromSelection(); /** Like scrollTo, but only scrolls if the index is not visible, regardless of hint. */ void scrollToRelaxed(const QModelIndex& index, ScrollHint hint = EnsureVisible); void invertSelection(); void setSelectedIndexes(const QList& indexes); void setToolTipEnabled(bool enabled); bool isToolTipEnabled() const; /** Sets the spacing. Does not use setSpacing()/spacing() from QListView */ void setSpacing(int spacing); /** Set if the PointingHand Cursor should be shown over the activation area */ void setUsePointingHandCursor(bool useCursor); /** Determine a step size for scrolling: The larger this number, * the smaller and more precise is the scrolling. Default is 10. */ void setScrollStepGranularity(int factor); virtual QSortFilterProxyModel* filterModel() const = 0; public Q_SLOTS: void showIndexNotification(const QModelIndex& index, const QString& message); void hideIndexNotification(); virtual void cut() { DragDropViewImplementation::cut(); } virtual void copy() { DragDropViewImplementation::copy(); } virtual void paste() { DragDropViewImplementation::paste(); } Q_SIGNALS: /// Emitted when any selection change occurs. Any of the signals below will be emitted before. void selectionChanged(); /// Emitted when the selection is completely cleared. void selectionCleared(); void zoomOutStep(); void zoomInStep(); /** For overlays: Like the respective parent class signals, but with additional info. * Do not change the mouse events. */ void clicked(const QMouseEvent* e, const QModelIndex& index); void entered(const QMouseEvent* e, const QModelIndex& index); /// While clicked() is emitted with a valid index, this corresponds to clicking on empty space void viewportClicked(const QMouseEvent* e); /** Remember you may want to check if the event is accepted or ignored. * This signal is emitted after being handled by this widget. * You can accept it if ignored. */ void keyPressed(QKeyEvent* e); protected Q_SLOTS: void slotActivated(const QModelIndex& index); void slotClicked(const QModelIndex& index); void slotEntered(const QModelIndex& index); void layoutAboutToBeChanged(); void layoutWasChanged(); virtual void slotThemeChanged(); virtual void slotSetupChanged(); protected: void encodeIsCutSelection(QMimeData* mime, bool isCutSelection); bool decodeIsCutSelection(const QMimeData* mimeData); void setToolTip(ItemViewToolTip* tip); void setItemDelegate(DItemDelegate* delegate); void updateDelegateSizes(); void userInteraction(); /** Returns an index that is representative for the category at position pos */ QModelIndex indexForCategoryAt(const QPoint& pos) const; // reimplemented from parent class void contextMenuEvent(QContextMenuEvent* event); void keyPressEvent(QKeyEvent* event); void mouseMoveEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void resizeEvent(QResizeEvent* e); void reset(); void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); void rowsInserted(const QModelIndex& parent, int start, int end); void selectionChanged(const QItemSelection&, const QItemSelection&); bool viewportEvent(QEvent* event); void wheelEvent(QWheelEvent* event); QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); /// Reimplement these in a subclass virtual void showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index); virtual void showContextMenu(QContextMenuEvent* event); - virtual void indexActivated(const QModelIndex& index); + virtual void indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers); /** Provides default behavior, can reimplement in a subclass. * Returns true if a tooltip was shown. * The help event is optional. */ virtual bool showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* e = 0); DECLARE_VIEW_DRAG_DROP_METHODS(DigikamKCategorizedView) /// Note: pure virtual dragDropHandler() still open from DragDropViewImplementation virtual QModelIndex mapIndexForDragDrop(const QModelIndex& index) const; virtual QPixmap pixmapForDrag(const QList& indexes) const; /** * Assuming the given indexes would be removed (hypothetically!), * return the index to be selected instead, starting from anchor. * The default implementation returns the next remaining sibling. */ virtual QModelIndex nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const; private Q_SLOTS: void slotGridSizeChanged(const QSize&); private: void ensureSelectionAfterChanges(); private: class Private; Private* const d; }; } // namespace Digikam #endif /* IMAGECATEGORIZEDVIEW_H */ diff --git a/showfoto/thumbbar/showfotocategorizedview.cpp b/showfoto/thumbbar/showfotocategorizedview.cpp index 69e5cb235b..1d5ea5dbcd 100644 --- a/showfoto/thumbbar/showfotocategorizedview.cpp +++ b/showfoto/thumbbar/showfotocategorizedview.cpp @@ -1,586 +1,586 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-07-13 * Description : Qt categorized item view for showfoto items * * Copyright (C) 2013 by Mohamed Anwer * * 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. * * ============================================================ */ #include "showfotocategorizedview.moc" // Qt includes #include // KDE includes #include #include // Local include #include "loadingcacheinterface.h" #include "itemviewtooltip.h" #include "showfotodelegate.h" #include "showfototooltipfiller.h" using namespace Digikam; namespace ShowFoto { class ShowfotoItemViewToolTip : public ItemViewToolTip { public: explicit ShowfotoItemViewToolTip(ShowfotoCategorizedView* const view) : ItemViewToolTip(view) { } ShowfotoCategorizedView* view() const { return static_cast(ItemViewToolTip::view()); } protected: virtual QString tipContents() { ShowfotoItemInfo info = ShowfotoImageModel::retrieveShowfotoItemInfo(currentIndex()); return ShowfotoToolTipFiller::ShowfotoItemInfoTipContents(info); } }; class ShowfotoCategorizedView::Private { public: Private() : model(0), filterModel(0), delegate(0), showToolTip(false), scrollToItemId(0), delayedEnterTimer(0), currentMouseEvent(0) { } ShowfotoImageModel* model; ShowfotoSortFilterModel* filterModel; ShowfotoDelegate* delegate; bool showToolTip; qlonglong scrollToItemId; QTimer* delayedEnterTimer; QMouseEvent* currentMouseEvent; }; ShowfotoCategorizedView::ShowfotoCategorizedView(QWidget* const parent) : DCategorizedView(parent), d(new Private) { setToolTip(new ShowfotoItemViewToolTip(this)); LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); d->delayedEnterTimer = new QTimer(this); d->delayedEnterTimer->setInterval(10); d->delayedEnterTimer->setSingleShot(false); connect(d->delayedEnterTimer, SIGNAL(timeout()), this, SLOT(slotDelayedEnter())); } ShowfotoCategorizedView::~ShowfotoCategorizedView() { d->delegate->removeAllOverlays(); delete d; } void ShowfotoCategorizedView::setModels(ShowfotoImageModel* model, ShowfotoSortFilterModel* filterModel) { if (d->delegate) { d->delegate->setAllOverlaysActive(false); } if (d->filterModel) { disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); disconnect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged())); } d->model = model; d->filterModel = filterModel; setModel(d->filterModel); connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged()), Qt::QueuedConnection); emit modelChanged(); if (d->delegate) { d->delegate->setAllOverlaysActive(true); } } ShowfotoImageModel* ShowfotoCategorizedView::showfotoImageModel() const { return d->model; } ShowfotoSortFilterModel* ShowfotoCategorizedView::showfotoSortFilterModel() const { return d->filterModel; } ShowfotoFilterModel* ShowfotoCategorizedView::showfotoFilterModel() const { return d->filterModel->showfotoFilterModel(); } ShowfotoThumbnailModel* ShowfotoCategorizedView::showfotoThumbnailModel() const { return qobject_cast(d->model); } QSortFilterProxyModel* ShowfotoCategorizedView::filterModel() const { return d->filterModel; } ShowfotoDelegate* ShowfotoCategorizedView::delegate() const { return d->delegate; } void ShowfotoCategorizedView::setItemDelegate(ShowfotoDelegate* delegate) { ThumbnailSize oldSize = thumbnailSize(); ShowfotoDelegate* const oldDelegate = d->delegate; if (oldDelegate) { hideIndexNotification(); d->delegate->setAllOverlaysActive(false); d->delegate->setViewOnAllOverlays(0); // Note: Be precise, no wildcard disconnect! disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); disconnect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } d->delegate = delegate; delegate->setThumbnailSize(oldSize); if (oldDelegate) { d->delegate->setSpacing(oldDelegate->spacing()); } DCategorizedView::setItemDelegate(d->delegate); updateDelegateSizes(); d->delegate->setViewOnAllOverlays(this); d->delegate->setAllOverlaysActive(true); connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); connect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } ShowfotoItemInfo ShowfotoCategorizedView::currentInfo() const { return d->filterModel->showfotoItemInfo(currentIndex()); } KUrl ShowfotoCategorizedView::currentUrl() const { return currentInfo().url; } QList ShowfotoCategorizedView::selectedShowfotoItemInfos() const { return d->filterModel->showfotoItemInfos(selectedIndexes()); } QList ShowfotoCategorizedView::selectedShowfotoItemInfosCurrentFirst() const { QList indexes = selectedIndexes(); QModelIndex current = currentIndex(); QList infos; foreach(const QModelIndex& index, indexes) { ShowfotoItemInfo info = d->filterModel->showfotoItemInfo(index); if (index == current) { infos.prepend(info); } else { infos.append(info); } } return infos; } QList ShowfotoCategorizedView::showfotoItemInfos() const { return d->filterModel->showfotoItemInfosSorted(); } KUrl::List ShowfotoCategorizedView::urls() const { QList infos = showfotoItemInfos(); KUrl::List urls; foreach(const ShowfotoItemInfo& info, infos) { urls << info.url; } return urls; } KUrl::List ShowfotoCategorizedView::selectedUrls() const { QList infos = selectedShowfotoItemInfos(); KUrl::List urls; foreach(const ShowfotoItemInfo& info, infos) { urls << info.url; } return urls; } void ShowfotoCategorizedView::toIndex(const KUrl& url) { DCategorizedView::toIndex(d->filterModel->indexForPath(url.toLocalFile())); } ShowfotoItemInfo ShowfotoCategorizedView::nextInOrder(const ShowfotoItemInfo& startingPoint, int nth) { QModelIndex index = d->filterModel->indexForShowfotoItemInfo(startingPoint); if (!index.isValid()) { return ShowfotoItemInfo(); } return d->filterModel->showfotoItemInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex())); } QModelIndex ShowfotoCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const { QModelIndex hint = DCategorizedView::nextIndexHint(anchor, removed); ShowfotoItemInfo info = d->filterModel->showfotoItemInfo(anchor); //kDebug() << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForShowfotoItemInfo(info); // Fixes a special case of multiple (face) entries for the same image. // If one is removed, any entry of the same image shall be preferred. if (d->model->indexesForShowfotoItemInfo(info).size() > 1) { // The hint is for a different info, but we may have a hint for the same info if (info != d->filterModel->showfotoItemInfo(hint)) { int minDiff = d->filterModel->rowCount(); QList indexesForShowfotoItemInfo = d->filterModel->mapListFromSource(d->model->indexesForShowfotoItemInfo(info)); foreach(const QModelIndex& index, indexesForShowfotoItemInfo) { if (index == anchor || !index.isValid() || removed.contains(index)) { continue; } int distance = qAbs(index.row() - anchor.row()); if (distance < minDiff) { minDiff = distance; hint = index; //kDebug() << "Chose index" << hint << "at distance" << minDiff << "to" << anchor; } } } } return hint; } ThumbnailSize ShowfotoCategorizedView::thumbnailSize() const { /* ShowfotoThumbnailModel *thumbModel = ShowfotoThumbnailModel(); if (thumbModel) return thumbModel->thumbnailSize(); */ if (d->delegate) { return d->delegate->thumbnailSize(); } return ThumbnailSize(); } void ShowfotoCategorizedView::setThumbnailSize(int size) { setThumbnailSize(ThumbnailSize(size)); } void ShowfotoCategorizedView::setThumbnailSize(const ThumbnailSize& s) { // we abuse this pair of method calls to restore scroll position layoutAboutToBeChanged(); d->delegate->setThumbnailSize(s); layoutWasChanged(); } void ShowfotoCategorizedView::setCurrentWhenAvailable(qlonglong showfotoItemId) { d->scrollToItemId = showfotoItemId; } void ShowfotoCategorizedView::setCurrentUrl(const KUrl& url) { if (url.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); return; } QString path = url.toLocalFile(); QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); } void ShowfotoCategorizedView::setCurrentInfo(const ShowfotoItemInfo& info) { QModelIndex index = d->filterModel->indexForShowfotoItemInfo(info); clearSelection(); setCurrentIndex(index); } void ShowfotoCategorizedView::setSelectedUrls(const KUrl::List& urlList) { QItemSelection mySelection; for (KUrl::List::const_iterator it = urlList.constBegin(); it!=urlList.constEnd(); ++it) { const QString path = it->path(); const QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { kWarning() << "no QModelIndex found for" << *it; } else { // TODO: is there a better way? mySelection.select(index, index); } } clearSelection(); selectionModel()->select(mySelection, QItemSelectionModel::Select); } void ShowfotoCategorizedView::setSelectedShowfotoItemInfos(const QList& infos) { QItemSelection mySelection; foreach(const ShowfotoItemInfo& info, infos) { QModelIndex index = d->filterModel->indexForShowfotoItemInfo(info); mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void ShowfotoCategorizedView::hintAt(const ShowfotoItemInfo& info) { if (info.isNull()) { return; } QModelIndex index = d->filterModel->indexForShowfotoItemInfo(info); if (!index.isValid()) { return; } selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); scrollTo(index); } void ShowfotoCategorizedView::addOverlay(ImageDelegateOverlay* overlay, ShowfotoDelegate* delegate) { if (!delegate) { delegate = d->delegate; } delegate->installOverlay(overlay); if (delegate == d->delegate) { overlay->setView(this); overlay->setActive(true); } } void ShowfotoCategorizedView::removeOverlay(ImageDelegateOverlay* overlay) { ShowfotoDelegate* const delegate = dynamic_cast(overlay->delegate()); if (delegate) { delegate->removeOverlay(overlay); } overlay->setView(0); } void ShowfotoCategorizedView::updateGeometries() { DCategorizedView::updateGeometries(); d->delayedEnterTimer->start(); } void ShowfotoCategorizedView::slotDelayedEnter() { // re-emit entered() for index under mouse (after layout). QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); if (mouseIndex.isValid()) { emit DigikamKCategorizedView::entered(mouseIndex); } } void ShowfotoCategorizedView::slotFileChanged(const QString& filePath) { QModelIndex index = d->filterModel->indexForPath(filePath); if (index.isValid()) { update(index); } } -void ShowfotoCategorizedView::indexActivated(const QModelIndex& index) +void ShowfotoCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers) { ShowfotoItemInfo info = d->filterModel->showfotoItemInfo(index); if (!info.isNull()) { - activated(info); + activated(info, modifiers); emit showfotoItemInfoActivated(info); } } void ShowfotoCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous) { DCategorizedView::currentChanged(index, previous); emit currentChanged(d->filterModel->showfotoItemInfo(index)); } void ShowfotoCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { DCategorizedView::selectionChanged(selectedItems, deselectedItems); if (!selectedItems.isEmpty()) { emit selected(d->filterModel->showfotoItemInfos(selectedItems.indexes())); } if (!deselectedItems.isEmpty()) { emit deselected(d->filterModel->showfotoItemInfos(deselectedItems.indexes())); } } -void ShowfotoCategorizedView::activated(const ShowfotoItemInfo&) +void ShowfotoCategorizedView::activated(const ShowfotoItemInfo&, Qt::KeyboardModifiers) { // implemented in subclass } void ShowfotoCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index) { ShowfotoItemInfo info = d->filterModel->showfotoItemInfo(index); showContextMenuOnInfo(event, info); } void ShowfotoCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const ShowfotoItemInfo&) { // implemented in subclass } void ShowfotoCategorizedView::paintEvent(QPaintEvent* e) { DCategorizedView::paintEvent(e); } QItemSelectionModel* ShowfotoCategorizedView::getSelectionModel() const { return selectionModel(); } AbstractItemDragDropHandler* ShowfotoCategorizedView::dragDropHandler() const { return d->model->dragDropHandler(); } } // namespace Showfoto diff --git a/showfoto/thumbbar/showfotocategorizedview.h b/showfoto/thumbbar/showfotocategorizedview.h index 2b9660663a..77c067adfb 100644 --- a/showfoto/thumbbar/showfotocategorizedview.h +++ b/showfoto/thumbbar/showfotocategorizedview.h @@ -1,193 +1,193 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-07-13 * Description : Qt categorized item view for showfoto items * * Copyright (C) 2013 by Mohamed Anwer * * 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. * * ============================================================ */ #ifndef SHOWFOTOCATEGORIZEDVIEW_H #define SHOWFOTOCATEGORIZEDVIEW_H // Local includes #include "dcategorizedview.h" #include "showfotoimagemodel.h" #include "showfotofiltermodel.h" #include "showfotothumbnailmodel.h" #include "showfotoiteminfo.h" #include "imagedelegateoverlay.h" namespace ShowFoto { class ShowfotoDelegate; class ShowfotoCategorizedView : public DCategorizedView { public: Q_OBJECT public: explicit ShowfotoCategorizedView(QWidget* const parent = 0); ~ShowfotoCategorizedView(); void setModels(ShowfotoImageModel* model, ShowfotoSortFilterModel* filterModel); ShowfotoImageModel* showfotoImageModel() const; ShowfotoSortFilterModel* showfotoSortFilterModel() const; QItemSelectionModel* getSelectionModel() const; /// Returns any ShowfotoFilterModel in chain. May not be sourceModel() ShowfotoFilterModel* showfotoFilterModel() const; /// Returns 0 if the ShowfotoImageModel is not an ShowfotoThumbnailModel ShowfotoThumbnailModel* showfotoThumbnailModel() const; ShowfotoDelegate* delegate() const; ShowfotoItemInfo currentInfo() const; KUrl currentUrl() const; QList selectedShowfotoItemInfos() const; QList selectedShowfotoItemInfosCurrentFirst() const; KUrl::List selectedUrls() const; QList showfotoItemInfos() const; KUrl::List urls() const; /** Selects the index as current and scrolls to it */ void toIndex(const KUrl& url); /** Returns the n-th info after the given one. * Specifically, return the previous info for nth = -1 * and the next info for n = 1. * Returns a null info if either startingPoint or the nth info are * not contained in the model */ ShowfotoItemInfo nextInOrder(const ShowfotoItemInfo& startingPoint, int nth); ShowfotoItemInfo previousInfo(const ShowfotoItemInfo& info) { return nextInOrder(info, -1); } ShowfotoItemInfo nextInfo(const ShowfotoItemInfo& info) { return nextInOrder(info, 1); } /// Add and remove an overlay. It will as well be removed automatically when destroyed. /// Unless you pass a different delegate, the current delegate will be used. void addOverlay(ImageDelegateOverlay* overlay, ShowfotoDelegate* delegate = 0); void removeOverlay(ImageDelegateOverlay* overlay); //TODO: Implement This // void addSelectionOverlay(ShowfotoDelegate* delegate = 0); ThumbnailSize thumbnailSize() const; virtual void setThumbnailSize(const ThumbnailSize& size); public Q_SLOTS: void setThumbnailSize(int size); /** Scroll the view to the given item when it becomes available */ void setCurrentWhenAvailable(qlonglong ShowfotoItemId); /** Set as current item the item identified by its file url */ void setCurrentUrl(const KUrl& url); /** Set as current item the item identified by the ShowfotoItemInfo */ void setCurrentInfo(const ShowfotoItemInfo& info); /** Set selected items identified by their file urls */ void setSelectedUrls(const KUrl::List& urlList); /** Set selected items */ void setSelectedShowfotoItemInfos(const QList& infos); /** Does something to gain attention for info, but not changing current selection */ void hintAt(const ShowfotoItemInfo& info); Q_SIGNALS: void currentChanged(const ShowfotoItemInfo& info); /// Emitted when new items are selected. The parameter includes only the newly selected infos, /// there may be other already selected infos. void selected(const QList& newSelectedInfos); /// Emitted when items are deselected. There may be other selected infos left. /// This signal is not emitted when the model is reset; then only selectionCleared is emitted. void deselected(const QList& nowDeselectedInfos); /// Emitted when the given ShowfotoItemInfo is activated. Info is never null. void showfotoItemInfoActivated(const ShowfotoItemInfo& info); /// Emitted when a new model is set void modelChanged(); protected: // reimplemented from parent class QSortFilterProxyModel* filterModel() const; AbstractItemDragDropHandler* dragDropHandler() const; QModelIndex nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const; void setItemDelegate(ShowfotoDelegate* delegate); - void indexActivated(const QModelIndex& index); + void indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers); void currentChanged(const QModelIndex& index, const QModelIndex& previous); void paintEvent(QPaintEvent* e); void selectionChanged(const QItemSelection&, const QItemSelection&); void updateGeometries(); /// Reimplement these in a subclass - virtual void activated(const ShowfotoItemInfo& info); + virtual void activated(const ShowfotoItemInfo& info, Qt::KeyboardModifiers modifiers); virtual void showContextMenuOnInfo(QContextMenuEvent* event, const ShowfotoItemInfo& info); virtual void showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index); private Q_SLOTS: void slotFileChanged(const QString& filePath); void slotDelayedEnter(); private: void scrollToStoredItem(); private: class Private; Private* const d; }; } // namespace Showfoto #endif // SHOWFOTOCATEGORIZEDVIEW_H diff --git a/utilities/importui/views/importcategorizedview.cpp b/utilities/importui/views/importcategorizedview.cpp index 891daeae77..877fd29225 100644 --- a/utilities/importui/views/importcategorizedview.cpp +++ b/utilities/importui/views/importcategorizedview.cpp @@ -1,624 +1,624 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-07-13 * Description : Qt categorized item view for camera items * * Copyright (C) 2012 by Islam Wazery * * 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. * * ============================================================ */ #include "importcategorizedview.moc" // Qt includes #include "QTimer" // KDE includes #include // Local includes #include "loadingcacheinterface.h" #include "imageselectionoverlay.h" #include "camitemsortsettings.h" #include "itemviewtooltip.h" #include "importdelegate.h" #include "importtooltipfiller.h" #include "thumbnailloadthread.h" #include "importsettings.h" namespace Digikam { class ImportItemViewToolTip : public ItemViewToolTip { public: explicit ImportItemViewToolTip(ImportCategorizedView* const view) : ItemViewToolTip(view) { } ImportCategorizedView* view() const { return static_cast(ItemViewToolTip::view()); } protected: virtual QString tipContents() { CamItemInfo info = ImportImageModel::retrieveCamItemInfo(currentIndex()); return ImportToolTipFiller::CamItemInfoTipContents(info); } }; class ImportCategorizedView::Private { public: Private() : model(0), filterModel(0), delegate(0), showToolTip(false), scrollToItemId(0), delayedEnterTimer(0), currentMouseEvent(0) { } ImportImageModel* model; ImportSortFilterModel* filterModel; ImportDelegate* delegate; bool showToolTip; qlonglong scrollToItemId; QTimer* delayedEnterTimer; QMouseEvent* currentMouseEvent; }; ImportCategorizedView::ImportCategorizedView(QWidget* const parent) : DCategorizedView(parent), d(new Private) { setToolTip(new ImportItemViewToolTip(this)); LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); d->delayedEnterTimer = new QTimer(this); d->delayedEnterTimer->setInterval(10); d->delayedEnterTimer->setSingleShot(true); connect(d->delayedEnterTimer, SIGNAL(timeout()), this, SLOT(slotDelayedEnter())); } ImportCategorizedView::~ImportCategorizedView() { d->delegate->removeAllOverlays(); delete d; } void ImportCategorizedView::setModels(ImportImageModel* model, ImportSortFilterModel* filterModel) { if (d->delegate) { d->delegate->setAllOverlaysActive(false); } if (d->filterModel) { disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); disconnect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged())); } if (d->model) { disconnect(d->model, SIGNAL(itemInfosAdded(QList)), this, SLOT(slotCamItemInfosAdded())); } d->model = model; d->filterModel = filterModel; setModel(d->filterModel); connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged()), Qt::QueuedConnection); connect(d->model, SIGNAL(itemInfosAdded(QList)), this, SLOT(slotCamItemInfosAdded())); emit modelChanged(); if (d->delegate) { d->delegate->setAllOverlaysActive(true); } } ImportImageModel* ImportCategorizedView::importImageModel() const { return d->model; } ImportSortFilterModel* ImportCategorizedView::importSortFilterModel() const { return d->filterModel; } ImportFilterModel* ImportCategorizedView::importFilterModel() const { return d->filterModel->importFilterModel(); } ImportThumbnailModel* ImportCategorizedView::importThumbnailModel() const { return qobject_cast(d->model); } QSortFilterProxyModel* ImportCategorizedView::filterModel() const { return d->filterModel; } ImportDelegate* ImportCategorizedView::delegate() const { return d->delegate; } void ImportCategorizedView::setItemDelegate(ImportDelegate* delegate) { ThumbnailSize oldSize = thumbnailSize(); ImportDelegate* oldDelegate = d->delegate; if (oldDelegate) { hideIndexNotification(); d->delegate->setAllOverlaysActive(false); d->delegate->setViewOnAllOverlays(0); // Note: Be precise, no wildcard disconnect! disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); disconnect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } d->delegate = delegate; delegate->setThumbnailSize(oldSize); if (oldDelegate) { d->delegate->setSpacing(oldDelegate->spacing()); } DCategorizedView::setItemDelegate(d->delegate); setCategoryDrawer(d->delegate->categoryDrawer()); updateDelegateSizes(); d->delegate->setViewOnAllOverlays(this); d->delegate->setAllOverlaysActive(true); connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); connect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } CamItemInfo ImportCategorizedView::currentInfo() const { return d->filterModel->camItemInfo(currentIndex()); } KUrl ImportCategorizedView::currentUrl() const { return currentInfo().url(); } QList ImportCategorizedView::selectedCamItemInfos() const { return d->filterModel->camItemInfos(selectedIndexes()); } QList ImportCategorizedView::selectedCamItemInfosCurrentFirst() const { QList indexes = selectedIndexes(); QModelIndex current = currentIndex(); QList infos; foreach(const QModelIndex& index, indexes) { CamItemInfo info = d->filterModel->camItemInfo(index); if (index == current) { infos.prepend(info); } else { infos.append(info); } } return infos; } QList ImportCategorizedView::camItemInfos() const { return d->filterModel->camItemInfosSorted(); } KUrl::List ImportCategorizedView::urls() const { QList infos = camItemInfos(); KUrl::List urls; foreach(const CamItemInfo& info, infos) { urls << info.url(); } return urls; } KUrl::List ImportCategorizedView::selectedUrls() const { QList infos = selectedCamItemInfos(); KUrl::List urls; foreach(const CamItemInfo& info, infos) { urls << info.url(); } return urls; } void ImportCategorizedView::toIndex(const KUrl& url) { DCategorizedView::toIndex(d->filterModel->indexForPath(url.toLocalFile())); } CamItemInfo ImportCategorizedView::nextInOrder(const CamItemInfo& startingPoint, int nth) { QModelIndex index = d->filterModel->indexForCamItemInfo(startingPoint); if (!index.isValid()) { return CamItemInfo(); } return d->filterModel->camItemInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex())); } QModelIndex ImportCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const { QModelIndex hint = DCategorizedView::nextIndexHint(anchor, removed); CamItemInfo info = d->filterModel->camItemInfo(anchor); //kDebug() << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForCamItemInfo(info); // Fixes a special case of multiple (face) entries for the same image. // If one is removed, any entry of the same image shall be preferred. if (d->model->numberOfIndexesForCamItemInfo(info) > 1) { // The hint is for a different info, but we may have a hint for the same info if (info != d->filterModel->camItemInfo(hint)) { int minDiff = d->filterModel->rowCount(); QList indexesForCamItemInfo = d->filterModel->mapListFromSource(d->model->indexesForCamItemInfo(info)); foreach(const QModelIndex& index, indexesForCamItemInfo) { if (index == anchor || !index.isValid() || removed.contains(index)) { continue; } int distance = qAbs(index.row() - anchor.row()); if (distance < minDiff) { minDiff = distance; hint = index; //kDebug() << "Chose index" << hint << "at distance" << minDiff << "to" << anchor; } } } } return hint; } ThumbnailSize ImportCategorizedView::thumbnailSize() const { /* ImportThumbnailModel *thumbModel = importThumbnailModel(); if (thumbModel) return thumbModel->thumbnailSize(); */ if (d->delegate) { return d->delegate->thumbnailSize(); } return ThumbnailSize(); } void ImportCategorizedView::setThumbnailSize(int size) { setThumbnailSize(ThumbnailSize(size)); } void ImportCategorizedView::setThumbnailSize(const ThumbnailSize& s) { // we abuse this pair of method calls to restore scroll position // TODO check if needed layoutAboutToBeChanged(); d->delegate->setThumbnailSize(s); layoutWasChanged(); } void ImportCategorizedView::setCurrentWhenAvailable(qlonglong camItemId) { d->scrollToItemId = camItemId; } void ImportCategorizedView::setCurrentUrl(const KUrl& url) { if (url.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); return; } QString path = url.toLocalFile(); QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); } void ImportCategorizedView::setCurrentInfo(const CamItemInfo& info) { QModelIndex index = d->filterModel->indexForCamItemInfo(info); clearSelection(); setCurrentIndex(index); } void ImportCategorizedView::setSelectedUrls(const KUrl::List& urlList) { QItemSelection mySelection; for (KUrl::List::const_iterator it = urlList.constBegin(); it!=urlList.constEnd(); ++it) { const QString path = it->path(); const QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { kWarning() << "no QModelIndex found for" << *it; } else { // TODO: is there a better way? mySelection.select(index, index); } } clearSelection(); selectionModel()->select(mySelection, QItemSelectionModel::Select); } void ImportCategorizedView::setSelectedCamItemInfos(const QList& infos) { QItemSelection mySelection; foreach(const CamItemInfo& info, infos) { QModelIndex index = d->filterModel->indexForCamItemInfo(info); mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void ImportCategorizedView::hintAt(const CamItemInfo& info) { if (info.isNull()) { return; } QModelIndex index = d->filterModel->indexForCamItemInfo(info); if (!index.isValid()) { return; } selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); scrollTo(index); } void ImportCategorizedView::addOverlay(ImageDelegateOverlay* overlay, ImportDelegate* delegate) { if (!delegate) { delegate = d->delegate; } delegate->installOverlay(overlay); if (delegate == d->delegate) { overlay->setView(this); overlay->setActive(true); } } void ImportCategorizedView::removeOverlay(ImageDelegateOverlay* overlay) { ImportDelegate* delegate = dynamic_cast(overlay->delegate()); if (delegate) { delegate->removeOverlay(overlay); } overlay->setView(0); } void ImportCategorizedView::updateGeometries() { DCategorizedView::updateGeometries(); d->delayedEnterTimer->start(); } void ImportCategorizedView::slotDelayedEnter() { // re-emit entered() for index under mouse (after layout). QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); if (mouseIndex.isValid()) { emit DigikamKCategorizedView::entered(mouseIndex); } } void ImportCategorizedView::addSelectionOverlay(ImportDelegate* delegate) { addOverlay(new ImageSelectionOverlay(this), delegate); } void ImportCategorizedView::scrollToStoredItem() { if (d->scrollToItemId) { if (d->model->hasImage(d->scrollToItemId)) { QModelIndex index = d->filterModel->indexForCamItemId(d->scrollToItemId); setCurrentIndex(index); scrollToRelaxed(index, QAbstractItemView::PositionAtCenter); d->scrollToItemId = 0; } } } void ImportCategorizedView::slotCamItemInfosAdded() { if (d->scrollToItemId) { scrollToStoredItem(); } } void ImportCategorizedView::slotFileChanged(const QString& filePath) { QModelIndex index = d->filterModel->indexForPath(filePath); if (index.isValid()) { update(index); } } -void ImportCategorizedView::indexActivated(const QModelIndex& index) +void ImportCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers) { CamItemInfo info = d->filterModel->camItemInfo(index); if (!info.isNull()) { - activated(info); + activated(info, modifiers); emit camItemInfoActivated(info); } } void ImportCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous) { DCategorizedView::currentChanged(index, previous); emit currentChanged(d->filterModel->camItemInfo(index)); } void ImportCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { DCategorizedView::selectionChanged(selectedItems, deselectedItems); if (!selectedItems.isEmpty()) { emit selected(d->filterModel->camItemInfos(selectedItems.indexes())); } if (!deselectedItems.isEmpty()) { emit deselected(d->filterModel->camItemInfos(deselectedItems.indexes())); } } -void ImportCategorizedView::activated(const CamItemInfo&) +void ImportCategorizedView::activated(const CamItemInfo&, Qt::KeyboardModifiers) { // implemented in subclass } void ImportCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index) { CamItemInfo info = d->filterModel->camItemInfo(index); showContextMenuOnInfo(event, info); } void ImportCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const CamItemInfo&) { // implemented in subclass } void ImportCategorizedView::paintEvent(QPaintEvent* e) { DCategorizedView::paintEvent(e); } QItemSelectionModel* ImportCategorizedView::getSelectionModel() const { return selectionModel(); } AbstractItemDragDropHandler* ImportCategorizedView::dragDropHandler() const { return d->model->dragDropHandler(); } } // namespace Digikam diff --git a/utilities/importui/views/importcategorizedview.h b/utilities/importui/views/importcategorizedview.h index 59d7ad9b2b..622d6f5376 100644 --- a/utilities/importui/views/importcategorizedview.h +++ b/utilities/importui/views/importcategorizedview.h @@ -1,185 +1,185 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-07-13 * Description : Qt categorized item view for camera items * * Copyright (C) 2012 by Islam Wazery * * 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. * * ============================================================ */ #ifndef IMPORTCATEGORIZEDVIEW_H #define IMPORTCATEGORIZEDVIEW_H // Local includes #include "dcategorizedview.h" #include "importimagemodel.h" #include "importfiltermodel.h" #include "importthumbnailmodel.h" #include "imagedelegateoverlay.h" #include "camiteminfo.h" namespace Digikam { class ImportDelegate; class ImportCategorizedView : public DCategorizedView { Q_OBJECT public: explicit ImportCategorizedView(QWidget* const parent = 0); ~ImportCategorizedView(); void setModels(ImportImageModel* model, ImportSortFilterModel* filterModel); ImportImageModel* importImageModel() const; ImportSortFilterModel* importSortFilterModel() const; QItemSelectionModel* getSelectionModel() const; /// Returns any ImportFilterModel in chain. May not be sourceModel() ImportFilterModel* importFilterModel() const; /// Returns 0 if the ImportImageModel is not an ImportThumbnailModel ImportThumbnailModel* importThumbnailModel() const; ImportDelegate* delegate() const; CamItemInfo currentInfo() const; KUrl currentUrl() const; QList selectedCamItemInfos() const; QList selectedCamItemInfosCurrentFirst() const; KUrl::List selectedUrls() const; QList camItemInfos() const; KUrl::List urls() const; /** Selects the index as current and scrolls to it */ void toIndex(const KUrl& url); /** Returns the n-th info after the given one. * Specifically, return the previous info for nth = -1 * and the next info for n = 1. * Returns a null info if either startingPoint or the nth info are * not contained in the model */ CamItemInfo nextInOrder(const CamItemInfo& startingPoint, int nth); CamItemInfo previousInfo(const CamItemInfo& info) { return nextInOrder(info, -1); } CamItemInfo nextInfo(const CamItemInfo& info) { return nextInOrder(info, 1); } /// Add and remove an overlay. It will as well be removed automatically when destroyed. /// Unless you pass a different delegate, the current delegate will be used. void addOverlay(ImageDelegateOverlay* overlay, ImportDelegate* delegate = 0); void removeOverlay(ImageDelegateOverlay* overlay); void addSelectionOverlay(ImportDelegate* delegate = 0); ThumbnailSize thumbnailSize() const; virtual void setThumbnailSize(const ThumbnailSize& size); public Q_SLOTS: void setThumbnailSize(int size); /** Scroll the view to the given item when it becomes available */ void setCurrentWhenAvailable(qlonglong camItemId); /** Set as current item the item identified by its file url */ void setCurrentUrl(const KUrl& url); /** Set as current item the item identified by the CamItemInfo */ void setCurrentInfo(const CamItemInfo& info); /** Set selected items identified by their file urls */ void setSelectedUrls(const KUrl::List& urlList); /** Set selected items */ void setSelectedCamItemInfos(const QList& infos); /** Does something to gain attention for info, but not changing current selection */ void hintAt(const CamItemInfo& info); Q_SIGNALS: void currentChanged(const CamItemInfo& info); /// Emitted when new items are selected. The parameter includes only the newly selected infos, /// there may be other already selected infos. void selected(const QList& newSelectedInfos); /// Emitted when items are deselected. There may be other selected infos left. /// This signal is not emitted when the model is reset; then only selectionCleared is emitted. void deselected(const QList& nowDeselectedInfos); /// Emitted when the given CamItemInfo is activated. Info is never null. void camItemInfoActivated(const CamItemInfo& info); /// Emitted when a new model is set void modelChanged(); protected Q_SLOTS: void slotCamItemInfosAdded(); protected: // reimplemented from parent class QSortFilterProxyModel* filterModel() const; AbstractItemDragDropHandler* dragDropHandler() const; QModelIndex nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const; void setItemDelegate(ImportDelegate* delegate); - void indexActivated(const QModelIndex& index); + void indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers); void currentChanged(const QModelIndex& index, const QModelIndex& previous); void paintEvent(QPaintEvent* e); void selectionChanged(const QItemSelection&, const QItemSelection&); void updateGeometries(); /// Reimplement these in a subclass - virtual void activated(const CamItemInfo& info); + virtual void activated(const CamItemInfo& info, Qt::KeyboardModifiers modifiers); virtual void showContextMenuOnInfo(QContextMenuEvent* event, const CamItemInfo& info); virtual void showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index); private Q_SLOTS: void slotFileChanged(const QString& filePath); void slotDelayedEnter(); private: void scrollToStoredItem(); private: class Private; Private* const d; }; } // namespace Digikam #endif // IMPORTCATEGORIZEDVIEW_H diff --git a/utilities/importui/views/importiconview.cpp b/utilities/importui/views/importiconview.cpp index 0f47a93604..0bde2cad42 100644 --- a/utilities/importui/views/importiconview.cpp +++ b/utilities/importui/views/importiconview.cpp @@ -1,472 +1,472 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-22-07 * Description : Icon view for import tool items * * Copyright (C) 2012 by Islam Wazery * Copyright (C) 2012-2014 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. * * ============================================================ */ #include "importiconview.moc" #include "importiconview_p.h" // Qt includes #include #include // KDE includes #include #include // Local includes #include "importcategorizedview.h" #include "importoverlays.h" #include "importsettings.h" #include "camitemsortsettings.h" #include "fileactionmngr.h" #include "importdelegate.h" #include "advancedrenamedialog.h" #include "advancedrenameprocessdialog.h" #include "imageviewutilities.h" #include "importcontextmenu.h" #include "importdragdrop.h" namespace Digikam { ImportIconView::ImportIconView(QWidget* const parent) : ImportCategorizedView(parent), d(new Private(this)) { } void ImportIconView::init() { d->normalDelegate = new ImportNormalDelegate(this); setItemDelegate(d->normalDelegate); setSpacing(10); ImportSettings* const settings = ImportSettings::instance(); setThumbnailSize((ThumbnailSize::Size)settings->getDefaultIconSize()); importImageModel()->setDragDropHandler(new ImportDragDropHandler(importImageModel())); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(false); setToolTipEnabled(settings->showToolTipsIsValid()); // selection overlay addSelectionOverlay(d->normalDelegate); //TODO: addSelectionOverlay(d->faceDelegate); // rotation overlays d->rotateLeftOverlay = ImportRotateOverlay::left(this); d->rotateRightOverlay = ImportRotateOverlay::right(this); addOverlay(new ImportDownloadOverlay(this)); addOverlay(new ImportLockOverlay(this)); addOverlay(new ImportCoordinatesOverlay(this)); d->updateOverlays(); // rating overlay ImportRatingOverlay* const ratingOverlay = new ImportRatingOverlay(this); addOverlay(ratingOverlay); //TODO: GroupIndicatorOverlay* groupOverlay = new GroupIndicatorOverlay(this); //TODO: addOverlay(groupOverlay); connect(ratingOverlay, SIGNAL(ratingEdited(QList,int)), this, SLOT(assignRating(QList,int))); //TODO: connect(groupOverlay, SIGNAL(toggleGroupOpen(QModelIndex)), //this, SLOT(groupIndicatorClicked(QModelIndex))); //TODO: connect(groupOverlay, SIGNAL(showButtonContextMenu(QModelIndex,QContextMenuEvent*)), //this, SLOT(showGroupContextMenu(QModelIndex,QContextMenuEvent*))); //TODO: connect(importImageModel()->dragDropHandler(), SIGNAL(assignTags(QList,QList)), //FileActionMngr::instance(), SLOT(assignTags(QList,QList))); //TODO: connect(importImageModel()->dragDropHandler(), SIGNAL(addToGroup(CamItemInfo,QList)), //FileActionMngr::instance(), SLOT(addToGroup(CamItemInfo,QList))); connect(settings, SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); slotSetupChanged(); } ImportIconView::~ImportIconView() { delete d; } ImageViewUtilities* ImportIconView::utilities() const { return d->utilities; } void ImportIconView::setThumbnailSize(const ThumbnailSize& size) { ImportCategorizedView::setThumbnailSize(size); } int ImportIconView::fitToWidthIcons() { return delegate()->calculatethumbSizeToFit(viewport()->size().width()); } CamItemInfo ImportIconView::camItemInfo(const QString& folder, const QString& file) { KUrl url(folder); url.adjustPath(KUrl::AddTrailingSlash); url.setFileName(file); QModelIndex indexForCamItemInfo = importFilterModel()->indexForPath(url.toLocalFile()); if(indexForCamItemInfo.isValid()) { return importFilterModel()->camItemInfo(indexForCamItemInfo); } return CamItemInfo(); } CamItemInfo& ImportIconView::camItemInfoRef(const QString& folder, const QString& file) { KUrl url(folder); url.adjustPath(KUrl::AddTrailingSlash); url.setFileName(file); QModelIndex indexForCamItemInfo = importFilterModel()->indexForPath(url.toLocalFile()); QModelIndex mappedIndex = importFilterModel()->mapToSource(indexForCamItemInfo); return importImageModel()->camItemInfoRef(mappedIndex); } void ImportIconView::slotSetupChanged() { setToolTipEnabled(ImportSettings::instance()->showToolTipsIsValid()); setFont(ImportSettings::instance()->getIconViewFont()); d->updateOverlays(); ImportCategorizedView::slotSetupChanged(); } void ImportIconView::rename() { KUrl::List urls = selectedUrls(); NewNamesList newNamesList; QPointer dlg = new AdvancedRenameDialog(this); dlg->slotAddImages(urls); if (dlg->exec() == KDialog::Accepted) { newNamesList = dlg->newNames(); } delete dlg; if (!newNamesList.isEmpty()) { QPointer dlg = new AdvancedRenameProcessDialog(newNamesList); dlg->exec(); delete dlg; } } void ImportIconView::deleteSelected(bool /*permanently*/) { CamItemInfoList camItemInfoList = selectedCamItemInfos(); //FIXME: This way of deletion may not working with camera items. //if (d->utilities->deleteImages(camItemInfoList, permanently)) //{ // awayFromSelection(); //} } void ImportIconView::deleteSelectedDirectly(bool /*permanently*/) { CamItemInfoList camItemInfoList = selectedCamItemInfos(); //FIXME: This way of deletion may not working with camera items. //d->utilities->deleteImagesDirectly(camItemInfoList, permanently); awayFromSelection(); } void ImportIconView::createGroupFromSelection() { //TODO: Impelemnt grouping in import tool. //QList selectedInfos = selectedCamItemInfosCurrentFirst(); //CamItemInfo groupLeader = selectedInfos.takeFirst(); //FileActionMngr::instance()->addToGroup(groupLeader, selectedInfos); } void ImportIconView::createGroupByTimeFromSelection() { //TODO: Impelemnt grouping in import tool. //QList selectedInfos = selectedCamItemInfosCurrentFirst(); //while (selectedInfos.size() > 0) //{ //QList group; //CamItemInfo groupLeader = selectedInfos.takeFirst(); //QDateTime dateTime = groupLeader.dateTime(); //while (selectedInfos.size() > 0 && abs(dateTime.secsTo(selectedInfos.first().dateTime())) < 2) //{ // group.push_back(selectedInfos.takeFirst()); //} //FileActionMngr::instance()->addToGroup(groupLeader, group); //} } void ImportIconView::ungroupSelected() { //TODO: Impelemnt grouping in import tool. //FileActionMngr::instance()->ungroup(selectedCamItemInfos()); } void ImportIconView::removeSelectedFromGroup() { //TODO: Impelemnt grouping in import tool. //FileActionMngr::instance()->removeFromGroup(selectedCamItemInfos()); } void ImportIconView::slotRotateLeft(const QList& indexes) { QList imageInfos; foreach(const QModelIndex& index, indexes) { ImageInfo imageInfo(importFilterModel()->camItemInfo(index).url()); imageInfos << imageInfo; } FileActionMngr::instance()->transform(imageInfos, KExiv2Iface::RotationMatrix::Rotate270); } void ImportIconView::slotRotateRight(const QList& indexes) { QList imageInfos; foreach(const QModelIndex& index, indexes) { ImageInfo imageInfo(importFilterModel()->camItemInfo(index).url()); imageInfos << imageInfo; } FileActionMngr::instance()->transform(imageInfos, KExiv2Iface::RotationMatrix::Rotate90); } -void ImportIconView::activated(const CamItemInfo& info) +void ImportIconView::activated(const CamItemInfo& info, Qt::KeyboardModifiers) { if (info.isNull()) { return; } if (ImportSettings::instance()->getItemLeftClickAction() == ImportSettings::ShowPreview) { emit previewRequested(info, false); } else { //TODO: openFile(info); } } void ImportIconView::showContextMenuOnInfo(QContextMenuEvent* event, const CamItemInfo& /*info*/) { QList selectedInfos = selectedCamItemInfosCurrentFirst(); QList selectedItemIDs; foreach(const CamItemInfo& info, selectedInfos) { selectedItemIDs << info.id; } // -------------------------------------------------------- KMenu popmenu(this); ImportContextMenuHelper cmhelper(&popmenu); cmhelper.addAction("importui_fullscreen"); cmhelper.addAction("options_show_menubar"); cmhelper.addAction("import_zoomfit2window"); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction("importui_imagedownload"); cmhelper.addAction("importui_imagemarkasdownloaded"); cmhelper.addAction("importui_imagelock"); cmhelper.addAction("importui_delete"); cmhelper.addSeparator(); cmhelper.addAction("importui_item_view"); cmhelper.addServicesMenu(selectedUrls()); //TODO: cmhelper.addRotateMenu(selectedItemIDs); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction("importui_selectall"); cmhelper.addAction("importui_selectnone"); cmhelper.addAction("importui_selectinvert"); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAssignTagsMenu(selectedItemIDs); cmhelper.addRemoveTagsMenu(selectedItemIDs); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addLabelsAction(); //if (!d->faceMode) //{ // cmhelper.addGroupMenu(selectedItemIDs); //} // special action handling -------------------------------- connect(&cmhelper, SIGNAL(signalAssignTag(int)), this, SLOT(assignTagToSelected(int))); //TODO: Implement tag view for import tool. connect(&cmhelper, SIGNAL(signalPopupTagsView()), this, SIGNAL(signalPopupTagsView())); connect(&cmhelper, SIGNAL(signalRemoveTag(int)), this, SLOT(removeTagFromSelected(int))); //connect(&cmhelper, SIGNAL(signalGotoTag(int)), //this, SIGNAL(gotoTagAndImageRequested(int))); connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)), this, SLOT(assignPickLabelToSelected(int))); connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)), this, SLOT(assignColorLabelToSelected(int))); connect(&cmhelper, SIGNAL(signalAssignRating(int)), this, SLOT(assignRatingToSelected(int))); //connect(&cmhelper, SIGNAL(signalAddToExistingQueue(int)), //this, SLOT(insertSelectedToExistingQueue(int))); //FIXME: connect(&cmhelper, SIGNAL(signalCreateGroup()), //this, SLOT(createGroupFromSelection())); //connect(&cmhelper, SIGNAL(signalUngroup()), //this, SLOT(ungroupSelected())); //connect(&cmhelper, SIGNAL(signalRemoveFromGroup()), //this, SLOT(removeSelectedFromGroup())); // -------------------------------------------------------- cmhelper.exec(event->globalPos()); } void ImportIconView::showContextMenu(QContextMenuEvent* event) { KMenu popmenu(this); ImportContextMenuHelper cmhelper(&popmenu); cmhelper.addAction("importui_fullscreen"); cmhelper.addAction("options_show_menubar"); cmhelper.addSeparator(); cmhelper.addAction("importui_close"); // -------------------------------------------------------- cmhelper.exec(event->globalPos()); } void ImportIconView::assignTagToSelected(int tagID) { CamItemInfoList infos = selectedCamItemInfos(); foreach(const CamItemInfo& info, infos) { importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).tagIds.append(tagID); } } void ImportIconView::removeTagFromSelected(int tagID) { CamItemInfoList infos = selectedCamItemInfos(); foreach(const CamItemInfo& info, infos) { importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).tagIds.removeAll(tagID); } } void ImportIconView::assignPickLabel(const QModelIndex& index, int pickId) { importImageModel()->camItemInfoRef(index).pickLabel = pickId; } void ImportIconView::assignPickLabelToSelected(int pickId) { CamItemInfoList infos = selectedCamItemInfos(); foreach(const CamItemInfo& info, infos) { importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).pickLabel = pickId; } } void ImportIconView::assignColorLabel(const QModelIndex& index, int colorId) { importImageModel()->camItemInfoRef(index).colorLabel = colorId; } void ImportIconView::assignColorLabelToSelected(int colorId) { CamItemInfoList infos = selectedCamItemInfos(); foreach(const CamItemInfo& info, infos) { importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).colorLabel = colorId; } } void ImportIconView::assignRating(const QList& indexes, int rating) { foreach(const QModelIndex& index, indexes) { if (index.isValid()) { importImageModel()->camItemInfoRef(index).rating = rating; } } } void ImportIconView::assignRatingToSelected(int rating) { CamItemInfoList infos = selectedCamItemInfos(); foreach(const CamItemInfo& info, infos) { importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).rating = rating; } } } // namespace Digikam diff --git a/utilities/importui/views/importiconview.h b/utilities/importui/views/importiconview.h index 248f3bd3ad..3160885f48 100644 --- a/utilities/importui/views/importiconview.h +++ b/utilities/importui/views/importiconview.h @@ -1,106 +1,106 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-22-07 * Description : Icon view for import tool items * * Copyright (C) 2012 by Islam Wazery * Copyright (C) 2012-2014 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. * * ============================================================ */ #ifndef ImportIconView_H #define ImportIconView_H // Local includes #include "importcategorizedview.h" namespace Digikam { class ImageViewUtilities; class ImportIconView : public ImportCategorizedView { Q_OBJECT public: explicit ImportIconView(QWidget* const parent = 0); ~ImportIconView(); void init(); ImageViewUtilities* utilities() const; int fitToWidthIcons(); CamItemInfo camItemInfo(const QString& folder, const QString& file); CamItemInfo& camItemInfoRef(const QString& folder, const QString& file); virtual void setThumbnailSize(const ThumbnailSize& size); public Q_SLOTS: void deleteSelected(bool permanently = false); void deleteSelectedDirectly(bool permanently = false); void createGroupFromSelection(); void createGroupByTimeFromSelection(); void ungroupSelected(); void removeSelectedFromGroup(); void rename(); void assignTagToSelected(int tagID); void removeTagFromSelected(int tagID); void assignPickLabel(const QModelIndex& index, int pickId); void assignPickLabelToSelected(int pickId); void assignColorLabel(const QModelIndex& index, int colorId); void assignColorLabelToSelected(int colorId); void assignRating(const QList& index, int rating); void assignRatingToSelected(int rating); Q_SIGNALS: void previewRequested(const CamItemInfo& info, bool downloadPreview); void signalPopupTagsView(); private Q_SLOTS: void slotRotateLeft(const QList&); void slotRotateRight(const QList&); //void slotInitProgressIndicator(); protected: - virtual void activated(const CamItemInfo& info); + virtual void activated(const CamItemInfo& info, Qt::KeyboardModifiers modifiers); virtual void showContextMenuOnInfo(QContextMenuEvent* event, const CamItemInfo& info); virtual void showContextMenu(QContextMenuEvent* event); virtual void slotSetupChanged(); private: class Private; Private* const d; }; } // namespace Digikam #endif // ImportIconView_H