diff --git a/ImageManager/ThumbnailCache.cpp b/ImageManager/ThumbnailCache.cpp
index 5597114b..74cc5a29 100644
--- a/ImageManager/ThumbnailCache.cpp
+++ b/ImageManager/ThumbnailCache.cpp
@@ -1,472 +1,465 @@
/* Copyright (C) 2003-2020 The KPhotoAlbum Development Team
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 of the License, 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.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ThumbnailCache.h"
#include "Logging.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace
{
// We split the thumbnails into chunks to avoid a huge file changing over and over again, with a bad hit for backups
constexpr int MAX_FILE_SIZE = 32 * 1024 * 1024;
constexpr int THUMBNAIL_FILE_VERSION = 4;
// We map some thumbnail files into memory and manage them in a least-recently-used fashion
constexpr size_t LRU_SIZE = 2;
constexpr int THUMBNAIL_CACHE_SAVE_INTERNAL_MS = (5 * 1000);
-
-const QString THUMBNAIL_DIR = QString::fromLatin1(".thumbnails/");
}
namespace ImageManager
{
/**
* The ThumbnailMapping wraps the memory-mapped data of a QFile.
* Upon initialization with a file name, the corresponding file is opened
* and its contents mapped into memory (as a QByteArray).
*
* Deleting the ThumbnailMapping unmaps the memory and closes the file.
*/
class ThumbnailMapping
{
public:
ThumbnailMapping(const QString &filename)
: file(filename)
, map(nullptr)
{
if (!file.open(QIODevice::ReadOnly))
qCWarning(ImageManagerLog, "Failed to open thumbnail file");
uchar *data = file.map(0, file.size());
if (!data || QFile::NoError != file.error()) {
qCWarning(ImageManagerLog, "Failed to map thumbnail file");
} else {
map = QByteArray::fromRawData(reinterpret_cast(data), file.size());
}
}
bool isValid()
{
return !map.isEmpty();
}
// we need to keep the file around to keep the data mapped:
QFile file;
QByteArray map;
};
+
+QString defaultThumbnailDirectory()
+{
+ return QString::fromLatin1(".thumbnails/");
+}
}
ImageManager::ThumbnailCache *ImageManager::ThumbnailCache::s_instance = nullptr;
ImageManager::ThumbnailCache::ThumbnailCache(const QString &baseDirectory)
: m_baseDir(baseDirectory)
, m_currentFile(0)
, m_currentOffset(0)
, m_timer(new QTimer)
, m_needsFullSave(true)
, m_isDirty(false)
, m_memcache(new QCache(LRU_SIZE))
, m_currentWriter(nullptr)
{
const QString dir = thumbnailPath(QString());
if (!QFile::exists(dir))
QDir().mkpath(dir);
load();
connect(this, &ImageManager::ThumbnailCache::doSave, this, &ImageManager::ThumbnailCache::saveImpl);
connect(m_timer, &QTimer::timeout, this, &ImageManager::ThumbnailCache::saveImpl);
m_timer->setInterval(THUMBNAIL_CACHE_SAVE_INTERNAL_MS);
m_timer->setSingleShot(true);
m_timer->start(THUMBNAIL_CACHE_SAVE_INTERNAL_MS);
}
ImageManager::ThumbnailCache::~ThumbnailCache()
{
m_needsFullSave = true;
saveInternal();
delete m_memcache;
delete m_timer;
if (m_currentWriter)
delete m_currentWriter;
}
void ImageManager::ThumbnailCache::insert(const DB::FileName &name, const QImage &image)
{
QMutexLocker thumbnailLocker(&m_thumbnailWriterLock);
if (!m_currentWriter) {
m_currentWriter = new QFile(fileNameForIndex(m_currentFile));
if (!m_currentWriter->open(QIODevice::ReadWrite)) {
qCWarning(ImageManagerLog, "Failed to open thumbnail file for inserting");
return;
}
}
if (!m_currentWriter->seek(m_currentOffset)) {
qCWarning(ImageManagerLog, "Failed to seek in thumbnail file");
return;
}
QMutexLocker dataLocker(&m_dataLock);
// purge in-memory cache for the current file:
m_memcache->remove(m_currentFile);
QByteArray data;
QBuffer buffer(&data);
bool OK = buffer.open(QIODevice::WriteOnly);
Q_ASSERT(OK);
Q_UNUSED(OK);
OK = image.save(&buffer, "JPG");
Q_ASSERT(OK);
const int size = data.size();
if (!(m_currentWriter->write(data.data(), size) == size && m_currentWriter->flush())) {
qCWarning(ImageManagerLog, "Failed to write image data to thumbnail file");
return;
}
if (m_currentOffset + size > MAX_FILE_SIZE) {
delete m_currentWriter;
m_currentWriter = nullptr;
}
thumbnailLocker.unlock();
if (m_hash.contains(name)) {
CacheFileInfo info = m_hash[name];
if (info.fileIndex == m_currentFile && info.offset == m_currentOffset && info.size == size) {
qCDebug(ImageManagerLog) << "Found duplicate thumbnail " << name.relative() << "but no change in information";
dataLocker.unlock();
return;
} else {
// File has moved; incremental save does no good.
qCDebug(ImageManagerLog) << "Found duplicate thumbnail " << name.relative() << " at new location, need full save! ";
m_saveLock.lock();
m_needsFullSave = true;
m_saveLock.unlock();
}
}
m_hash.insert(name, CacheFileInfo(m_currentFile, m_currentOffset, size));
m_isDirty = true;
m_unsavedHash.insert(name, CacheFileInfo(m_currentFile, m_currentOffset, size));
// Update offset
m_currentOffset += size;
if (m_currentOffset > MAX_FILE_SIZE) {
m_currentFile++;
m_currentOffset = 0;
}
int unsaved = m_unsavedHash.count();
dataLocker.unlock();
// Thumbnail building is a lot faster now. Even on an HDD this corresponds to less
// than 1 minute of work.
//
// We need to call the internal version that does not interact with the timer.
// We can't simply signal from here because if we're in the middle of loading new
// images the signal won't get invoked until we return to the main application loop.
if (unsaved >= 100) {
saveInternal();
}
}
QString ImageManager::ThumbnailCache::fileNameForIndex(int index) const
{
return thumbnailPath(QString::fromLatin1("thumb-") + QString::number(index));
}
QPixmap ImageManager::ThumbnailCache::lookup(const DB::FileName &name) const
{
auto array = lookupRawData(name);
if (array.isNull())
return QPixmap();
QBuffer buffer(&array);
buffer.open(QIODevice::ReadOnly);
QImage image;
image.load(&buffer, "JPG");
// Notice the above image is sharing the bits with the file, so I can't just return it as it then will be invalid when the file goes out of scope.
// PENDING(blackie) Is that still true?
return QPixmap::fromImage(image);
}
QByteArray ImageManager::ThumbnailCache::lookupRawData(const DB::FileName &name) const
{
m_dataLock.lock();
CacheFileInfo info = m_hash[name];
m_dataLock.unlock();
ThumbnailMapping *t = m_memcache->object(info.fileIndex);
if (!t || !t->isValid()) {
t = new ThumbnailMapping(fileNameForIndex(info.fileIndex));
if (!t->isValid()) {
qCWarning(ImageManagerLog, "Failed to map thumbnail file");
return QByteArray();
}
m_memcache->insert(info.fileIndex, t);
}
QByteArray array(t->map.mid(info.offset, info.size));
return array;
}
void ImageManager::ThumbnailCache::saveFull() const
{
// First ensure that any dirty thumbnails are written to disk
m_thumbnailWriterLock.lock();
if (m_currentWriter) {
delete m_currentWriter;
m_currentWriter = nullptr;
}
m_thumbnailWriterLock.unlock();
QMutexLocker dataLocker(&m_dataLock);
if (!m_isDirty) {
return;
}
QTemporaryFile file;
if (!file.open()) {
qCWarning(ImageManagerLog, "Failed to create temporary file");
return;
}
QHash tempHash = m_hash;
m_unsavedHash.clear();
m_needsFullSave = false;
// Clear the dirty flag early so that we can allow further work to proceed.
// If the save fails, we'll set the dirty flag again.
m_isDirty = false;
dataLocker.unlock();
QDataStream stream(&file);
stream << THUMBNAIL_FILE_VERSION
<< m_currentFile
<< m_currentOffset
<< m_hash.count();
for (auto it = tempHash.constBegin(); it != tempHash.constEnd(); ++it) {
const CacheFileInfo &cacheInfo = it.value();
stream << it.key().relative()
<< cacheInfo.fileIndex
<< cacheInfo.offset
<< cacheInfo.size;
}
file.close();
const QString realFileName = thumbnailPath(QString::fromLatin1("thumbnailindex"));
QFile::remove(realFileName);
if (!file.copy(realFileName)) {
qCWarning(ImageManagerLog, "Failed to copy the temporary file %s to %s", qPrintable(file.fileName()), qPrintable(realFileName));
dataLocker.relock();
m_isDirty = true;
m_needsFullSave = true;
} else {
QFile realFile(realFileName);
realFile.open(QIODevice::ReadOnly);
realFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther);
realFile.close();
}
}
// Incremental save does *not* clear the dirty flag. We always want to do a full
// save eventually.
void ImageManager::ThumbnailCache::saveIncremental() const
{
m_thumbnailWriterLock.lock();
if (m_currentWriter) {
delete m_currentWriter;
m_currentWriter = nullptr;
}
m_thumbnailWriterLock.unlock();
QMutexLocker dataLocker(&m_dataLock);
if (m_unsavedHash.count() == 0) {
return;
}
QHash tempUnsavedHash = m_unsavedHash;
m_unsavedHash.clear();
m_isDirty = true;
const QString realFileName = thumbnailPath(QString::fromLatin1("thumbnailindex"));
QFile file(realFileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) {
qCWarning(ImageManagerLog, "Failed to open thumbnail cache for appending");
m_needsFullSave = true;
return;
}
QDataStream stream(&file);
for (auto it = tempUnsavedHash.constBegin(); it != tempUnsavedHash.constEnd(); ++it) {
const CacheFileInfo &cacheInfo = it.value();
stream << it.key().relative()
<< cacheInfo.fileIndex
<< cacheInfo.offset
<< cacheInfo.size;
}
file.close();
}
void ImageManager::ThumbnailCache::saveInternal() const
{
m_saveLock.lock();
const QString realFileName = thumbnailPath(QString::fromLatin1("thumbnailindex"));
// If something has asked for a full save, do it!
if (m_needsFullSave || !QFile(realFileName).exists()) {
saveFull();
} else {
saveIncremental();
}
m_saveLock.unlock();
}
void ImageManager::ThumbnailCache::saveImpl() const
{
m_timer->stop();
saveInternal();
m_timer->setInterval(THUMBNAIL_CACHE_SAVE_INTERNAL_MS);
m_timer->setSingleShot(true);
m_timer->start(THUMBNAIL_CACHE_SAVE_INTERNAL_MS);
}
void ImageManager::ThumbnailCache::save() const
{
m_saveLock.lock();
m_needsFullSave = true;
m_saveLock.unlock();
emit doSave();
}
void ImageManager::ThumbnailCache::load()
{
QFile file(thumbnailPath(QString::fromLatin1("thumbnailindex")));
if (!file.exists())
return;
QElapsedTimer timer;
timer.start();
file.open(QIODevice::ReadOnly);
QDataStream stream(&file);
int version;
stream >> version;
if (version != THUMBNAIL_FILE_VERSION)
return; //Discard cache
// We can't allow anything to modify the structure while we're doing this.
QMutexLocker dataLocker(&m_dataLock);
int count = 0;
stream >> m_currentFile
>> m_currentOffset
>> count;
while (!stream.atEnd()) {
QString name;
int fileIndex;
int offset;
int size;
stream >> name
>> fileIndex
>> offset
>> size;
m_hash.insert(DB::FileName::fromRelativePath(name), CacheFileInfo(fileIndex, offset, size));
if (fileIndex > m_currentFile) {
m_currentFile = fileIndex;
m_currentOffset = offset + size;
} else if (fileIndex == m_currentFile && offset + size > m_currentOffset) {
m_currentOffset = offset + size;
}
if (m_currentOffset > MAX_FILE_SIZE) {
m_currentFile++;
m_currentOffset = 0;
}
count++;
}
qCDebug(TimingLog) << "Loaded thumbnails in " << timer.elapsed() / 1000.0 << " seconds";
}
bool ImageManager::ThumbnailCache::contains(const DB::FileName &name) const
{
QMutexLocker dataLocker(&m_dataLock);
bool answer = m_hash.contains(name);
return answer;
}
QString ImageManager::ThumbnailCache::thumbnailPath(const QString &file) const
{
return m_baseDir + file;
}
ImageManager::ThumbnailCache *ImageManager::ThumbnailCache::instance()
{
- if (!s_instance) {
- const QString thumbnailDirectory = QDir(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath(THUMBNAIL_DIR);
- s_instance = new ThumbnailCache { thumbnailDirectory };
- }
return s_instance;
}
-void ImageManager::ThumbnailCache::deleteInstance()
-{
- delete s_instance;
- s_instance = nullptr;
-}
-
void ImageManager::ThumbnailCache::flush()
{
QMutexLocker dataLocker(&m_dataLock);
for (int i = 0; i <= m_currentFile; ++i)
QFile::remove(fileNameForIndex(i));
m_currentFile = 0;
m_currentOffset = 0;
m_isDirty = true;
m_hash.clear();
m_unsavedHash.clear();
m_memcache->clear();
dataLocker.unlock();
save();
}
void ImageManager::ThumbnailCache::removeThumbnail(const DB::FileName &fileName)
{
QMutexLocker dataLocker(&m_dataLock);
m_isDirty = true;
m_hash.remove(fileName);
dataLocker.unlock();
save();
}
void ImageManager::ThumbnailCache::removeThumbnails(const DB::FileNameList &files)
{
QMutexLocker dataLocker(&m_dataLock);
m_isDirty = true;
for (const DB::FileName &fileName : files) {
m_hash.remove(fileName);
}
dataLocker.unlock();
save();
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/ImageManager/ThumbnailCache.h b/ImageManager/ThumbnailCache.h
index 1c261571..d8572a8b 100644
--- a/ImageManager/ThumbnailCache.h
+++ b/ImageManager/ThumbnailCache.h
@@ -1,170 +1,175 @@
/* Copyright (C) 2003-2020 Jesper K. Pedersen
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 of the License, 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.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef THUMBNAILCACHE_H
#define THUMBNAILCACHE_H
#include "CacheFileInfo.h"
#include
#include
#include
#include
#include
template
class QCache;
namespace ImageManager
{
class ThumbnailMapping;
/**
* @brief The ThumbnailCache implements thumbnail storage optimized for speed.
*
* ## On-disk storage
* The problem with the FreeDesktop.org thumbnail storage is that there is one file per image.
* This means that showing a full page of thumbnails, containing dozens of images requires many
* file operations.
*
* Our storage scheme introduces a single index file (\c thumbnailindex) that contains
* the index of known thumbnails and their location in the thumbnail storage files (\c thumb-N).
* The thumbnail storage files contain raw JPEG thumbnail data.
*
* This layout creates far less files on the filesystem,
* the files can be memory-mapped, and because similar sets of images are often
* shown together, data locality is used to our advantage.
*
* ## Caveats
* Note that thumbnails are only ever added, never deleted from the thumbnail files.
* Old images remain in the thumbnail files - they are just removed from the index file.
*
* ## Further reading
* - https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html
*/
class ThumbnailCache : public QObject
{
Q_OBJECT
public:
+ // FIXME: until we get rid of the singleton, just allow direct access to this:
+ static ThumbnailCache *s_instance;
static ThumbnailCache *instance();
- static void deleteInstance();
/**
* @brief ThumbnailCache
* Provide access to a KPhotoAlbum-style thumbnail cache in the given directory.
* @param baseDirectory the directory in which the \c thumbnailindex file resides.
*/
ThumbnailCache(const QString &baseDirectory);
+ ~ThumbnailCache() override;
/**
* @brief Insert a thumbnail for the given file.
* @param name the image file name
* @param image the thumbnail data
*/
void insert(const DB::FileName &name, const QImage &image);
/**
* @brief lookup and return the thumbnail for the given file.
* @param name the image file name
* @return a QPixmap containing the thumbnail, or a null QPixmap if no thumbnail was found.
*/
QPixmap lookup(const DB::FileName &name) const;
/**
* @brief lookupRawData
* @param name the image file name
* @return the raw JPEG thumbnail data or a null QByteArray.
*/
QByteArray lookupRawData(const DB::FileName &name) const;
/**
* @brief Check if the ThumbnailCache contains a thumbnail for the given file.
* @param name the image file name
* @return \c true if the thumbnail exists, \c false otherwise.
*/
bool contains(const DB::FileName &name) const;
/**
* @brief "Forget" the thumbnail for an image.
* @param name the image file name
*/
void removeThumbnail(const DB::FileName &name);
/**
* @brief "Forget" the thumbnails for the given images.
* Like removeThumbnail(), but for a list of images
* @param names a list of image file names
*/
void removeThumbnails(const DB::FileNameList &names);
public slots:
/**
* @brief Save the thumbnail cache to disk.
*/
void save() const;
/**
* @brief Invalidate the ThumbnailCache and remove the thumbnail files and index.
*/
void flush();
signals:
/**
* @brief doSave is emitted when save() is called.
* This signal is more or less an internal signal.
*/
void doSave() const;
private:
/**
* @brief load the \c thumbnailindex file if possible.
* This function populates the thumbnail hash, but does not
* load any actual thumbnail data.
* If the file does not exist, or if it is not compatible,
* then it is discarded.
*/
void load();
- ~ThumbnailCache() override;
QString fileNameForIndex(int index) const;
QString thumbnailPath(const QString &fileName) const;
- static ThumbnailCache *s_instance;
const QString m_baseDir;
QHash m_hash;
mutable QHash m_unsavedHash;
/* Protects accesses to the data (hash and unsaved hash) */
mutable QMutex m_dataLock;
/* Prevents multiple saves from happening simultaneously */
mutable QMutex m_saveLock;
/* Protects writing thumbnails to disk */
mutable QMutex m_thumbnailWriterLock;
int m_currentFile;
int m_currentOffset;
mutable QTimer *m_timer;
mutable bool m_needsFullSave;
mutable bool m_isDirty;
void saveFull() const;
void saveIncremental() const;
void saveInternal() const;
void saveImpl() const;
/**
* Holds an in-memory cache of thumbnail files.
*/
mutable QCache *m_memcache;
mutable QFile *m_currentWriter;
};
+/**
+ * @brief defaultThumbnailDirectory
+ * @return the default thumbnail (sub-)directory name, e.g. ".thumbnails"
+ */
+QString defaultThumbnailDirectory();
}
#endif /* THUMBNAILCACHE_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/Window.cpp b/MainWindow/Window.cpp
index 4025fd21..4aa44c9b 100644
--- a/MainWindow/Window.cpp
+++ b/MainWindow/Window.cpp
@@ -1,1827 +1,1837 @@
/* Copyright (C) 2003-2020 The KPhotoAlbum Development Team
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 of the License, 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.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "Window.h"
#include
#include
#ifdef HAVE_STDLIB_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for #if KIO_VERSION...
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef KF5Purpose_FOUND
#include
#endif
#include "AutoStackImages.h"
#include "BreadcrumbViewer.h"
#include "CopyPopup.h"
#include "DeleteDialog.h"
#include "DirtyIndicator.h"
#include "DuplicateMerger/DuplicateMerger.h"
#include "ExternalPopup.h"
#include "FeatureDialog.h"
#include "ImageCounter.h"
#include "InvalidDateFinder.h"
#include "Logging.h"
#include "Options.h"
#include "SearchBar.h"
#include "SplashScreen.h"
#include "StatisticsDialog.h"
#include "StatusBar.h"
#include "TokenEditor.h"
#include "UpdateVideoThumbnail.h"
#include "WelcomeDialog.h"
#ifdef HAVE_MARBLE
#include
"),
i18n("Feature has not been configured"));
}
}
void MainWindow::Window::setupStatusBar()
{
m_statusBar = new MainWindow::StatusBar;
setStatusBar(m_statusBar);
setLocked(Settings::SettingsData::instance()->locked(), true, false);
connect(m_statusBar, &StatusBar::thumbnailSettingsRequested, [this]() { this->slotOptions(); m_settingsDialog->activatePage(Settings::SettingsPage::ThumbnailsPage); });
}
void MainWindow::Window::slotRecreateExifDB()
{
Exif::Database::instance()->recreate();
}
void MainWindow::Window::useNextVideoThumbnail()
{
UpdateVideoThumbnail::useNext(selected());
}
void MainWindow::Window::usePreviousVideoThumbnail()
{
UpdateVideoThumbnail::usePrevious(selected());
}
void MainWindow::Window::mergeDuplicates()
{
DuplicateMerger *merger = new DuplicateMerger;
merger->show();
}
void MainWindow::Window::slotThumbnailSizeChanged()
{
QString thumbnailSizeMsg = i18nc("@info:status",
//xgettext:no-c-format
"Thumbnail width: %1px (storage size: %2px)",
Settings::SettingsData::instance()->actualThumbnailSize(),
Settings::SettingsData::instance()->thumbnailSize());
m_statusBar->showMessage(thumbnailSizeMsg, 4000);
}
void MainWindow::Window::createSearchBar()
{
// Set up the search tool bar
SearchBar *searchBar = new SearchBar(this);
searchBar->setLineEditEnabled(false);
searchBar->setObjectName(QString::fromUtf8("searchBar"));
connect(searchBar, &SearchBar::textChanged, m_browser, &Browser::BrowserWidget::slotLimitToMatch);
connect(searchBar, &SearchBar::returnPressed, m_browser, &Browser::BrowserWidget::slotInvokeSeleted);
connect(searchBar, &SearchBar::keyPressed, m_browser, &Browser::BrowserWidget::scrollKeyPressed);
connect(m_browser, &Browser::BrowserWidget::viewChanged, searchBar, &SearchBar::reset);
connect(m_browser, &Browser::BrowserWidget::isSearchable, searchBar, &SearchBar::setLineEditEnabled);
m_filterWidget = m_thumbnailView->createFilterWidget(this);
addToolBar(m_filterWidget);
m_filterWidget->setObjectName(QString::fromUtf8("filterBar"));
connect(m_browser, &Browser::BrowserWidget::viewChanged, ThumbnailView::ThumbnailFacade::instance(), &ThumbnailView::ThumbnailFacade::clearFilter);
connect(m_browser, &Browser::BrowserWidget::isFilterable, m_filterWidget, &ThumbnailView::FilterWidget::setEnabled);
}
void MainWindow::Window::executeStartupActions()
{
new ImageManager::ThumbnailBuilder(m_statusBar, this);
if (!Settings::SettingsData::instance()->incrementalThumbnails())
ImageManager::ThumbnailBuilder::instance()->buildMissing();
connect(Settings::SettingsData::instance(), &Settings::SettingsData::thumbnailSizeChanged,
this, &Window::slotBuildThumbnailsIfWanted);
if (!FeatureDialog::hasVideoThumbnailer()) {
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutLengthInfo);
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutVideoThumbnailsJob);
}
}
void MainWindow::Window::checkIfVideoThumbnailerIsInstalled()
{
if (Options::the()->demoMode())
return;
if (!FeatureDialog::hasVideoThumbnailer()) {
KMessageBox::information(this,
i18n("Unable to find ffmpeg on the system.
"
"Without it, KPhotoAlbum will not be able to display video thumbnails and video lengths. "
"Please install the ffmpeg package
"),
i18n("Video thumbnails are not available"), QString::fromLatin1("VideoThumbnailerNotInstalled"));
}
}
bool MainWindow::Window::anyVideosSelected() const
{
const auto selectedFiles = selected();
for (const DB::FileName &fileName : selectedFiles) {
if (Utilities::isVideo(fileName))
return true;
}
return false;
}
void MainWindow::Window::setHistogramVisibilty(bool visible) const
{
if (visible) {
m_dateBar->show();
m_dateBarLine->show();
} else {
m_dateBar->hide();
m_dateBarLine->hide();
}
}
void MainWindow::Window::slotImageRotated(const DB::FileName &fileName)
{
// An image has been rotated by the annotation dialog or the viewer.
// We have to reload the respective thumbnail to get it in the right angle
ImageManager::ThumbnailCache::instance()->removeThumbnail(fileName);
}
bool MainWindow::Window::dbIsDirty() const
{
return m_statusBar->mp_dirtyIndicator->isSaveDirty();
}
#ifdef HAVE_MARBLE
void MainWindow::Window::showPositionBrowser()
{
auto positionBrowser = positionBrowserWidget();
m_stack->setCurrentWidget(positionBrowser);
updateStates(false);
}
Map::MapView *MainWindow::Window::positionBrowserWidget()
{
if (!m_positionBrowser) {
m_positionBrowser = new Map::MapView(m_stack, Map::UsageType::InlineMapView);
m_stack->addWidget(m_positionBrowser);
}
return m_positionBrowser;
}
#endif
UserFeedback MainWindow::Window::askWarningContinueCancel(const QString &msg, const QString &title, const QString &dialogId)
{
auto answer = KMessageBox::warningContinueCancel(this, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dialogId);
return (answer == KMessageBox::Continue) ? UserFeedback::Confirm : UserFeedback::Deny;
}
UserFeedback MainWindow::Window::askQuestionYesNo(const QString &msg, const QString &title, const QString &dialogId)
{
auto answer = KMessageBox::questionYesNo(this, msg, title, KStandardGuiItem::yes(), KStandardGuiItem::no(), dialogId);
return (answer == KMessageBox::Yes) ? UserFeedback::Confirm : UserFeedback::Deny;
}
void MainWindow::Window::showInformation(const QString &msg, const QString &title, const QString &dialogId)
{
KMessageBox::information(this, msg, title, dialogId);
}
void MainWindow::Window::showSorry(const QString &msg, const QString &title, const QString &)
{
KMessageBox::sorry(this, msg, title);
}
void MainWindow::Window::showError(const QString &msg, const QString &title, const QString &)
{
KMessageBox::error(this, msg, title);
}
bool MainWindow::Window::isDialogDisabled(const QString &dialogId)
{
// Note(jzarl): there are different methods for different kinds of dialogs.
// However, all these methods share exactly the same code in KMessageBox.
// If that ever changes, we can still update our implementation - until then I won't just copy a stupid API...
return !KMessageBox::shouldBeShownContinue(dialogId);
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/Window.h b/MainWindow/Window.h
index f8f861e2..74de5a2b 100644
--- a/MainWindow/Window.h
+++ b/MainWindow/Window.h
@@ -1,299 +1,302 @@
/* Copyright (C) 2003-2020 The KPhotoAlbum Development Team
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 of the License, 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.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef MAINWINDOW_WINDOW_H
#define MAINWINDOW_WINDOW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class QAction;
class QCloseEvent;
class QContextMenuEvent;
class QFrame;
class QLabel;
class QMoveEvent;
class QResizeEvent;
class QStackedWidget;
class QTimer;
class KActionMenu;
class KTipDialog;
class KToggleAction;
#ifdef HAVE_MARBLE
namespace Map
{
class MapView;
}
#endif
namespace AnnotationDialog
{
class Dialog;
}
namespace Browser
{
class BrowserWidget;
class BreadcrumbList;
}
namespace DateBar
{
class DateBarWidget;
}
namespace DB
{
class ImageInfoList;
}
namespace HTMLGenerator
{
class HTMLDialog;
}
-namespace Plugins
+namespace ImageManager
{
-class Interface;
+class ThumbnailCache;
}
namespace Settings
{
class SettingsDialog;
}
namespace ThumbnailView
{
class ThumbnailFacade;
class FilterWidget;
}
class BreadcrumbViewer;
namespace MainWindow
{
class DeleteDialog;
class StatusBar;
class TokenEditor;
class Window : public KXmlGuiWindow, public DB::UIDelegate
{
Q_OBJECT
public:
explicit Window(QWidget *parent);
~Window() override;
static void configureImages(const DB::ImageInfoList &list, bool oneAtATime);
static Window *theMainWindow();
+
+ ImageManager::ThumbnailCache *thumbnailCache() const;
+
DB::FileNameList selected(ThumbnailView::SelectionMode mode = ThumbnailView::ExpandCollapsedStacks) const;
DB::ImageSearchInfo currentContext();
QString currentBrowseCategory() const;
void setStackHead(const DB::FileName &image);
void setHistogramVisibilty(bool visible) const;
bool dbIsDirty() const;
#ifdef HAVE_MARBLE
void showPositionBrowser();
Map::MapView *positionBrowserWidget();
#endif
// implement UI delegate interface
// Note(jzarl): we just could create a UIDelegate class that takes a QWidget,
// implementing the same messageParent approach that we took before.
// For now, I don't see anything wrong with directly implementing the interface instead.
// I may change my mind later and I'm ready to convinced of the errors of my way, though...
DB::UserFeedback askWarningContinueCancel(const QString &msg, const QString &title, const QString &dialogId) override;
DB::UserFeedback askQuestionYesNo(const QString &msg, const QString &title, const QString &dialogId) override;
void showInformation(const QString &msg, const QString &title, const QString &dialogId) override;
void showSorry(const QString &msg, const QString &title, const QString &) override;
void showError(const QString &msg, const QString &title, const QString &) override;
bool isDialogDisabled(const QString &dialogId) override;
public slots:
void showThumbNails(const DB::FileNameList &items);
void reloadThumbnails(ThumbnailView::SelectionUpdateMethod method = ThumbnailView::MaintainSelection);
void runDemo();
void slotImageRotated(const DB::FileName &fileName);
void slotSave();
protected slots:
void showThumbNails();
bool slotExit();
void slotOptions();
void slotConfigureAllImages();
void slotConfigureImagesOneAtATime();
void slotCreateImageStack();
void slotUnStackImages();
void slotSetStackHead();
void slotCopySelectedURLs();
void slotPasteInformation();
void slotDeleteSelected();
void slotReReadExifInfo();
void slotAutoStackImages();
void slotSearch();
// FIXME(jzarl): improve this function signature:
void slotView(bool reuse, bool slideShow = false, bool random = false);
void slotView();
void slotViewNewWindow();
void slotSortByDateAndTime();
void slotSortAllByDateAndTime();
void slotLimitToSelected();
void slotExportToHTML();
void slotAutoSave();
void showBrowser();
void slotOptionGroupChanged();
void showTipOfDay();
void lockToDefaultScope();
void setDefaultScopePositive();
void setDefaultScopeNegative();
void unlockFromDefaultScope();
void configureShortcuts();
void slotSetFileName(const DB::FileName &);
void updateContextMenuFromSelectionSize(int selectionSize);
void slotUpdateViewMenu(DB::Category::ViewType);
void slotShowNotOnDisk();
void slotBuildThumbnails();
void slotBuildThumbnailsIfWanted();
void slotRunSlideShow();
void slotRunRandomizedSlideShow();
void slotImport();
void slotExport();
void delayedInit();
void slotReenableMessages();
void slotImagesChanged(const QList &);
void slotRemoveTokens();
void slotShowListOfFiles();
void updateDateBar(const Browser::BreadcrumbList &);
void updateDateBar();
void slotShowImagesWithInvalidDate();
void slotShowImagesWithChangedMD5Sum();
void showDateBarTip(const QString &);
void slotJumpToContext();
void setDateRange(const DB::ImageDate &);
void clearDateRange();
void startAutoSaveTimer();
void slotRecalcCheckSums();
void slotShowExifInfo();
void showFeatures();
void showImage(const DB::FileName &fileName);
void slotOrderIncr();
void slotOrderDecr();
void slotRotateSelectedLeft();
void slotRotateSelectedRight();
void rotateSelected(int angle);
void showVideos();
void slotStatistics();
void slotRecreateExifDB();
void useNextVideoThumbnail();
void usePreviousVideoThumbnail();
void mergeDuplicates();
void slotThumbnailSizeChanged();
void slotMarkUntagged();
protected:
void configureImages(bool oneAtATime);
QString welcome();
bool event(QEvent *event) override;
void closeEvent(QCloseEvent *e) override;
void resizeEvent(QResizeEvent *) override;
void moveEvent(QMoveEvent *) override;
void setupMenuBar();
void createAnnotationDialog();
bool load();
void contextMenuEvent(QContextMenuEvent *e) override;
void setLocked(bool b, bool force, bool recount = true);
void configImages(const DB::ImageInfoList &list, bool oneAtATime);
void updateStates(bool thumbNailView);
DB::FileNameList selectedOnDisk();
void setupPluginMenu();
void launchViewer(const DB::FileNameList &mediaList, bool reuse, bool slideShow, bool random);
void setupStatusBar();
void setPluginMenuState(const char *name, const QList &actions);
void createSearchBar();
void executeStartupActions();
void checkIfVideoThumbnailerIsInstalled();
bool anyVideosSelected() const;
private:
static Window *s_instance;
+ ImageManager::ThumbnailCache *m_thumbnailCache;
ThumbnailView::ThumbnailFacade *m_thumbnailView;
Settings::SettingsDialog *m_settingsDialog;
QPointer m_annotationDialog;
QStackedWidget *m_stack;
QTimer *m_autoSaveTimer;
Browser::BrowserWidget *m_browser;
DeleteDialog *m_deleteDialog;
QAction *m_lock;
QAction *m_unlock;
QAction *m_setDefaultPos;
QAction *m_setDefaultNeg;
QAction *m_jumpToContext;
HTMLGenerator::HTMLDialog *m_htmlDialog;
QAction *m_configOneAtATime;
QAction *m_configAllSimultaniously;
QAction *m_createImageStack;
QAction *m_unStackImages;
QAction *m_setStackHead;
QAction *m_view;
QAction *m_rotLeft;
QAction *m_rotRight;
QAction *m_sortByDateAndTime;
QAction *m_sortAllByDateAndTime;
QAction *m_AutoStackImages;
QAction *m_viewInNewWindow;
KActionMenu *m_viewMenu;
KToggleAction *m_smallListView;
KToggleAction *m_largeListView;
KToggleAction *m_largeIconView;
KActionMenu *m_colorSchemeMenu;
QAction *m_generateHtml;
QAction *m_copy;
QAction *m_paste;
QAction *m_deleteSelected;
QAction *m_limitToMarked;
QAction *m_selectAll;
QAction *m_clearSelection;
QAction *m_runSlideShow;
QAction *m_runRandomSlideShow;
- Plugins::Interface *m_pluginInterface;
QAction *m_showExifDialog;
QAction *m_recreateThumbnails;
QAction *m_useNextVideoThumbnail;
QAction *m_usePreviousVideoThumbnail;
QAction *m_markUntagged;
TokenEditor *m_tokenEditor;
DateBar::DateBarWidget *m_dateBar;
QFrame *m_dateBarLine;
QMap> m_viewerInputMacros;
MainWindow::StatusBar *m_statusBar;
QString m_lastTarget;
#ifdef HAVE_MARBLE
Map::MapView *m_positionBrowser;
#endif
ThumbnailView::FilterWidget *m_filterWidget;
};
}
#endif /* MAINWINDOW_WINDOW_H */
// vi:expandtab:tabstop=4 shiftwidth=4: