Commit 4639e6f0 authored by Gilles Caulier's avatar Gilles Caulier 🗼
Browse files

Restore CameraThumbsCtrl class to manage and cache thumbnails from camera...

Restore CameraThumbsCtrl class to manage and cache thumbnails from camera (concept from 2.x serie, dropped with 3.x model view port)
It cache Camera thumb in max size and pass to thumb model pixmap which need to ne resized. No need to reload thumb from camera if thumb size is changed.
This take a lot of time.
This controler is also used in CameMessageBox to confirm which images to remove from camera. This message box is also restored (fdropped in same condition with 3.x serie)
Note: This controller have been moved as well to ImportThumnailModel class which have been a wrong idea. This implementation must be separated and shared with class tht needs cached thumbnails.
ImportThumnailModel use this controller directly which simplify a lots implementation.
BUGS: 330063
CCMAIL: al.wazery@gmail.com
CCMAIL: tpr@iki.fi
parent 8caefb96
......@@ -46,6 +46,7 @@
//local includes
#include "cameracontroller.h"
#include "imageposition.h"
#include "imageinfo.h"
#include "imagemodel.h"
......
......@@ -3,6 +3,7 @@ SET(libimportui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/main/importsettings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/cameracontroller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/camerathumbsctrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/camerahistoryupdater.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/dkcamera.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/gpcamera.cpp
......@@ -12,7 +13,7 @@ SET(libimportui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/dialogs/camerafolderdialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dialogs/camerainfodialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dialogs/capturedlg.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/dialogs/cameramessagebox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dialogs/cameramessagebox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/widgets/capturewidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/widgets/freespacewidget.cpp
......
......@@ -25,6 +25,10 @@
#include "importdelegate.moc"
#include "importdelegatepriv.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QCache>
......
......@@ -7,7 +7,7 @@
* Description : Import tool interface
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2013 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2014 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2012 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2012 by Islam Wazery <wazery at ubuntu dot com>
......@@ -101,6 +101,7 @@
#include "albumselectdialog.h"
#include "cameracontroller.h"
#include "camerafolderdialog.h"
#include "cameramessagebox.h"
#include "camerainfodialog.h"
#include "cameralist.h"
#include "cameranamehelper.h"
......@@ -163,9 +164,9 @@ ImportUI::ImportUI(QWidget* const parent, const QString& cameraTitle,
d->cameraTitle = (title.isEmpty()) ? cameraTitle : title;
setCaption(d->cameraTitle);
setupCameraController(model, port, path);
setupUserArea();
setInitialSorting();
setupCameraController(model, port, path);
setupActions();
setupStatusBar();
setupAccelerators();
......@@ -229,16 +230,17 @@ ImportUI* ImportUI::instance()
void ImportUI::setupUserArea()
{
ImportThumbnailModel* model = new ImportThumbnailModel(this);
ImportFilterModel* filterModel = new ImportFilterModel(this);
ImportThumbnailModel* const model = new ImportThumbnailModel(this);
ImportFilterModel* const filterModel = new ImportFilterModel(this);
filterModel->setSourceImportModel(model);
filterModel->sort(0); // an initial sorting is necessary
KHBox* const widget = new KHBox(this);
d->splitter = new SidebarSplitter(widget);
KVBox* const vbox = new KVBox(d->splitter);
d->view = new ImportView(this, model, filterModel, vbox);
d->view->importFilterModel()->setCameraThumbsController(d->camThumbsCtrl);
d->historyView = new DHistoryView(vbox);
d->rightSideBar = new ImagePropertiesSideBarCamGui(widget, d->splitter, KMultiTabBar::Right, true);
d->rightSideBar->setObjectName("CameraGui Sidebar Right");
......@@ -257,7 +259,7 @@ void ImportUI::setupUserArea()
d->errorWidget->setMessageType(KMessageWidget::Error);
d->errorWidget->setCloseButtonVisible(false);
d->errorWidget->hide();
// -------------------------------------------------------------------------
d->advBox = new RExpanderBox(d->rightSideBar);
......@@ -639,11 +641,11 @@ void ImportUI::setupActions()
d->connectAction = new KAction(KIcon("view-refresh"), i18nc("@action Connection failed, try again?", "Retry"), this);
connect(d->connectAction, SIGNAL(triggered()), d->controller, SLOT(slotConnect()));
createGUI(xmlFile());
d->showMenuBarAction->setChecked(!menuBar()->isHidden()); // NOTE: workaround for B.K.O #171080
// hide the unsupported actions
d->uploadAction->setVisible(d->controller->cameraUploadSupport());
......@@ -799,10 +801,11 @@ void ImportUI::setupCameraController(const QString& model, const QString& port,
connect(d->controller, SIGNAL(signalUploaded(CamItemInfo)),
this, SLOT(slotUploaded(CamItemInfo)));
// TODO make this nicer...
d->view->importFilterModel()->setCameraController(d->controller);
d->controller->start();
// Setup Thumbnails controller -------------------------------------------------------
d->camThumbsCtrl = new CameraThumbsCtrl(d->controller, this);
}
CameraController* ImportUI::getCameraController() const
......@@ -1884,7 +1887,7 @@ void ImportUI::deleteItems(bool onlySelected, bool onlyDownloaded)
QString infoMsg(i18nc("@info", "The items listed below are locked by camera (read-only). "
"These items will not be deleted. If you really want to delete these items, "
"please unlock them and try again."));
//CameraMessageBox::informationList(this, infoMsg, lockedList, i18n("Information"));
CameraMessageBox::informationList(d->camThumbsCtrl, this, infoMsg, lockedList, i18n("Information"));
}
if (folders.isEmpty())
......@@ -1900,30 +1903,31 @@ void ImportUI::deleteItems(bool onlySelected, bool onlyDownloaded)
"Are you sure?",
deleteList.count()));
// if (CameraMessageBox::warningContinueCancelList(this,
// warnMsg,
// deleteList,
// i18n("Warning"),
// KGuiItem(i18n("Delete")),
// KStandardGuiItem::cancel(),
// QString("DontAskAgainToDeleteItemsFromCamera"))
// == KMessageBox::Continue)
// {
QStringList::const_iterator itFolder = folders.constBegin();
QStringList::const_iterator itFile = files.constBegin();
if (CameraMessageBox::warningContinueCancelList(d->camThumbsCtrl,
this,
warnMsg,
deleteList,
i18n("Warning"),
KGuiItem(i18n("Delete")),
KStandardGuiItem::cancel(),
QString("DontAskAgainToDeleteItemsFromCamera"))
== KMessageBox::Continue)
{
QStringList::const_iterator itFolder = folders.constBegin();
QStringList::const_iterator itFile = files.constBegin();
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(deleteList.count());
d->statusProgressBar->progressBarMode(StatusProgressBar::ProgressBarMode);
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(deleteList.count());
d->statusProgressBar->progressBarMode(StatusProgressBar::ProgressBarMode);
for (; itFolder != folders.constEnd(); ++itFolder, ++itFile)
{
d->controller->deleteFile(*itFolder, *itFile);
// the currentlyDeleting list is used to prevent loading items which
// will immanently be deleted into the sidebar and wasting time
d->currentlyDeleting.append(*itFolder + *itFile);
for (; itFolder != folders.constEnd(); ++itFolder, ++itFile)
{
d->controller->deleteFile(*itFolder, *itFile);
// the currentlyDeleting list is used to prevent loading items which
// will immanently be deleted into the sidebar and wasting time
d->currentlyDeleting.append(*itFolder + *itFile);
}
}
// }
}
bool ImportUI::checkDiskSpace(PAlbum *pAlbum)
......
......@@ -7,7 +7,7 @@
* Description : Camera interface
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2013 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2014 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
......@@ -46,6 +46,7 @@
// Local includes
#include "camerathumbsctrl.h"
#include "cameracontroller.h"
#include "filtercombo.h"
#include "dzoombar.h"
......@@ -122,6 +123,7 @@ public:
helpMenu(0),
advBox(0),
splitter(0),
camThumbsCtrl(0),
controller(0),
historyUpdater(0),
view(0),
......@@ -205,7 +207,7 @@ public:
KToggleAction* iconViewAction;
KToggleAction* camItemPreviewAction;
KToggleAction* mapViewAction;
QActionGroup* cameraActions;
KUrl lastDestURL;
......@@ -216,6 +218,7 @@ public:
SidebarSplitter* splitter;
CameraThumbsCtrl* camThumbsCtrl;
CameraController* controller;
CameraHistoryUpdater* historyUpdater;
......
......@@ -342,10 +342,10 @@ void ImportFilterModel::setFilter(Digikam::Filter* filter)
invalidateFilter();
}
void ImportFilterModel::setCameraController(Digikam::CameraController* controller)
void ImportFilterModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl)
{
Q_D(ImportFilterModel);
d->importImageModel->setCameraController(controller);
d->importImageModel->setCameraThumbsController(thumbsCtrl);
}
void ImportFilterModel::setSendCamItemInfoSignals(bool sendSignals)
......
......@@ -143,8 +143,8 @@ public Q_SLOTS:
void setCategorizationMode(CamItemSortSettings::CategorizationMode mode);
void setSortRole(CamItemSortSettings::SortRole role);
void setSortOrder(CamItemSortSettings::SortOrder order);
void setFilter(Filter *);
void setCameraController(Digikam::CameraController* controller);
void setFilter(Filter*);
void setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl);
//TODO: Implement grouping in import tool.
//void setGroupOpen(qlonglong group, bool open);
......
......@@ -24,9 +24,17 @@
#include "importimagemodel.moc"
// Qt includes
#include "downloadhistory.h"
#include <QHash>
#include <KDebug>
// KDE includes
#include <kdebug.h>
// Local includes
#include "downloadhistory.h"
#include "cameracontroller.h"
namespace Digikam
{
......@@ -109,9 +117,10 @@ ImportImageModel::~ImportImageModel()
delete d;
}
void ImportImageModel::setCameraController(CameraController* const controller)
void ImportImageModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl)
{
d->controller = controller;
d->controller = thumbsCtrl->cameraController();
connect(d->controller, SIGNAL(signalFileList(CamItemInfoList)),
SLOT(addCamItemInfos(CamItemInfoList)));
......@@ -619,7 +628,7 @@ void ImportImageModel::publiciseInfos(const CamItemInfoList& infos)
for (int i = firstNewIndex; i <= lastNewIndex; ++i)
{
CamItemInfo& info = d->infos[i];
// TODO move this to a separate thread, see CameraHistoryUpdater
// TODO this is ugly, using different enums to point the similar status..
// TODO can we/do we want to differentiate at all between whether the status is unknown and not downloaded?
......
......@@ -35,7 +35,7 @@
// Local includes
#include "dragdropimplementations.h"
#include "cameracontroller.h"
#include "camerathumbsctrl.h"
#include "camiteminfo.h"
namespace Digikam
......@@ -72,7 +72,7 @@ public:
~ImportImageModel();
// Used to set the camera controller, and connect with it.
virtual void setCameraController(CameraController* const controller);
virtual void setCameraThumbsController(CameraThumbsCtrl* const controller);
/** If a cache is kept, lookup by file path is fast,
* without a cache it is O(n). Default is false. */
......
......@@ -34,6 +34,10 @@
#include <kdebug.h>
#include <kdeversion.h>
// Local includes
#include "cameracontroller.h"
namespace Digikam
{
......@@ -42,51 +46,39 @@ class ImportThumbnailModel::Private
public:
Private() :
controller(0),
kdeJob(0),
thumbsCtrl(0),
thumbSize(0),
lastGlobalThumbSize(0),
emitDataChanged(true)
{
}
CameraController* controller;
KUrl::List pendingItems;
QCache<KUrl, CachedItem> cache; // Camera info/thumb cache based on item url keys.
QList<CamItemInfo> kdeTodo;
QHash<KUrl, CamItemInfo> kdeJobHash;
KIO::PreviewJob* kdeJob;
CameraThumbsCtrl* thumbsCtrl;
ThumbnailSize thumbSize;
ThumbnailSize lastGlobalThumbSize;
bool emitDataChanged;
QReadWriteLock cacheLock;
ThumbnailSize thumbSize;
ThumbnailSize lastGlobalThumbSize;
bool emitDataChanged;
};
ImportThumbnailModel::ImportThumbnailModel(QObject* const parent)
: ImportImageModel(parent), d(new Private)
{
setKeepsFileUrlCache(true);
setCacheSize(200);
}
ImportThumbnailModel::~ImportThumbnailModel()
{
clearCache();
delete d;
}
void ImportThumbnailModel::setCameraController(CameraController* const controller)
void ImportThumbnailModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl)
{
d->controller = controller;
d->thumbsCtrl = thumbsCtrl;
connect(d->controller, SIGNAL(signalThumbInfo(QString,QString,CamItemInfo,QImage)),
this, SLOT(slotThumbInfoLoaded(QString,QString,CamItemInfo,QImage)));
connect(d->thumbsCtrl, SIGNAL(signalThumbInfoReady(const CamItemInfo&)),
this, SLOT(slotThumbInfoReady(const CamItemInfo&)));
connect(d->controller, SIGNAL(signalThumbInfoFailed(QString,QString,CamItemInfo)),
this, SLOT(slotThumbInfoFailed(QString,QString,CamItemInfo)));
ImportImageModel::setCameraController(controller);
ImportImageModel::setCameraThumbsController(d->thumbsCtrl);
}
ThumbnailSize ImportThumbnailModel::thumbnailSize() const
......@@ -101,7 +93,7 @@ void ImportThumbnailModel::setEmitDataChanged(bool emitSignal)
QVariant ImportThumbnailModel::data(const QModelIndex& index, int role) const
{
if (role == ThumbnailRole && d->controller && index.isValid())
if (role == ThumbnailRole && d->thumbsCtrl && index.isValid())
{
CamItemInfo info = camItemInfo(index);
QString path = info.url().prettyUrl();
......@@ -112,21 +104,15 @@ QVariant ImportThumbnailModel::data(const QModelIndex& index, int role) const
// at least gphoto2 doesn't really like it and will error a lot and slow down
if (info.isNull() || path.isEmpty() || !info.previewPossible)
{
return QVariant(d->controller->mimeTypeThumbnail(path, d->thumbSize.size()));
}
bool thumbChanged = false;
if(d->thumbSize != d->lastGlobalThumbSize)
{
thumbChanged = true;
return QVariant(d->thumbsCtrl->cameraController()->mimeTypeThumbnail(path, d->thumbSize.size()));
}
if (getThumbInfo(info, item, d->thumbSize, thumbChanged))
if (d->thumbsCtrl->getThumbInfo(info, item))
{
return QVariant(item.second);
return QVariant(item.second.scaled(d->thumbSize.size(), d->thumbSize.size(), Qt::KeepAspectRatio));
}
return QVariant(d->controller->mimeTypeThumbnail(path, d->thumbSize.size()));
return QVariant(d->thumbsCtrl->cameraController()->mimeTypeThumbnail(path, d->thumbSize.size()));
}
return ImportImageModel::data(index, role);
......@@ -163,56 +149,15 @@ bool ImportThumbnailModel::setData(const QModelIndex& index, const QVariant& val
return ImportImageModel::setData(index, value, role);
}
bool ImportThumbnailModel::getThumbInfo(const CamItemInfo& info, CachedItem& item, ThumbnailSize thumbSize, bool thumbChanged) const
{
// If thumbSize changed clear cache and reload thumbs for items.
if(thumbChanged)
{
clearCache();
d->pendingItems.clear();
}
// We look if items are not in cache.
if (hasItemFromCache(info.url()))
{
item = *retrieveItemFromCache(info.url());
return true;
// kDebug() << "Found in cache: " << info.url();
}
// We look if items are not in pending list.
else if (!d->pendingItems.contains(info.url()))
{
d->pendingItems << info.url();
// kDebug() << "Request thumbs from camera : " << info.url();
d->controller->getThumbsInfo(CamItemInfoList() << info, thumbSize.size());
// TODO set the thumb to indicate loading process?
}
item = CachedItem(info, d->controller->mimeTypeThumbnail(info.name, thumbSize.size()));
return false;
}
void ImportThumbnailModel::slotThumbInfoLoaded(const QString& folder, const QString& file, const CamItemInfo& info, const QImage& thumb)
void ImportThumbnailModel::slotThumbInfoReady(const CamItemInfo& info)
{
Q_UNUSED(folder);
QImage thumbnail = thumb;
if (thumb.isNull())
{
thumbnail = d->controller->mimeTypeThumbnail(file, d->thumbSize.size()).toImage();
}
putItemToCache(info.url(), info, QPixmap::fromImage(thumbnail));
d->pendingItems.removeAll(info.url());
CachedItem item;
d->thumbsCtrl->getThumbInfo(info, item);
// In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all.
foreach(const QModelIndex& index, indexesForUrl(info.url()))
{
if (thumb.isNull())
if (item.second.isNull())
{
emit thumbnailFailed(index, d->thumbSize.size());
}
......@@ -228,197 +173,4 @@ void ImportThumbnailModel::slotThumbInfoLoaded(const QString& folder, const QStr
}
}
void ImportThumbnailModel::slotThumbInfoFailed(const QString& folder, const QString& file, const CamItemInfo& info)
{
Q_UNUSED(folder);
// TODO share the completely same images? f.ex. in case of error message, it's not worth storing a separate entry for it..
if (d->controller->cameraDriverType() == DKCamera::UMSDriver)
{
QPixmap pix = d->controller->mimeTypeThumbnail(file, d->thumbSize.size());
putItemToCache(info.url(), info, pix);
loadWithKDE(info);
}
else
{
QPixmap pix = d->controller->mimeTypeThumbnail(file, d->thumbSize.size());
putItemToCache(info.url(), info, pix);
d->pendingItems.removeAll(info.url());
// In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all.
foreach(const QModelIndex& index, indexesForUrl(info.url()))
{
if (pix.isNull())
{
emit thumbnailFailed(index, d->thumbSize.size());
}
else
{
emit thumbnailAvailable(index, d->thumbSize.size());
if (d->emitDataChanged)
{
emit dataChanged(index, index);
}
}
}
}
}
void ImportThumbnailModel::loadWithKDE(const CamItemInfo& info)
{
d->kdeTodo << info;
startKdePreviewJob();
}
void ImportThumbnailModel::startKdePreviewJob()
{
if (d->kdeJob || d->kdeTodo.isEmpty())
{
return;
}
d->kdeJobHash.clear();
KUrl::List list;
foreach(const CamItemInfo& info, d->kdeTodo)
{
KUrl url = info.url();
list << url;
d->kdeJobHash[url] = info;
}
d->kdeTodo.clear();
#if KDE_IS_VERSION(4,7,0)
KFileItemList items;
foreach (const KUrl& url, list)
{
if (url.isValid())
items.append(KFileItem(KFileItem::Unknown, KFileItem::Unknown, url, true));
}
d->kdeJob = KIO::filePreview(items, QSize(ThumbnailSize::maxThumbsSize(), ThumbnailSize::maxThumbsSize()));
#else
d->kdeJob = KIO::filePreview(list, ThumbnailSize::maxThumbsSize());
#endif
connect(d->kdeJob, SIGNAL(gotPreview(KFileItem,QPixmap)),
this, SLOT(slotGotKDEPreview(KFileItem,QPixmap)));
connect(d->kdeJob, SIGNAL(failed(KFileItem)),
this, SLOT(slotFailedKDEPreview(KFileItem)));
connect(d->kdeJob, SIGNAL(finished(KJob*)),
this, SLOT(slotKdePreviewFinished(KJob*)));
}
void ImportThumbnailModel::slotGotKDEPreview(const KFileItem& item, const QPixmap& pix)
{
procressKDEPreview(item, pix);
}
void ImportThumbnailModel::slotFailedKDEPreview(const KFileItem& item)
{
procressKDEPreview(item, QPixmap());
}
void ImportThumbnailModel::procressKDEPreview(const KFileItem& item, const QPixmap& pix)
{
CamItemInfo info = d->kdeJobHash.value(item.url());
QUrl url = info.url();
if (info.isNull())
{
return;
}
QString file = item.url().fileName();
//QString folder = item.url().toLocalFile().remove(QString("/") + file);
QPixmap thumb;
if (pix.isNull())
{
// This call must be run outside Camera Controller thread.
thumb = d->controller->mimeTypeThumbnail(file, d->thumbSize.size());
kDebug() << "Failed thumb from KDE Preview : " << item.url();
}
else
{
thumb = pix;
kDebug() << "Got thumb from KDE Preview : "