diff --git a/core/app/date/ddatepicker_p.cpp b/core/app/date/ddatepicker_p.cpp
index 3c04f12d09..3c0ca7a148 100644
--- a/core/app/date/ddatepicker_p.cpp
+++ b/core/app/date/ddatepicker_p.cpp
@@ -1,223 +1,223 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 1997-04-21
* Description : A date selection widget.
*
* Copyright (C) 2011-2018 by Gilles Caulier
* Copyright (C) 1997 by Tim D. Gilman
* Copyright (C) 1998-2001 by Mirko Boehm
* Copyright (C) 2007 by John Layt
*
* 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 "ddatepicker_p.h"
// Qt includes
#include
#include
// KDE includes
#include
namespace Digikam
{
DatePickerValidator::DatePickerValidator(DDatePicker* const parent)
: QValidator(parent),
m_picker(parent)
{
}
QValidator::State DatePickerValidator::validate(QString& text, int&) const
{
QLocale::FormatType formats[] =
{
QLocale::LongFormat,
QLocale::ShortFormat,
QLocale::NarrowFormat
};
QLocale locale = m_picker->locale();
for (int i = 0; i < 3; i++)
{
QDate tmp = locale.toDate(text, formats[i]);
if (tmp.isValid())
{
return Acceptable;
}
}
return QValidator::Intermediate;
}
// ------------------------------------------------------------------------------
// Week numbers are defined by ISO 8601
// See http://www.merlyn.demon.co.uk/weekinfo.htm for details
DatePickerYearSelector::DatePickerYearSelector(const QDate& currentDate, QWidget* const parent)
: QLineEdit(parent),
val(new QIntValidator(this)),
result(0)
{
oldDate = currentDate;
setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
setFrame(false);
// TODO: Find a way to get that from QLocale
//val->setRange( calendar->year( calendar->earliestValidDate() ),
// calendar->year( calendar->latestValidDate() ) );
setValidator(val);
connect(this, &QLineEdit::returnPressed,
this, &DatePickerYearSelector::yearEnteredSlot);
}
void DatePickerYearSelector::yearEnteredSlot()
{
bool ok;
int newYear;
// check if entered value is a number
newYear = text().toInt(&ok);
if (!ok)
{
QApplication::beep();
return;
}
// check if new year will lead to a valid date
if (QDate(newYear, oldDate.month(), oldDate.day()).isValid())
{
result = newYear;
emit(closeMe(1));
}
else
{
QApplication::beep();
}
}
int DatePickerYearSelector::year() const
{
return result;
}
void DatePickerYearSelector::setYear(int year)
{
setText(QString::number(year));
}
// ------------------------------------------------------------------------------
DDatePicker::Private::Private(DDatePicker* const qq)
: q(qq)
{
closeButton = 0;
selectWeek = 0;
todayButton = 0;
navigationLayout = 0;
yearForward = 0;
yearBackward = 0;
monthForward = 0;
monthBackward = 0;
selectMonth = 0;
selectYear = 0;
line = 0;
val = 0;
table = 0;
fontsize = 0;
}
void DDatePicker::Private::fillWeeksCombo()
{
// every year can have a different number of weeks
// it could be that we had 53,1..52 and now 1..53 which is the same number but different
// so always fill with new values
// We show all week numbers for all weeks between first day of year to last day of year
// This of course can be a list like 53,1,2..52
const QDate thisDate = q->date();
const int thisYear = thisDate.year();
QDate day(thisDate.year(), 1, 1);
const QDate lastDayOfYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1);
selectWeek->clear();
// Starting from the first day in the year, loop through the year a week at a time
// adding an entry to the week combo for each week in the year
for (; day.isValid() && day <= lastDayOfYear; day = day.addDays(7))
{
// Get the ISO week number for the current day and what year that week is in
// e.g. 1st day of this year may fall in week 53 of previous year
int weekYear = thisYear;
const int week = day.weekNumber(&weekYear);
- QString weekString = i18n("Week %1", QString::number(week));
+ QString weekString = i18n("Week %1", week);
// show that this is a week from a different year
if (weekYear != thisYear)
{
weekString += QLatin1Char('*');
}
// when the week is selected, go to the same weekday as the one
// that is currently selected in the date table
QDate targetDate = day.addDays(thisDate.dayOfWeek() - day.dayOfWeek());
selectWeek->addItem(weekString, targetDate);
// make sure that the week of the lastDayOfYear is always inserted: in Chinese calendar
// system, this is not always the case
if (day < lastDayOfYear &&
day.daysTo(lastDayOfYear) < 7 &&
lastDayOfYear.weekNumber() != day.weekNumber())
{
day = lastDayOfYear.addDays(-7);
}
}
}
QDate DDatePicker::Private::validDateInYearMonth(int year, int month)
{
QDate newDate;
// Try to create a valid date in this year and month
// First try the first of the month, then try last of month
if (QDate(year, month, 1).isValid())
{
newDate = QDate(year, month, 1);
}
else if (QDate(year, month + 1, 1).isValid())
{
newDate = QDate(year, month + 1, 1).addDays(-1);
}
else
{
newDate = QDate::fromJulianDay(0);
}
return newDate;
}
} // namespace Digikam
diff --git a/core/libs/album/albummanager.cpp b/core/libs/album/albummanager.cpp
index 7ab1ea3797..a8e3aca52a 100644
--- a/core/libs/album/albummanager.cpp
+++ b/core/libs/album/albummanager.cpp
@@ -1,3756 +1,3756 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-06-15
* Description : Albums manager interface.
*
* Copyright (C) 2004 by Renchi Raju
* Copyright (C) 2006-2018 by Gilles Caulier
* Copyright (C) 2006-2011 by Marcel Wiesweg
* Copyright (C) 2015 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 "albummanager.h"
// C ANSI includes
extern "C"
{
#include
#include
#include
}
// C++ includes
#include
#include
#include
#include
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "digikam_debug.h"
#include "coredb.h"
#include "album.h"
#include "applicationsettings.h"
#include "metadatasettings.h"
#include "metadatasynchronizer.h"
#include "albumwatch.h"
#include "imageattributeswatch.h"
#include "collectionlocation.h"
#include "collectionmanager.h"
#include "digikam_config.h"
#include "coredbaccess.h"
#include "coredboperationgroup.h"
#include "dbengineguierrorhandler.h"
#include "dbengineparameters.h"
#include "databaseserverstarter.h"
#include "coredbthumbinfoprovider.h"
#include "coredburl.h"
#include "coredbsearchxml.h"
#include "coredbwatch.h"
#include "dio.h"
#include "facetags.h"
#include "facetagseditor.h"
#include "imagelister.h"
#include "scancontroller.h"
#include "setupcollections.h"
#include "setup.h"
#include "tagscache.h"
#include "thumbsdbaccess.h"
#include "thumbnailloadthread.h"
#include "dnotificationwrapper.h"
#include "dbjobinfo.h"
#include "dbjobsmanager.h"
#include "dbjobsthread.h"
#include "similaritydb.h"
#include "similaritydbaccess.h"
namespace Digikam
{
class Q_DECL_HIDDEN PAlbumPath
{
public:
PAlbumPath()
: albumRootId(-1)
{
}
PAlbumPath(int albumRootId, const QString& albumPath)
: albumRootId(albumRootId),
albumPath(albumPath)
{
}
// cppcheck-suppress noExplicitConstructor
PAlbumPath(PAlbum* const album)
{
if (album->isRoot())
{
albumRootId = -1;
}
else
{
albumRootId = album->albumRootId();
albumPath = album->albumPath();
}
}
bool operator==(const PAlbumPath& other) const
{
return (other.albumRootId == albumRootId &&
other.albumPath == albumPath);
}
public:
int albumRootId;
QString albumPath;
};
// -----------------------------------------------------------------------------------
uint qHash(const PAlbumPath& id)
{
return ( ::qHash(id.albumRootId) ^ ::qHash(id.albumPath) );
}
// -----------------------------------------------------------------------------------
class Q_DECL_HIDDEN AlbumManager::Private
{
public:
explicit Private()
: changed(false),
hasPriorizedDbPath(false),
dbFakeConnection(false),
showOnlyAvailableAlbums(false),
albumListJob(0),
dateListJob(0),
tagListJob(0),
personListJob(0),
albumWatch(0),
rootPAlbum(0),
rootTAlbum(0),
rootDAlbum(0),
rootSAlbum(0),
currentlyMovingAlbum(0),
changingDB(false),
scanPAlbumsTimer(0),
scanTAlbumsTimer(0),
scanSAlbumsTimer(0),
scanDAlbumsTimer(0),
updatePAlbumsTimer(0),
albumItemCountTimer(0),
tagItemCountTimer(0)
{
}
bool changed;
bool hasPriorizedDbPath;
bool dbFakeConnection;
bool showOnlyAvailableAlbums;
AlbumsDBJobsThread* albumListJob;
DatesDBJobsThread* dateListJob;
TagsDBJobsThread* tagListJob;
TagsDBJobsThread* personListJob;
AlbumWatch* albumWatch;
PAlbum* rootPAlbum;
TAlbum* rootTAlbum;
DAlbum* rootDAlbum;
SAlbum* rootSAlbum;
QHash allAlbumsIdHash;
QHash albumPathHash;
QHash albumRootAlbumHash;
Album* currentlyMovingAlbum;
QMultiHash guardedPointers;
/** For multiple selection support **/
QList currentAlbums;
bool changingDB;
QTimer* scanPAlbumsTimer;
QTimer* scanTAlbumsTimer;
QTimer* scanSAlbumsTimer;
QTimer* scanDAlbumsTimer;
QTimer* updatePAlbumsTimer;
QTimer* albumItemCountTimer;
QTimer* tagItemCountTimer;
QSet changedPAlbums;
QMap pAlbumsCount;
QMap tAlbumsCount;
QMap dAlbumsCount;
QMap fAlbumsCount;
public:
QString labelForAlbumRootAlbum(const CollectionLocation& location)
{
QString label = location.label();
if (label.isEmpty())
{
label = location.albumRootPath();
}
return label;
}
};
// -----------------------------------------------------------------------------------
class Q_DECL_HIDDEN ChangingDB
{
public:
explicit ChangingDB(AlbumManager::Private* const d)
: d(d)
{
d->changingDB = true;
}
~ChangingDB()
{
d->changingDB = false;
}
AlbumManager::Private* const d;
};
// -----------------------------------------------------------------------------------
class Q_DECL_HIDDEN AlbumManagerCreator
{
public:
AlbumManager object;
};
Q_GLOBAL_STATIC(AlbumManagerCreator, creator)
// -----------------------------------------------------------------------------------
// A friend-class shortcut to circumvent accessing this from within the destructor
AlbumManager* AlbumManager::internalInstance = 0;
AlbumManager* AlbumManager::instance()
{
return &creator->object;
}
AlbumManager::AlbumManager()
: d(new Private)
{
qRegisterMetaType>("QMap");
qRegisterMetaType>("QMap");
qRegisterMetaType >>("QMap >");
internalInstance = this;
d->albumWatch = new AlbumWatch(this);
// these operations are pretty fast, no need for long queuing
d->scanPAlbumsTimer = new QTimer(this);
d->scanPAlbumsTimer->setInterval(50);
d->scanPAlbumsTimer->setSingleShot(true);
connect(d->scanPAlbumsTimer, SIGNAL(timeout()),
this, SLOT(scanPAlbums()));
d->scanTAlbumsTimer = new QTimer(this);
d->scanTAlbumsTimer->setInterval(50);
d->scanTAlbumsTimer->setSingleShot(true);
connect(d->scanTAlbumsTimer, SIGNAL(timeout()),
this, SLOT(scanTAlbums()));
d->scanSAlbumsTimer = new QTimer(this);
d->scanSAlbumsTimer->setInterval(50);
d->scanSAlbumsTimer->setSingleShot(true);
connect(d->scanSAlbumsTimer, SIGNAL(timeout()),
this, SLOT(scanSAlbums()));
d->updatePAlbumsTimer = new QTimer(this);
d->updatePAlbumsTimer->setInterval(50);
d->updatePAlbumsTimer->setSingleShot(true);
connect(d->updatePAlbumsTimer, SIGNAL(timeout()),
this, SLOT(updateChangedPAlbums()));
// this operation is much more expensive than the other scan methods
d->scanDAlbumsTimer = new QTimer(this);
d->scanDAlbumsTimer->setInterval(30 * 1000);
d->scanDAlbumsTimer->setSingleShot(true);
connect(d->scanDAlbumsTimer, SIGNAL(timeout()),
this, SLOT(scanDAlbumsScheduled()));
// moderately expensive
d->albumItemCountTimer = new QTimer(this);
d->albumItemCountTimer->setInterval(1000);
d->albumItemCountTimer->setSingleShot(true);
connect(d->albumItemCountTimer, SIGNAL(timeout()),
this, SLOT(getAlbumItemsCount()));
// more expensive
d->tagItemCountTimer = new QTimer(this);
d->tagItemCountTimer->setInterval(2500);
d->tagItemCountTimer->setSingleShot(true);
connect(d->tagItemCountTimer, SIGNAL(timeout()),
this, SLOT(getTagItemsCount()));
}
AlbumManager::~AlbumManager()
{
delete d->rootPAlbum;
delete d->rootTAlbum;
delete d->rootDAlbum;
delete d->rootSAlbum;
internalInstance = 0;
delete d;
}
void AlbumManager::cleanUp()
{
// This is what we prefer to do before Application destruction
if (d->dateListJob)
{
d->dateListJob->cancel();
d->dateListJob = 0;
}
if (d->albumListJob)
{
d->albumListJob->cancel();
d->albumListJob = 0;
}
if (d->tagListJob)
{
d->tagListJob->cancel();
d->tagListJob = 0;
}
if (d->personListJob)
{
d->personListJob->cancel();
d->personListJob = 0;
}
}
bool AlbumManager::databaseEqual(const DbEngineParameters& parameters) const
{
DbEngineParameters params = CoreDbAccess::parameters();
return (params == parameters);
}
static bool moveToBackup(const QFileInfo& info)
{
if (info.exists())
{
QFileInfo backup(info.dir(), info.fileName() + QLatin1String("-backup-") + QDateTime::currentDateTime().toString(Qt::ISODate));
bool ret = QDir().rename(info.filePath(), backup.filePath());
if (!ret)
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("Failed to backup the existing database file (\"%1\"). "
"Refusing to replace file without backup, using the existing file.",
QDir::toNativeSeparators(info.filePath())));
return false;
}
}
return true;
}
static bool copyToNewLocation(const QFileInfo& oldFile, const QFileInfo& newFile,
const QString otherMessage = QString())
{
QString message = otherMessage;
if (message.isNull())
{
message = i18n("Failed to copy the old database file (\"%1\") "
"to its new location (\"%2\"). "
"Starting with an empty database.",
QDir::toNativeSeparators(oldFile.filePath()),
QDir::toNativeSeparators(newFile.filePath()));
}
bool ret = QFile::copy(oldFile.filePath(), newFile.filePath());
if (!ret)
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), message);
return false;
}
return true;
}
void AlbumManager::checkDatabaseDirsAfterFirstRun(const QString& dbPath, const QString& albumPath)
{
// for bug #193522
QDir newDir(dbPath);
QDir albumDir(albumPath);
DbEngineParameters newParams = DbEngineParameters::parametersForSQLiteDefaultFile(newDir.path());
QFileInfo digikam4DB(newParams.SQLiteDatabaseFile());
if (!digikam4DB.exists())
{
QFileInfo digikam3DB(newDir, QLatin1String("digikam3.db"));
QFileInfo digikamVeryOldDB(newDir, QLatin1String("digikam.db"));
if (digikam3DB.exists() || digikamVeryOldDB.exists())
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
i18n("Database Folder"),
i18n("You have chosen the folder \"%1\" as the place to store the database. "
"A database file from an older version of digiKam is found in this folder.
"
"Would you like to upgrade the old database file - confirming "
"that this database file was indeed created for the pictures located in the folder \"%2\" - "
"or ignore the old file and start with a new database?
",
QDir::toNativeSeparators(newDir.path()),
QDir::toNativeSeparators(albumDir.path())),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
msgBox->button(QMessageBox::Yes)->setText(i18n("Upgrade Database"));
msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
msgBox->button(QMessageBox::No)->setText(i18n("Create New Database"));
msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new")));
msgBox->setDefaultButton(QMessageBox::Yes);
int result = msgBox->exec();
if (result == QMessageBox::Yes)
{
// CoreDbSchemaUpdater expects Album Path to point to the album root of the 0.9 db file.
// Restore this situation.
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group("Album Settings");
group.writeEntry("Album Path", albumDir.path());
group.sync();
}
else if (result == QMessageBox::No)
{
moveToBackup(digikam3DB);
moveToBackup(digikamVeryOldDB);
}
delete msgBox;
}
}
}
void AlbumManager::changeDatabase(const DbEngineParameters& newParams)
{
// if there is no file at the new place, copy old one
DbEngineParameters params = CoreDbAccess::parameters();
// New database type SQLITE
if (newParams.isSQLite())
{
DatabaseServerStarter::instance()->stopServerManagerProcess();
QDir newDir(newParams.getCoreDatabaseNameOrDir());
QFileInfo newFile(newDir, QLatin1String("digikam4.db"));
if (!newFile.exists())
{
QFileInfo digikam3DB(newDir, QLatin1String("digikam3.db"));
QFileInfo digikamVeryOldDB(newDir, QLatin1String("digikam.db"));
if (digikam3DB.exists() || digikamVeryOldDB.exists())
{
int result = -1;
if (params.isSQLite())
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
i18n("New database folder"),
i18n("You have chosen the folder \"%1\" as the new place to store the database. "
"A database file from an older version of digiKam is found in this folder.
"
"Would you like to upgrade the old database file, start with a new database, "
"or copy the current database to this location and continue using it?
",
QDir::toNativeSeparators(newDir.path())),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
qApp->activeWindow());
msgBox->button(QMessageBox::Yes)->setText(i18n("Upgrade Database"));
msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
msgBox->button(QMessageBox::No)->setText(i18n("Create New Database"));
msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new")));
msgBox->button(QMessageBox::Cancel)->setText(i18n("Copy Current Database"));
msgBox->button(QMessageBox::Cancel)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
msgBox->setDefaultButton(QMessageBox::Yes);
result = msgBox->exec();
delete msgBox;
}
else
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
i18n("New database folder"),
i18n("You have chosen the folder \"%1\" as the new place to store the database. "
"A database file from an older version of digiKam is found in this folder.
"
"Would you like to upgrade the old database file or start with a new database?
",
QDir::toNativeSeparators(newDir.path())),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
msgBox->button(QMessageBox::Yes)->setText(i18n("Upgrade Database"));
msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
msgBox->button(QMessageBox::No)->setText(i18n("Create New Database"));
msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new")));
msgBox->setDefaultButton(QMessageBox::Yes);
result = msgBox->exec();
delete msgBox;
}
if (result == QMessageBox::Yes)
{
// CoreDbSchemaUpdater expects Album Path to point to the album root of the 0.9 db file.
// Restore this situation.
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(QLatin1String("Album Settings"));
group.writeEntry(QLatin1String("Album Path"), newDir.path());
group.sync();
}
else if (result == QMessageBox::No)
{
moveToBackup(digikam3DB);
moveToBackup(digikamVeryOldDB);
}
else if (result == QMessageBox::Cancel)
{
QFileInfo oldFile(params.SQLiteDatabaseFile());
copyToNewLocation(oldFile, newFile, i18n("Failed to copy the old database file (\"%1\") "
"to its new location (\"%2\"). "
"Trying to upgrade old databases.",
QDir::toNativeSeparators(oldFile.filePath()),
QDir::toNativeSeparators(newFile.filePath())));
}
}
else
{
int result = QMessageBox::Yes;
if (params.isSQLite())
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
i18n("New database folder"),
i18n("You have chosen the folder \"%1\" as the new place to store the database.
"
"Would you like to copy the current database to this location "
"and continue using it, or start with a new database?
",
QDir::toNativeSeparators(newDir.path())),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
msgBox->button(QMessageBox::Yes)->setText(i18n("Create New Database"));
msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("document-new")));
msgBox->button(QMessageBox::No)->setText(i18n("Copy Current Database"));
msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
msgBox->setDefaultButton(QMessageBox::Yes);
result = msgBox->exec();
delete msgBox;
}
if (result == QMessageBox::No)
{
QFileInfo oldFile(params.SQLiteDatabaseFile());
copyToNewLocation(oldFile, newFile);
}
}
}
else
{
int result = QMessageBox::No;
if (params.isSQLite())
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
i18n("New database folder"),
i18n("You have chosen the folder \"%1\" as the new place to store the database. "
"There is already a database file in this location.
"
"Would you like to use this existing file as the new database, or remove it "
"and copy the current database to this place?
",
QDir::toNativeSeparators(newDir.path())),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
msgBox->button(QMessageBox::Yes)->setText(i18n("Copy Current Database"));
msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
msgBox->button(QMessageBox::No)->setText(i18n("Use Existing File"));
msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-open")));
msgBox->setDefaultButton(QMessageBox::Yes);
result = msgBox->exec();
delete msgBox;
}
if (result == QMessageBox::Yes)
{
// first backup
if (moveToBackup(newFile))
{
QFileInfo oldFile(params.SQLiteDatabaseFile());
// then copy
copyToNewLocation(oldFile, newFile);
}
}
}
}
if (setDatabase(newParams, false))
{
QApplication::setOverrideCursor(Qt::WaitCursor);
startScan();
QApplication::restoreOverrideCursor();
ScanController::instance()->completeCollectionScan();
}
}
bool AlbumManager::setDatabase(const DbEngineParameters& params, bool priority, const QString& suggestedAlbumRoot)
{
// This is to ensure that the setup does not overrule the command line.
// TODO: there is a bug that setup is showing something different here.
if (priority)
{
d->hasPriorizedDbPath = true;
}
else if (d->hasPriorizedDbPath)
{
// ignore change without priority
return true;
}
// shutdown possibly running collection scans. Must call resumeCollectionScan further down.
ScanController::instance()->cancelAllAndSuspendCollectionScan();
QApplication::setOverrideCursor(Qt::WaitCursor);
d->changed = true;
disconnect(CollectionManager::instance(), 0, this, 0);
CollectionManager::instance()->setWatchDisabled();
if (CoreDbAccess::databaseWatch())
{
disconnect(CoreDbAccess::databaseWatch(), 0, this, 0);
}
DatabaseServerStarter::instance()->stopServerManagerProcess();
d->albumWatch->clear();
cleanUp();
d->currentAlbums.clear();
emit signalAlbumCurrentChanged(d->currentAlbums);
emit signalAlbumsCleared();
d->albumPathHash.clear();
d->allAlbumsIdHash.clear();
d->albumRootAlbumHash.clear();
// deletes all child albums as well
delete d->rootPAlbum;
delete d->rootTAlbum;
delete d->rootDAlbum;
delete d->rootSAlbum;
d->rootPAlbum = 0;
d->rootTAlbum = 0;
d->rootDAlbum = 0;
d->rootSAlbum = 0;
// -- Database initialization -------------------------------------------------
// ensure, embedded database is loaded
qCDebug(DIGIKAM_GENERAL_LOG) << params;
// workaround for the problem mariaDB >= 10.2 and QTBUG-63108
if (params.isMySQL())
{
addFakeConnection();
}
if (params.internalServer)
{
DatabaseServerError result = DatabaseServerStarter::instance()->startServerManagerProcess(params);
if (result.getErrorType() != DatabaseServerError::NoErrors)
{
QWidget* const parent = QWidget::find(0);
QString message = i18n("An error occurred during the internal server start.
"
"Details:\n %1", result.getErrorText());
QApplication::changeOverrideCursor(Qt::ArrowCursor);
QMessageBox::critical(parent, qApp->applicationName(), message);
QApplication::changeOverrideCursor(Qt::WaitCursor);
}
}
CoreDbAccess::setParameters(params, CoreDbAccess::MainApplication);
DbEngineGuiErrorHandler* const handler = new DbEngineGuiErrorHandler(CoreDbAccess::parameters());
CoreDbAccess::initDbEngineErrorHandler(handler);
if (!handler->checkDatabaseConnection())
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("Failed to open the database. "
"
You cannot use digiKam without a working database. "
"digiKam will attempt to start now, but it will not be functional. "
"Please check the database settings in the configuration menu.
"
));
CoreDbAccess::setParameters(DbEngineParameters(), CoreDbAccess::DatabaseSlave);
QApplication::restoreOverrideCursor();
return true;
}
d->albumWatch->setDbEngineParameters(params);
// still suspended from above
ScanController::instance()->resumeCollectionScan();
ScanController::Advice advice = ScanController::instance()->databaseInitialization();
QApplication::restoreOverrideCursor();
switch (advice)
{
case ScanController::Success:
break;
case ScanController::ContinueWithoutDatabase:
{
QString errorMsg = CoreDbAccess().lastError();
if (errorMsg.isEmpty())
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("Failed to open the database. "
"
You cannot use digiKam without a working database. "
"digiKam will attempt to start now, but it will not be functional. "
"Please check the database settings in the configuration menu.
"
));
}
else
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("Failed to open the database. Error message from database:
"
"%1
"
- "
You cannot use digiKam without a working database. "
+ "
You cannot use digiKam without a working database. "
"digiKam will attempt to start now, but it will not be functional. "
"Please check the database settings in the configuration menu.
",
errorMsg));
}
return true;
}
case ScanController::AbortImmediately:
return false;
}
// -- Locale Checking ---------------------------------------------------------
QString currLocale = QString::fromUtf8((QTextCodec::codecForLocale()->name()));
QString dbLocale = CoreDbAccess().db()->getSetting(QLatin1String("Locale"));
// guilty until proven innocent
bool localeChanged = true;
if (dbLocale.isNull())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "No locale found in database";
// Copy an existing locale from the settings file (used < 0.8)
// to the database.
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(QLatin1String("General Settings"));
if (group.hasKey(QLatin1String("Locale")))
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Locale found in configfile";
dbLocale = group.readEntry(QLatin1String("Locale"), QString());
// this hack is necessary, as we used to store the entire
// locale info LC_ALL (for eg: en_US.UTF-8) earlier,
// we now save only the encoding (UTF-8)
QString oldConfigLocale = QString::fromUtf8(::setlocale(0, 0));
if (oldConfigLocale == dbLocale)
{
dbLocale = currLocale;
localeChanged = false;
CoreDbAccess().db()->setSetting(QLatin1String("Locale"), dbLocale);
}
}
else
{
qCDebug(DIGIKAM_GENERAL_LOG) << "No locale found in config file";
dbLocale = currLocale;
localeChanged = false;
CoreDbAccess().db()->setSetting(QLatin1String("Locale"), dbLocale);
}
}
else
{
if (dbLocale == currLocale)
{
localeChanged = false;
}
}
if (localeChanged)
{
// TODO it would be better to replace all yes/no confirmation dialogs with ones that has custom
// buttons that denote the actions directly, i.e.: ["Ignore and Continue"] ["Adjust locale"]
int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(),
i18n("Your locale has changed since this "
"album was last opened.\n"
"Old locale: %1, new locale: %2\n"
"If you have recently changed your locale, you need not be concerned.\n"
"Please note that if you switched to a locale "
"that does not support some of the filenames in your collection, "
"these files may no longer be found in the collection. "
"If you are sure that you want to "
"continue, click 'Yes'. "
"Otherwise, click 'No' and correct your "
"locale setting before restarting digiKam.",
dbLocale, currLocale),
QMessageBox::Yes | QMessageBox::No);
if (result != QMessageBox::Yes)
{
return false;
}
CoreDbAccess().db()->setSetting(QLatin1String("Locale"), currLocale);
}
// -- UUID Checking ---------------------------------------------------------
QList disappearedLocations = CollectionManager::instance()->checkHardWiredLocations();
foreach(const CollectionLocation& loc, disappearedLocations)
{
QString locDescription;
QStringList candidateIds, candidateDescriptions;
CollectionManager::instance()->migrationCandidates(loc, &locDescription, &candidateIds, &candidateDescriptions);
qCDebug(DIGIKAM_GENERAL_LOG) << "Migration candidates for" << locDescription << ":" << candidateIds << candidateDescriptions;
QDialog* const dialog = new QDialog;
QWidget* const widget = new QWidget(dialog);
QGridLayout* const mainLayout = new QGridLayout;
mainLayout->setColumnStretch(1, 1);
QLabel* const deviceIconLabel = new QLabel;
deviceIconLabel->setPixmap(QIcon::fromTheme(QLatin1String("drive-harddisk")).pixmap(64));
mainLayout->addWidget(deviceIconLabel, 0, 0);
QLabel* const mainLabel = new QLabel(i18n("The collection
%1
(%2)
is currently not found on your system.
"
"Please choose the most appropriate option to handle this situation:
",
loc.label(), QDir::toNativeSeparators(locDescription)));
mainLabel->setWordWrap(true);
mainLayout->addWidget(mainLabel, 0, 1);
QGroupBox* const groupBox = new QGroupBox;
mainLayout->addWidget(groupBox, 1, 0, 1, 2);
QGridLayout* const layout = new QGridLayout;
layout->setColumnStretch(1, 1);
QRadioButton* migrateButton = 0;
QComboBox* migrateChoices = 0;
if (!candidateIds.isEmpty())
{
migrateButton = new QRadioButton;
QLabel* const migrateLabel = new QLabel(i18n("The collection is still available, but the identifier changed.
"
"This can be caused by restoring a backup, changing the partition layout "
"or the file system settings.
"
"The collection is now located at this place:
"));
migrateLabel->setWordWrap(true);
migrateChoices = new QComboBox;
for (int i = 0 ; i < candidateIds.size() ; ++i)
{
migrateChoices->addItem(QDir::toNativeSeparators(candidateDescriptions.at(i)), candidateIds.at(i));
}
layout->addWidget(migrateButton, 0, 0, Qt::AlignTop);
layout->addWidget(migrateLabel, 0, 1);
layout->addWidget(migrateChoices, 1, 1);
}
QRadioButton* const isRemovableButton = new QRadioButton;
QLabel* const isRemovableLabel = new QLabel(i18n("The collection is located on a storage device which is not always attached. "
"Mark the collection as a removable collection."));
isRemovableLabel->setWordWrap(true);
layout->addWidget(isRemovableButton, 2, 0, Qt::AlignTop);
layout->addWidget(isRemovableLabel, 2, 1);
QRadioButton* const solveManuallyButton = new QRadioButton;
QLabel* const solveManuallyLabel = new QLabel(i18n("Take no action now. I would like to solve the problem "
"later using the setup dialog"));
solveManuallyLabel->setWordWrap(true);
layout->addWidget(solveManuallyButton, 3, 0, Qt::AlignTop);
layout->addWidget(solveManuallyLabel, 3, 1);
groupBox->setLayout(layout);
widget->setLayout(mainLayout);
QVBoxLayout* const vbx = new QVBoxLayout(dialog);
QDialogButtonBox* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok, dialog);
vbx->addWidget(widget);
vbx->addWidget(buttons);
dialog->setLayout(vbx);
dialog->setWindowTitle(i18n("Collection not found"));
connect(buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
dialog, SLOT(accept()));
// Default option: If there is only one candidate, default to migration.
// Otherwise default to do nothing now.
if (migrateButton && candidateIds.size() == 1)
{
migrateButton->setChecked(true);
}
else
{
solveManuallyButton->setChecked(true);
}
if (dialog->exec())
{
if (migrateButton && migrateButton->isChecked())
{
CollectionManager::instance()->migrateToVolume(loc, migrateChoices->itemData(migrateChoices->currentIndex()).toString());
}
else if (isRemovableButton->isChecked())
{
CollectionManager::instance()->changeType(loc, CollectionLocation::TypeVolumeRemovable);
}
}
delete dialog;
}
// -- ---------------------------------------------------------
// check that we have one album root
if (CollectionManager::instance()->allLocations().isEmpty())
{
if (suggestedAlbumRoot.isEmpty())
{
Setup::execSinglePage(Setup::CollectionsPage);
}
else
{
QUrl albumRoot(QUrl::fromLocalFile(suggestedAlbumRoot));
CollectionManager::instance()->addLocation(albumRoot, albumRoot.fileName());
// Not needed? See bug #188959
//ScanController::instance()->completeCollectionScan();
}
}
// -- ---------------------------------------------------------
QApplication::setOverrideCursor(Qt::WaitCursor);
ThumbnailLoadThread::initializeThumbnailDatabase(CoreDbAccess::parameters().thumbnailParameters(),
new ThumbsDbInfoProvider());
DbEngineGuiErrorHandler* const thumbnailsDBHandler = new DbEngineGuiErrorHandler(ThumbsDbAccess::parameters());
ThumbsDbAccess::initDbEngineErrorHandler(thumbnailsDBHandler);
// Activate the similarity database.
SimilarityDbAccess::setParameters(params.similarityParameters());
DbEngineGuiErrorHandler* const similarityHandler = new DbEngineGuiErrorHandler(SimilarityDbAccess::parameters());
SimilarityDbAccess::initDbEngineErrorHandler(similarityHandler);
if (SimilarityDbAccess::checkReadyForUse(0))
{
qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Similarity database ready for use";
}
else
{
qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Failed to initialize similarity database";
}
QApplication::restoreOverrideCursor();
return true;
}
void AlbumManager::startScan()
{
if (!d->changed)
{
return;
}
d->changed = false;
// create root albums
d->rootPAlbum = new PAlbum(i18n("Albums"));
insertPAlbum(d->rootPAlbum, 0);
d->rootTAlbum = new TAlbum(i18n("Tags"), 0, true);
insertTAlbum(d->rootTAlbum, 0);
d->rootSAlbum = new SAlbum(i18n("Searches"), 0, true);
emit signalAlbumAboutToBeAdded(d->rootSAlbum, 0, 0);
d->allAlbumsIdHash[d->rootSAlbum->globalID()] = d->rootSAlbum;
emit signalAlbumAdded(d->rootSAlbum);
d->rootDAlbum = new DAlbum(QDate(), true);
emit signalAlbumAboutToBeAdded(d->rootDAlbum, 0, 0);
d->allAlbumsIdHash[d->rootDAlbum->globalID()] = d->rootDAlbum;
emit signalAlbumAdded(d->rootDAlbum);
// Create albums for album roots. Reuse logic implemented in the method
foreach(const CollectionLocation& location, CollectionManager::instance()->allLocations())
{
handleCollectionStatusChange(location, CollectionLocation::LocationNull);
}
// listen to location status changes
connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(CollectionLocation,int)),
this, SLOT(slotCollectionLocationStatusChanged(CollectionLocation,int)));
connect(CollectionManager::instance(), SIGNAL(locationPropertiesChanged(CollectionLocation)),
this, SLOT(slotCollectionLocationPropertiesChanged(CollectionLocation)));
// reload albums
refresh();
// listen to album database changes
connect(CoreDbAccess::databaseWatch(), SIGNAL(albumChange(AlbumChangeset)),
this, SLOT(slotAlbumChange(AlbumChangeset)));
connect(CoreDbAccess::databaseWatch(), SIGNAL(tagChange(TagChangeset)),
this, SLOT(slotTagChange(TagChangeset)));
connect(CoreDbAccess::databaseWatch(), SIGNAL(searchChange(SearchChangeset)),
this, SLOT(slotSearchChange(SearchChangeset)));
// listen to collection image changes
connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)),
this, SLOT(slotCollectionImageChange(CollectionImageChangeset)));
connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)),
this, SLOT(slotImageTagChange(ImageTagChangeset)));
// listen to image attribute changes
connect(ImageAttributesWatch::instance(), SIGNAL(signalImageDateChanged(qlonglong)),
d->scanDAlbumsTimer, SLOT(start()));
emit signalAllAlbumsLoaded();
}
void AlbumManager::slotCollectionLocationStatusChanged(const CollectionLocation& location, int oldStatus)
{
// not before initialization
if (!d->rootPAlbum)
{
return;
}
if (handleCollectionStatusChange(location, oldStatus))
{
// a change occurred. Possibly albums have appeared or disappeared
scanPAlbums();
}
}
/// Returns true if it added or removed an album
bool AlbumManager::handleCollectionStatusChange(const CollectionLocation& location, int oldStatus)
{
enum Action
{
Add,
Remove,
DoNothing
};
Action action = DoNothing;
switch (oldStatus)
{
case CollectionLocation::LocationNull:
case CollectionLocation::LocationHidden:
case CollectionLocation::LocationUnavailable:
{
switch (location.status())
{
case CollectionLocation::LocationNull: // not possible
break;
case CollectionLocation::LocationHidden:
action = Remove;
break;
case CollectionLocation::LocationAvailable:
action = Add;
break;
case CollectionLocation::LocationUnavailable:
if (d->showOnlyAvailableAlbums)
{
action = Remove;
}
else
{
action = Add;
}
break;
case CollectionLocation::LocationDeleted:
action = Remove;
break;
}
break;
}
case CollectionLocation::LocationAvailable:
{
switch (location.status())
{
case CollectionLocation::LocationNull:
case CollectionLocation::LocationHidden:
case CollectionLocation::LocationDeleted:
action = Remove;
break;
case CollectionLocation::LocationUnavailable:
if (d->showOnlyAvailableAlbums)
{
action = Remove;
}
break;
case CollectionLocation::LocationAvailable: // not possible
break;
}
break;
}
case CollectionLocation::LocationDeleted: // not possible
break;
}
if (action == Add && !d->albumRootAlbumHash.value(location.id()))
{
// This is the only place where album root albums are added
addAlbumRoot(location);
return true;
}
else if (action == Remove && d->albumRootAlbumHash.value(location.id()))
{
removeAlbumRoot(location);
return true;
}
return false;
}
void AlbumManager::slotCollectionLocationPropertiesChanged(const CollectionLocation& location)
{
PAlbum* const album = d->albumRootAlbumHash.value(location.id());
if (album)
{
QString newLabel = d->labelForAlbumRootAlbum(location);
if (album->title() != newLabel)
{
album->setTitle(newLabel);
emit signalAlbumRenamed(album);
}
}
}
void AlbumManager::addAlbumRoot(const CollectionLocation& location)
{
PAlbum* album = d->albumRootAlbumHash.value(location.id());
if (!album)
{
// Create a PAlbum for the Album Root.
QString label = d->labelForAlbumRootAlbum(location);
album = new PAlbum(location.id(), label);
qCDebug(DIGIKAM_GENERAL_LOG) << "Added root album called: " << album->title();
// insert album root created into hash
d->albumRootAlbumHash.insert(location.id(), album);
}
}
void AlbumManager::removeAlbumRoot(const CollectionLocation& location)
{
// retrieve and remove from hash
PAlbum* const album = d->albumRootAlbumHash.take(location.id());
if (album)
{
// delete album and all its children
removePAlbum(album);
}
}
bool AlbumManager::isShowingOnlyAvailableAlbums() const
{
return d->showOnlyAvailableAlbums;
}
void AlbumManager::setShowOnlyAvailableAlbums(bool onlyAvailable)
{
if (d->showOnlyAvailableAlbums == onlyAvailable)
{
return;
}
d->showOnlyAvailableAlbums = onlyAvailable;
emit signalShowOnlyAvailableAlbumsChanged(d->showOnlyAvailableAlbums);
// We need to update the unavailable locations.
// We assume the handleCollectionStatusChange does the right thing (even though old status == current status)
foreach (const CollectionLocation& location, CollectionManager::instance()->allLocations())
{
if (location.status() == CollectionLocation::LocationUnavailable)
{
handleCollectionStatusChange(location, CollectionLocation::LocationUnavailable);
}
}
}
void AlbumManager::refresh()
{
scanPAlbums();
scanTAlbums();
scanSAlbums();
scanDAlbums();
}
void AlbumManager::prepareItemCounts()
{
// There is no way to find out if any data we had collected
// previously is still valid - recompute
scanDAlbums();
getAlbumItemsCount();
getTagItemsCount();
}
void AlbumManager::scanPAlbums()
{
d->scanPAlbumsTimer->stop();
// first insert all the current normal PAlbums into a map for quick lookup
QHash oldAlbums;
AlbumIterator it(d->rootPAlbum);
while (it.current())
{
PAlbum* const a = (PAlbum*)(*it);
oldAlbums[a->id()] = a;
++it;
}
// scan db and get a list of all albums
QList currentAlbums = CoreDbAccess().db()->scanAlbums();
// sort by relative path so that parents are created before children
std::sort(currentAlbums.begin(), currentAlbums.end());
QList newAlbums;
// go through all the Albums and see which ones are already present
foreach(const AlbumInfo& info, currentAlbums)
{
// check that location of album is available
if (d->showOnlyAvailableAlbums && !CollectionManager::instance()->locationForAlbumRootId(info.albumRootId).isAvailable())
{
continue;
}
if (oldAlbums.contains(info.id))
{
oldAlbums.remove(info.id);
}
else
{
newAlbums << info;
}
}
// now oldAlbums contains all the deleted albums and
// newAlbums contains all the new albums
// delete old albums, informing all frontends
// The albums have to be removed with children being removed first,
// removePAlbum takes care of that.
// So we only feed it the albums from oldAlbums topmost in hierarchy.
QSet topMostOldAlbums;
foreach(PAlbum* const album, oldAlbums)
{
if (album->isTrashAlbum())
{
continue;
}
if (!album->parent() || !oldAlbums.contains(album->parent()->id()))
{
topMostOldAlbums << album;
}
}
foreach(PAlbum* const album, topMostOldAlbums)
{
// recursively removes all children and the album
removePAlbum(album);
}
// sort by relative path so that parents are created before children
std::sort(newAlbums.begin(), newAlbums.end());
// create all new albums
foreach(const AlbumInfo& info, newAlbums)
{
if (info.relativePath.isEmpty())
{
continue;
}
PAlbum* album = 0, *parent = 0;
if (info.relativePath == QLatin1String("/"))
{
// Albums that represent the root directory of an album root
// We have them as here new albums first time after their creation
parent = d->rootPAlbum;
album = d->albumRootAlbumHash.value(info.albumRootId);
if (!album)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Did not find album root album in hash";
continue;
}
// it has been created from the collection location
// with album root id, parentPath "/" and a name, but no album id yet.
album->m_id = info.id;
}
else
{
// last section, no slash
QString name = info.relativePath.section(QLatin1Char('/'), -1, -1);
// all but last sections, leading slash, no trailing slash
QString parentPath = info.relativePath.section(QLatin1Char('/'), 0, -2);
if (parentPath.isEmpty())
{
parent = d->albumRootAlbumHash.value(info.albumRootId);
}
else
{
parent = d->albumPathHash.value(PAlbumPath(info.albumRootId, parentPath));
}
if (!parent)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find parent with url: "
<< parentPath << " for: "
<< info.relativePath;
continue;
}
// Create the new album
album = new PAlbum(info.albumRootId, parentPath, name, info.id);
}
album->m_caption = info.caption;
album->m_category = info.category;
album->m_date = info.date;
album->m_iconId = info.iconId;
insertPAlbum(album, parent);
if (album->isAlbumRoot())
{
// Inserting virtual Trash PAlbum for AlbumsRootAlbum using special constructor
PAlbum* trashAlbum = new PAlbum(album->title(), album->id());
insertPAlbum(trashAlbum, album);
}
}
if (!topMostOldAlbums.isEmpty() || !newAlbums.isEmpty())
{
emit signalAlbumsUpdated(Album::PHYSICAL);
}
getAlbumItemsCount();
}
void AlbumManager::updateChangedPAlbums()
{
d->updatePAlbumsTimer->stop();
// scan db and get a list of all albums
QList currentAlbums = CoreDbAccess().db()->scanAlbums();
bool needScanPAlbums = false;
// Find the AlbumInfo for each id in changedPAlbums
foreach(int id, d->changedPAlbums)
{
foreach(const AlbumInfo& info, currentAlbums)
{
if (info.id == id)
{
d->changedPAlbums.remove(info.id);
PAlbum* album = findPAlbum(info.id);
if (album)
{
// Renamed?
if (info.relativePath != QLatin1String("/"))
{
// Handle rename of album name
// last section, no slash
QString name = info.relativePath.section(QLatin1Char('/'), -1, -1);
QString parentPath = info.relativePath;
parentPath.chop(name.length());
if (parentPath != album->m_parentPath || info.albumRootId != album->albumRootId())
{
// Handle actual move operations: trigger ScanPAlbums
needScanPAlbums = true;
removePAlbum(album);
break;
}
else if (name != album->title())
{
album->setTitle(name);
updateAlbumPathHash();
emit signalAlbumRenamed(album);
}
}
// Update caption, collection, date
album->m_caption = info.caption;
album->m_category = info.category;
album->m_date = info.date;
// Icon changed?
if (album->m_iconId != info.iconId)
{
album->m_iconId = info.iconId;
emit signalAlbumIconChanged(album);
}
}
}
}
}
if (needScanPAlbums)
{
scanPAlbums();
}
}
void AlbumManager::getAlbumItemsCount()
{
d->albumItemCountTimer->stop();
if (!ApplicationSettings::instance()->getShowFolderTreeViewItemsCount())
{
return;
}
if (d->albumListJob)
{
d->albumListJob->cancel();
d->albumListJob = 0;
}
AlbumsDBJobInfo jInfo;
jInfo.setFoldersJob();
d->albumListJob = DBJobsManager::instance()->startAlbumsJobThread(jInfo);
connect(d->albumListJob, SIGNAL(finished()),
this, SLOT(slotAlbumsJobResult()));
connect(d->albumListJob, SIGNAL(foldersData(QMap)),
this, SLOT(slotAlbumsJobData(QMap)));
}
void AlbumManager::scanTAlbums()
{
d->scanTAlbumsTimer->stop();
// list TAlbums directly from the db
// first insert all the current TAlbums into a map for quick lookup
typedef QMap TagMap;
TagMap tmap;
tmap.insert(0, d->rootTAlbum);
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
TAlbum* t = (TAlbum*)(*it);
tmap.insert(t->id(), t);
++it;
}
// Retrieve the list of tags from the database
TagInfo::List tList = CoreDbAccess().db()->scanTags();
// sort the list. needed because we want the tags can be read in any order,
// but we want to make sure that we are ensure to find the parent TAlbum
// for a new TAlbum
{
QHash tagHash;
// insert items into a dict for quick lookup
for (TagInfo::List::const_iterator iter = tList.constBegin() ; iter != tList.constEnd() ; ++iter)
{
TagInfo info = *iter;
TAlbum* const album = new TAlbum(info.name, info.id);
album->m_icon = info.icon;
album->m_iconId = info.iconId;
album->m_pid = info.pid;
tagHash.insert(info.id, album);
}
tList.clear();
// also add root tag
TAlbum* const rootTag = new TAlbum(QLatin1String("root"), 0, true);
tagHash.insert(0, rootTag);
// build tree
for (QHash::const_iterator iter = tagHash.constBegin() ; iter != tagHash.constEnd() ; ++iter)
{
TAlbum* album = *iter;
if (album->m_id == 0)
{
continue;
}
TAlbum* const parent = tagHash.value(album->m_pid);
if (parent)
{
album->setParent(parent);
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag "
<< album->m_title
<< " with pid "
<< album->m_pid;
}
}
tagHash.clear();
// now insert the items into the list. becomes sorted
AlbumIterator it(rootTag);
while (it.current())
{
TagInfo info;
TAlbum* const album = static_cast(it.current());
if (album)
{
info.id = album->m_id;
info.pid = album->m_pid;
info.name = album->m_title;
info.icon = album->m_icon;
info.iconId = album->m_iconId;
}
tList.append(info);
++it;
}
// this will also delete all child albums
delete rootTag;
}
for (TagInfo::List::const_iterator it = tList.constBegin() ; it != tList.constEnd() ; ++it)
{
TagInfo info = *it;
// check if we have already added this tag
if (tmap.contains(info.id))
{
continue;
}
// Its a new album. Find the parent of the album
TagMap::const_iterator iter = tmap.constFind(info.pid);
if (iter == tmap.constEnd())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag "
<< info.name
<< " with pid "
<< info.pid;
continue;
}
TAlbum* const parent = iter.value();
// Create the new TAlbum
TAlbum* const album = new TAlbum(info.name, info.id, false);
album->m_icon = info.icon;
album->m_iconId = info.iconId;
insertTAlbum(album, parent);
// also insert it in the map we are doing lookup of parent tags
tmap.insert(info.id, album);
}
if (!tList.isEmpty())
{
emit signalAlbumsUpdated(Album::TAG);
}
getTagItemsCount();
}
void AlbumManager::getTagItemsCount()
{
d->tagItemCountTimer->stop();
if (!ApplicationSettings::instance()->getShowFolderTreeViewItemsCount())
{
return;
}
tagItemsCount();
personItemsCount();
}
void AlbumManager::tagItemsCount()
{
if (d->tagListJob)
{
d->tagListJob->cancel();
d->tagListJob = 0;
}
TagsDBJobInfo jInfo;
jInfo.setFoldersJob();
d->tagListJob = DBJobsManager::instance()->startTagsJobThread(jInfo);
connect(d->tagListJob, SIGNAL(finished()),
this, SLOT(slotTagsJobResult()));
connect(d->tagListJob, SIGNAL(foldersData(QMap)),
this, SLOT(slotTagsJobData(QMap)));
}
void AlbumManager::personItemsCount()
{
if (d->personListJob)
{
d->personListJob->cancel();
d->personListJob = 0;
}
TagsDBJobInfo jInfo;
jInfo.setFaceFoldersJob();
d->personListJob = DBJobsManager::instance()->startTagsJobThread(jInfo);
connect(d->personListJob, SIGNAL(finished()),
this, SLOT(slotPeopleJobResult()));
connect(d->personListJob, SIGNAL(faceFoldersData(QMap >)), // krazy:exclude=normalize
this, SLOT(slotPeopleJobData(QMap >))); // krazy:exclude=normalize
}
void AlbumManager::scanSAlbums()
{
d->scanSAlbumsTimer->stop();
// list SAlbums directly from the db
// first insert all the current SAlbums into a map for quick lookup
QMap oldSearches;
AlbumIterator it(d->rootSAlbum);
while (it.current())
{
SAlbum* const search = (SAlbum*)(*it);
oldSearches[search->id()] = search;
++it;
}
// scan db and get a list of all albums
QList currentSearches = CoreDbAccess().db()->scanSearches();
QList newSearches;
// go through all the Albums and see which ones are already present
foreach(const SearchInfo& info, currentSearches)
{
if (oldSearches.contains(info.id))
{
SAlbum* const album = oldSearches[info.id];
if (info.name != album->title() ||
info.type != album->searchType() ||
info.query != album->query())
{
QString oldName = album->title();
album->setSearch(info.type, info.query);
album->setTitle(info.name);
if (oldName != album->title())
{
emit signalAlbumRenamed(album);
}
emit signalSearchUpdated(album);
}
oldSearches.remove(info.id);
}
else
{
newSearches << info;
}
}
// remove old albums that have been deleted
foreach(SAlbum* const album, oldSearches)
{
emit signalAlbumAboutToBeDeleted(album);
d->allAlbumsIdHash.remove(album->globalID());
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
}
// add new albums
foreach(const SearchInfo& info, newSearches)
{
SAlbum* const album = new SAlbum(info.name, info.id);
album->setSearch(info.type, info.query);
emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild());
album->setParent(d->rootSAlbum);
d->allAlbumsIdHash[album->globalID()] = album;
emit signalAlbumAdded(album);
}
}
void AlbumManager::scanDAlbumsScheduled()
{
// Avoid a cycle of killing a job which takes longer than the timer interval
if (d->dateListJob)
{
d->scanDAlbumsTimer->start();
return;
}
scanDAlbums();
}
void AlbumManager::scanDAlbums()
{
d->scanDAlbumsTimer->stop();
if (d->dateListJob)
{
d->dateListJob->cancel();
d->dateListJob = 0;
}
DatesDBJobInfo jInfo;
jInfo.setFoldersJob();
d->dateListJob = DBJobsManager::instance()->startDatesJobThread(jInfo);
connect(d->dateListJob, SIGNAL(finished()),
this, SLOT(slotDatesJobResult()));
connect(d->dateListJob, SIGNAL(foldersData(QMap)),
this, SLOT(slotDatesJobData(QMap)));
}
AlbumList AlbumManager::allPAlbums() const
{
AlbumList list;
if (d->rootPAlbum)
{
list.append(d->rootPAlbum);
}
AlbumIterator it(d->rootPAlbum);
while (it.current())
{
list.append(*it);
++it;
}
return list;
}
AlbumList AlbumManager::allTAlbums() const
{
AlbumList list;
if (d->rootTAlbum)
{
list.append(d->rootTAlbum);
}
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
list.append(*it);
++it;
}
return list;
}
AlbumList AlbumManager::allSAlbums() const
{
AlbumList list;
if (d->rootSAlbum)
{
list.append(d->rootSAlbum);
}
AlbumIterator it(d->rootSAlbum);
while (it.current())
{
list.append(*it);
++it;
}
return list;
}
AlbumList AlbumManager::allDAlbums() const
{
AlbumList list;
if (d->rootDAlbum)
{
list.append(d->rootDAlbum);
}
AlbumIterator it(d->rootDAlbum);
while (it.current())
{
list.append(*it);
++it;
}
return list;
}
void AlbumManager::setCurrentAlbums(const QList& albums)
{
if (albums.isEmpty())
return;
d->currentAlbums.clear();
/**
* Filter out the null pointers
*/
foreach(Album* const album, albums)
{
if (album)
{
d->currentAlbums << album;
}
}
/**
* Sort is needed to identify selection correctly, ex AlbumHistory
*/
std::sort(d->currentAlbums.begin(), d->currentAlbums.end());
emit signalAlbumCurrentChanged(d->currentAlbums);
}
AlbumList AlbumManager::currentAlbums() const
{
return d->currentAlbums;
}
PAlbum* AlbumManager::currentPAlbum() const
{
/**
* Temporary fix, to return multiple items,
* iterate and cast each element
*/
if (!d->currentAlbums.isEmpty())
return dynamic_cast(d->currentAlbums.first());
else
return 0;
}
QList AlbumManager::currentTAlbums() const
{
/**
* This method is not yet used
*/
QList talbums;
QList::iterator it;
for (it = d->currentAlbums.begin() ; it != d->currentAlbums.end() ; ++it)
{
TAlbum* const temp = dynamic_cast(*it);
if (temp)
talbums.append(temp);
}
return talbums;
}
PAlbum* AlbumManager::findPAlbum(const QUrl& url) const
{
CollectionLocation location = CollectionManager::instance()->locationForUrl(url);
if (location.isNull())
{
return 0;
}
return d->albumPathHash.value(PAlbumPath(location.id(), CollectionManager::instance()->album(location, url)));
}
PAlbum* AlbumManager::findPAlbum(int id) const
{
if (!d->rootPAlbum)
{
return 0;
}
int gid = d->rootPAlbum->globalID() + id;
return static_cast((d->allAlbumsIdHash.value(gid)));
}
TAlbum* AlbumManager::findTAlbum(int id) const
{
if (!d->rootTAlbum)
{
return 0;
}
int gid = d->rootTAlbum->globalID() + id;
return static_cast((d->allAlbumsIdHash.value(gid)));
}
SAlbum* AlbumManager::findSAlbum(int id) const
{
if (!d->rootSAlbum)
{
return 0;
}
int gid = d->rootSAlbum->globalID() + id;
return static_cast((d->allAlbumsIdHash.value(gid)));
}
DAlbum* AlbumManager::findDAlbum(int id) const
{
if (!d->rootDAlbum)
{
return 0;
}
int gid = d->rootDAlbum->globalID() + id;
return static_cast((d->allAlbumsIdHash.value(gid)));
}
Album* AlbumManager::findAlbum(int gid) const
{
return d->allAlbumsIdHash.value(gid);
}
Album* AlbumManager::findAlbum(Album::Type type, int id) const
{
return findAlbum(Album::globalID(type, id));
}
TAlbum* AlbumManager::findTAlbum(const QString& tagPath) const
{
// handle gracefully with or without leading slash
bool withLeadingSlash = tagPath.startsWith(QLatin1Char('/'));
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
TAlbum* const talbum = static_cast(*it);
if (talbum->tagPath(withLeadingSlash) == tagPath)
{
return talbum;
}
++it;
}
return 0;
}
SAlbum* AlbumManager::findSAlbum(const QString& name) const
{
for (Album* album = d->rootSAlbum->firstChild() ; album ; album = album->next())
{
if (album->title() == name)
{
return dynamic_cast(album);
}
}
return 0;
}
QList AlbumManager::findSAlbumsBySearchType(int searchType) const
{
QList albums;
for (Album* album = d->rootSAlbum->firstChild() ; album ; album = album->next())
{
if (album != 0)
{
SAlbum* sAlbum = dynamic_cast(album);
if ((sAlbum != 0) && (sAlbum->searchType() == searchType))
{
albums.append(sAlbum);
}
}
}
return albums;
}
void AlbumManager::addGuardedPointer(Album* album, Album** pointer)
{
if (album)
{
d->guardedPointers.insert(album, pointer);
}
}
void AlbumManager::removeGuardedPointer(Album* album, Album** pointer)
{
if (album)
{
d->guardedPointers.remove(album, pointer);
}
}
void AlbumManager::changeGuardedPointer(Album* oldAlbum, Album* album, Album** pointer)
{
if (oldAlbum)
{
d->guardedPointers.remove(oldAlbum, pointer);
}
if (album)
{
d->guardedPointers.insert(album, pointer);
}
}
void AlbumManager::invalidateGuardedPointers(Album* album)
{
if (!album)
{
return;
}
QMultiHash::iterator it = d->guardedPointers.find(album);
for (; it != d->guardedPointers.end() && it.key() == album; ++it)
{
if (it.value())
{
*(it.value()) = 0;
}
}
}
PAlbum* AlbumManager::createPAlbum(const QString& albumRootPath, const QString& name,
const QString& caption, const QDate& date,
const QString& category,
QString& errMsg)
{
CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRootPath);
return createPAlbum(location, name, caption, date, category, errMsg);
}
PAlbum* AlbumManager::createPAlbum(const CollectionLocation& location, const QString& name,
const QString& caption, const QDate& date,
const QString& category,
QString& errMsg)
{
if (location.isNull() || !location.isAvailable())
{
errMsg = i18n("The collection location supplied is invalid or currently not available.");
return 0;
}
PAlbum* album = d->albumRootAlbumHash.value(location.id());
if (!album)
{
errMsg = i18n("No album for collection location: Internal error");
return 0;
}
return createPAlbum(album, name, caption, date, category, errMsg);
}
PAlbum* AlbumManager::createPAlbum(PAlbum* parent,
const QString& name,
const QString& caption,
const QDate& date,
const QString& category,
QString& errMsg)
{
if (!parent)
{
errMsg = i18n("No parent found for album.");
return 0;
}
// sanity checks
if (name.isEmpty())
{
errMsg = i18n("Album name cannot be empty.");
return 0;
}
if (name.contains(QLatin1Char('/')))
{
errMsg = i18n("Album name cannot contain '/'.");
return 0;
}
if (parent->isRoot())
{
errMsg = i18n("createPAlbum does not accept the root album as parent.");
return 0;
}
QString albumPath = parent->isAlbumRoot() ? QString(QLatin1Char('/') + name) : QString(parent->albumPath() + QLatin1Char('/') + name);
int albumRootId = parent->albumRootId();
// first check if we have a sibling album with the same name
PAlbum* child = static_cast(parent->firstChild());
while (child)
{
if (child->albumRootId() == albumRootId && child->albumPath() == albumPath)
{
errMsg = i18n("An existing album has the same name.");
return 0;
}
child = static_cast(child->next());
}
CoreDbUrl url = parent->databaseUrl();
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + QLatin1Char('/') + name);
QUrl fileUrl = url.fileUrl();
bool ret = QDir().mkdir(fileUrl.toLocalFile());
if (!ret)
{
errMsg = i18n("Failed to create directory '%1'", fileUrl.toString()); // TODO add tags?
return 0;
}
ChangingDB changing(d);
int id = CoreDbAccess().db()->addAlbum(albumRootId, albumPath, caption, date, category);
if (id == -1)
{
errMsg = i18n("Failed to add album to database");
return 0;
}
QString parentPath;
if (!parent->isAlbumRoot())
{
parentPath = parent->albumPath();
}
PAlbum* const album = new PAlbum(albumRootId, parentPath, name, id);
album->m_caption = caption;
album->m_category = category;
album->m_date = date;
insertPAlbum(album, parent);
emit signalAlbumsUpdated(Album::PHYSICAL);
return album;
}
bool AlbumManager::renamePAlbum(PAlbum* album, const QString& newName,
QString& errMsg)
{
if (!album)
{
errMsg = i18n("No such album");
return false;
}
if (album == d->rootPAlbum)
{
errMsg = i18n("Cannot rename root album");
return false;
}
if (album->isAlbumRoot())
{
errMsg = i18n("Cannot rename album root album");
return false;
}
if (newName.contains(QLatin1Char('/')))
{
errMsg = i18n("Album name cannot contain '/'");
return false;
}
// first check if we have another sibling with the same name
if (hasDirectChildAlbumWithTitle(album->m_parent, newName))
{
errMsg = i18n("Another album with the same name already exists.\n"
"Please choose another name.");
return false;
}
d->albumWatch->removeWatchedPAlbums(album);
QString oldAlbumPath = album->albumPath();
QUrl oldUrl = album->fileUrl();
album->setTitle(newName);
album->m_path = newName;
QUrl newUrl = album->fileUrl();
QString newAlbumPath = album->albumPath();
// We use a private shortcut around collection scanner noticing our changes,
// we rename them directly. Faster.
ScanController::instance()->suspendCollectionScan();
bool ret = QDir().rename(oldUrl.toLocalFile(), newUrl.toLocalFile());
if (!ret)
{
ScanController::instance()->resumeCollectionScan();
errMsg = i18n("Failed to rename Album");
return false;
}
// now rename the album and subalbums in the database
{
CoreDbAccess access;
ChangingDB changing(d);
access.db()->renameAlbum(album->id(), album->albumRootId(), album->albumPath());
PAlbum* subAlbum = 0;
AlbumIterator it(album);
while ((subAlbum = static_cast(it.current())) != 0)
{
subAlbum->m_parentPath = newAlbumPath + subAlbum->m_parentPath.mid(oldAlbumPath.length());
access.db()->renameAlbum(subAlbum->id(), album->albumRootId(), subAlbum->albumPath());
emit signalAlbumNewPath(subAlbum);
++it;
}
}
updateAlbumPathHash();
emit signalAlbumRenamed(album);
ScanController::instance()->resumeCollectionScan();
return true;
}
void AlbumManager::updateAlbumPathHash()
{
// Update AlbumDict. basically clear it and rebuild from scratch
d->albumPathHash.clear();
AlbumIterator it(d->rootPAlbum);
PAlbum* subAlbum = 0;
while ((subAlbum = static_cast(it.current())) != 0)
{
d->albumPathHash[subAlbum] = subAlbum;
++it;
}
}
bool AlbumManager::updatePAlbumIcon(PAlbum* album, qlonglong iconID, QString& errMsg)
{
if (!album)
{
errMsg = i18n("No such album");
return false;
}
if (album == d->rootPAlbum)
{
errMsg = i18n("Cannot edit root album");
return false;
}
{
CoreDbAccess access;
ChangingDB changing(d);
access.db()->setAlbumIcon(album->id(), iconID);
album->m_iconId = iconID;
}
emit signalAlbumIconChanged(album);
return true;
}
qlonglong AlbumManager::getItemFromAlbum(PAlbum* album, const QString& fileName)
{
return CoreDbAccess().db()->getItemFromAlbum(album->id(), fileName);
}
TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const QString& name,
const QString& iconkde, QString& errMsg)
{
if (!parent)
{
errMsg = i18n("No parent found for tag");
return 0;
}
// sanity checks
if (name.isEmpty())
{
errMsg = i18n("Tag name cannot be empty");
return 0;
}
if (name.contains(QLatin1Char('/')))
{
errMsg = i18n("Tag name cannot contain '/'");
return 0;
}
// first check if we have another album with the same name
if (hasDirectChildAlbumWithTitle(parent, name))
{
errMsg = i18n("Tag name already exists");
return 0;
}
ChangingDB changing(d);
int id = CoreDbAccess().db()->addTag(parent->id(), name, iconkde, 0);
if (id == -1)
{
errMsg = i18n("Failed to add tag to database");
return 0;
}
TAlbum* const album = new TAlbum(name, id, false);
album->m_icon = iconkde;
insertTAlbum(album, parent);
TAlbum* personParentTag = findTAlbum(FaceTags::personParentTag());
if (personParentTag && personParentTag->isAncestorOf(album))
{
FaceTags::ensureIsPerson(album->id());
}
emit signalAlbumsUpdated(Album::TAG);
return album;
}
AlbumList AlbumManager::findOrCreateTAlbums(const QStringList& tagPaths)
{
// find tag ids for tag paths in list, create if they don't exist
QList tagIDs = TagsCache::instance()->getOrCreateTags(tagPaths);
// create TAlbum objects for the newly created tags
scanTAlbums();
AlbumList resultList;
for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
{
resultList.append(findTAlbum(*it));
}
return resultList;
}
bool AlbumManager::deleteTAlbum(TAlbum* album, QString& errMsg, bool askUser)
{
if (!album)
{
errMsg = i18n("No such album");
return false;
}
if (album == d->rootTAlbum)
{
errMsg = i18n("Cannot delete Root Tag");
return false;
}
QList imageIds;
if (askUser)
{
imageIds = CoreDbAccess().db()->getItemIDsInTag(album->id());
}
{
CoreDbAccess access;
ChangingDB changing(d);
access.db()->deleteTag(album->id());
Album* subAlbum = 0;
AlbumIterator it(album);
while ((subAlbum = it.current()) != 0)
{
access.db()->deleteTag(subAlbum->id());
++it;
}
}
removeTAlbum(album);
emit signalAlbumsUpdated(Album::TAG);
if (askUser)
{
askUserForWriteChangedTAlbumToFiles(imageIds);
}
return true;
}
bool AlbumManager::hasDirectChildAlbumWithTitle(Album* parent, const QString& title)
{
Album* sibling = parent->firstChild();
while (sibling)
{
if (sibling->title() == title)
{
return true;
}
sibling = sibling->next();
}
return false;
}
bool AlbumManager::renameTAlbum(TAlbum* album, const QString& name,
QString& errMsg)
{
if (!album)
{
errMsg = i18n("No such album");
return false;
}
if (album == d->rootTAlbum)
{
errMsg = i18n("Cannot edit root tag");
return false;
}
if (name.contains(QLatin1Char('/')))
{
errMsg = i18n("Tag name cannot contain '/'");
return false;
}
// first check if we have another sibling with the same name
if (hasDirectChildAlbumWithTitle(album->m_parent, name))
{
errMsg = i18n("Another tag with the same name already exists.\n"
"Please choose another name.");
return false;
}
ChangingDB changing(d);
CoreDbAccess().db()->setTagName(album->id(), name);
album->setTitle(name);
emit signalAlbumRenamed(album);
askUserForWriteChangedTAlbumToFiles(album);
return true;
}
bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum* newParent, QString& errMsg)
{
if (!album)
{
errMsg = i18n("No such album");
return false;
}
if (!newParent)
{
errMsg = i18n("Attempt to move TAlbum to nowhere");
return false;
}
if (album == d->rootTAlbum)
{
errMsg = i18n("Cannot move root tag");
return false;
}
if (hasDirectChildAlbumWithTitle(newParent, album->title()))
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
qApp->applicationName(),
i18n("Another tag with the same name already exists.\n"
"Do you want to merge the tags?"),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
int result = msgBox->exec();
delete msgBox;
if (result == QMessageBox::Yes)
{
TAlbum* const destAlbum = findTAlbum(newParent->tagPath() +
QLatin1Char('/') +
album->title());
return mergeTAlbum(album, destAlbum, false, errMsg);
}
else
{
return true;
}
}
d->currentlyMovingAlbum = album;
emit signalAlbumAboutToBeMoved(album);
emit signalAlbumAboutToBeDeleted(album);
if (album->parent())
{
album->parent()->removeChild(album);
}
album->setParent(0);
emit signalAlbumDeleted(album);
emit signalAlbumHasBeenDeleted(reinterpret_cast(album));
emit signalAlbumAboutToBeAdded(album, newParent, newParent->lastChild());
ChangingDB changing(d);
CoreDbAccess().db()->setTagParentID(album->id(), newParent->id());
album->setParent(newParent);
emit signalAlbumAdded(album);
emit signalAlbumMoved(album);
emit signalAlbumsUpdated(Album::TAG);
d->currentlyMovingAlbum = 0;
TAlbum* personParentTag = findTAlbum(FaceTags::personParentTag());
if (personParentTag && personParentTag->isAncestorOf(album))
{
FaceTags::ensureIsPerson(album->id());
}
askUserForWriteChangedTAlbumToFiles(album);
return true;
}
bool AlbumManager::mergeTAlbum(TAlbum* album, TAlbum* destAlbum, bool dialog, QString& errMsg)
{
if (!album || !destAlbum)
{
errMsg = i18n("No such album");
return false;
}
if (album == d->rootTAlbum || destAlbum == d->rootTAlbum)
{
errMsg = i18n("Cannot merge root tag");
return false;
}
if (album->firstChild())
{
errMsg = i18n("Only a tag without children can be merged!");
return false;
}
if (dialog)
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
qApp->applicationName(),
i18n("Do you want to merge tag '%1' into tag '%2'?",
album->title(), destAlbum->title()),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
int result = msgBox->exec();
delete msgBox;
if (result == QMessageBox::No)
{
return true;
}
}
int oldId = album->id();
int mergeId = destAlbum->id();
if (oldId == mergeId)
{
return true;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
QList imageIds = CoreDbAccess().db()->getItemIDsInTag(oldId);
CoreDbOperationGroup group;
group.setMaximumTime(200);
foreach(const qlonglong& imageId, imageIds)
{
QList facesList = FaceTagsEditor().databaseFaces(imageId);
bool foundFace = false;
foreach(const FaceTagsIface& face, facesList)
{
if (face.tagId() == oldId)
{
foundFace = true;
FaceTagsEditor().removeFace(face);
FaceTagsEditor().add(imageId, mergeId, face.region(), false);
}
}
if (!foundFace)
{
ImageInfo info(imageId);
info.removeTag(oldId);
info.setTag(mergeId);
group.allowLift();
}
}
QApplication::restoreOverrideCursor();
if (!deleteTAlbum(album, errMsg, false))
{
return false;
}
askUserForWriteChangedTAlbumToFiles(imageIds);
return true;
}
bool AlbumManager::updateTAlbumIcon(TAlbum* album, const QString& iconKDE,
qlonglong iconID, QString& errMsg)
{
if (!album)
{
errMsg = i18n("No such tag");
return false;
}
if (album == d->rootTAlbum)
{
errMsg = i18n("Cannot edit root tag");
return false;
}
{
CoreDbAccess access;
ChangingDB changing(d);
access.db()->setTagIcon(album->id(), iconKDE, iconID);
album->m_icon = iconKDE;
album->m_iconId = iconID;
}
emit signalAlbumIconChanged(album);
return true;
}
AlbumList AlbumManager::getRecentlyAssignedTags(bool includeInternal) const
{
QList tagIDs = CoreDbAccess().db()->getRecentlyAssignedTags();
AlbumList resultList;
for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
{
TAlbum* const album = findTAlbum(*it);
if (album)
{
if (!includeInternal && album->isInternalTag())
{
continue;
}
resultList.append(album);
}
}
return resultList;
}
QStringList AlbumManager::tagPaths(const QList& tagIDs, bool leadingSlash, bool includeInternal) const
{
QStringList tagPaths;
for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
{
TAlbum* album = findTAlbum(*it);
if (album)
{
if (!includeInternal && album->isInternalTag())
{
continue;
}
tagPaths.append(album->tagPath(leadingSlash));
}
}
return tagPaths;
}
QStringList AlbumManager::tagNames(const QList& tagIDs, bool includeInternal) const
{
QStringList tagNames;
foreach(int id, tagIDs)
{
TAlbum* const album = findTAlbum(id);
if (album)
{
if (!includeInternal && album->isInternalTag())
{
continue;
}
tagNames << album->title();
}
}
return tagNames;
}
QHash AlbumManager::tagPaths(bool leadingSlash, bool includeInternal) const
{
QHash hash;
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
TAlbum* const t = (TAlbum*)(*it);
if (includeInternal || !t->isInternalTag())
{
hash.insert(t->id(), t->tagPath(leadingSlash));
}
++it;
}
return hash;
}
QHash AlbumManager::tagNames(bool includeInternal) const
{
QHash hash;
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
TAlbum* const t = (TAlbum*)(*it);
if (includeInternal || !t->isInternalTag())
{
hash.insert(t->id(), t->title());
}
++it;
}
return hash;
}
QList< int > AlbumManager::subTags(int tagId, bool recursive)
{
TAlbum* const album = this->findTAlbum(tagId);
return album->childAlbumIds(recursive);
}
AlbumList AlbumManager::findTagsWithProperty(const QString& property)
{
AlbumList list;
QList ids = TagsCache::instance()->tagsWithProperty(property);
foreach(int id, ids)
{
TAlbum* const album = findTAlbum(id);
if (album)
{
list << album;
}
}
return list;
}
AlbumList AlbumManager::findTagsWithProperty(const QString& property, const QString& value)
{
AlbumList list;
AlbumIterator it(d->rootTAlbum);
while (it.current())
{
if (static_cast(*it)->property(property) == value)
{
list << *it;
}
++it;
}
return list;
}
QHash AlbumManager::albumTitles() const
{
QHash hash;
AlbumIterator it(d->rootPAlbum);
while (it.current())
{
PAlbum* const a = (PAlbum*)(*it);
hash.insert(a->id(), a->title());
++it;
}
return hash;
}
SAlbum* AlbumManager::createSAlbum(const QString& name, DatabaseSearch::Type type, const QString& query)
{
// first iterate through all the search albums and see if there's an existing
// SAlbum with same name. (Remember, SAlbums are arranged in a flat list)
SAlbum* album = findSAlbum(name);
ChangingDB changing(d);
if (album)
{
updateSAlbum(album, query, name, type);
return album;
}
int id = CoreDbAccess().db()->addSearch(type, name, query);
if (id == -1)
{
return 0;
}
album = new SAlbum(name, id);
emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild());
album->setSearch(type, query);
album->setParent(d->rootSAlbum);
d->allAlbumsIdHash.insert(album->globalID(), album);
emit signalAlbumAdded(album);
return album;
}
bool AlbumManager::updateSAlbum(SAlbum* album, const QString& changedQuery,
const QString& changedName, DatabaseSearch::Type type)
{
if (!album)
{
return false;
}
QString newName = changedName.isNull() ? album->title() : changedName;
DatabaseSearch::Type newType = (type == DatabaseSearch::UndefinedType) ? album->searchType() : type;
ChangingDB changing(d);
CoreDbAccess().db()->updateSearch(album->id(), newType, newName, changedQuery);
QString oldName = album->title();
album->setSearch(newType, changedQuery);
album->setTitle(newName);
if (oldName != album->title())
{
emit signalAlbumRenamed(album);
}
if (!d->currentAlbums.isEmpty())
{
if (d->currentAlbums.first() == album)
{
emit signalAlbumCurrentChanged(d->currentAlbums);
}
}
return true;
}
bool AlbumManager::deleteSAlbum(SAlbum* album)
{
if (!album)
{
return false;
}
emit signalAlbumAboutToBeDeleted(album);
ChangingDB changing(d);
CoreDbAccess().db()->deleteSearch(album->id());
d->allAlbumsIdHash.remove(album->globalID());
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
return true;
}
QMap AlbumManager::getPAlbumsCount() const
{
return d->pAlbumsCount;
}
QMap AlbumManager::getTAlbumsCount() const
{
return d->tAlbumsCount;
}
QMap AlbumManager::getDAlbumsCount() const
{
return d->dAlbumsCount;
}
QMap AlbumManager::getFaceCount() const
{
return d->fAlbumsCount;
}
bool AlbumManager::isMovingAlbum(Album* album) const
{
return d->currentlyMovingAlbum == album;
}
void AlbumManager::insertPAlbum(PAlbum* album, PAlbum* parent)
{
if (!album)
{
return;
}
emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0);
if (parent)
{
album->setParent(parent);
}
d->albumPathHash[album] = album;
d->allAlbumsIdHash[album->globalID()] = album;
emit signalAlbumAdded(album);
}
void AlbumManager::removePAlbum(PAlbum* album)
{
if (!album)
{
return;
}
// remove all children of this album
Album* child = album->firstChild();
PAlbum* toBeRemoved = 0;
while (child)
{
Album* next = child->next();
toBeRemoved = static_cast(child);
if (toBeRemoved)
{
removePAlbum(toBeRemoved);
toBeRemoved = 0;
}
child = next;
}
emit signalAlbumAboutToBeDeleted(album);
d->albumPathHash.remove(album);
d->allAlbumsIdHash.remove(album->globalID());
CoreDbUrl url = album->databaseUrl();
if (!d->currentAlbums.isEmpty())
{
if (album == d->currentAlbums.first())
{
d->currentAlbums.clear();
emit signalAlbumCurrentChanged(d->currentAlbums);
}
}
if (album->isAlbumRoot())
{
d->albumRootAlbumHash.remove(album->albumRootId());
}
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
}
void AlbumManager::insertTAlbum(TAlbum* album, TAlbum* parent)
{
if (!album)
{
return;
}
emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0);
if (parent)
{
album->setParent(parent);
}
d->allAlbumsIdHash.insert(album->globalID(), album);
emit signalAlbumAdded(album);
}
void AlbumManager::removeTAlbum(TAlbum* album)
{
if (!album)
{
return;
}
// remove all children of this album
Album* child = album->firstChild();
TAlbum* toBeRemoved = 0;
while (child)
{
Album* next = child->next();
toBeRemoved = static_cast(child);
if (toBeRemoved)
{
removeTAlbum(toBeRemoved);
toBeRemoved = 0;
}
child = next;
}
emit signalAlbumAboutToBeDeleted(album);
d->allAlbumsIdHash.remove(album->globalID());
if (!d->currentAlbums.isEmpty())
{
if (album == d->currentAlbums.first())
{
d->currentAlbums.clear();
emit signalAlbumCurrentChanged(d->currentAlbums);
}
}
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
}
void AlbumManager::notifyAlbumDeletion(Album* album)
{
invalidateGuardedPointers(album);
}
void AlbumManager::slotAlbumsJobResult()
{
if (!d->albumListJob)
{
return;
}
if (d->albumListJob->hasErrors())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list albums";
// Pop-up a message about the error.
DNotificationWrapper(QString(), d->albumListJob->errorsList().first(),
0, i18n("digiKam"));
}
d->albumListJob = 0;
}
void AlbumManager::slotAlbumsJobData(const QMap &albumsStatMap)
{
if (albumsStatMap.isEmpty())
{
return;
}
d->pAlbumsCount = albumsStatMap;
emit signalPAlbumsDirty(albumsStatMap);
}
void AlbumManager::slotPeopleJobResult()
{
if (!d->personListJob)
{
return;
}
if (d->personListJob->hasErrors())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list face tags";
// Pop-up a message about the error.
DNotificationWrapper(QString(), d->personListJob->errorsList().first(),
0, i18n("digiKam"));
}
d->personListJob = 0;
}
void AlbumManager::slotPeopleJobData(const QMap >& facesStatMap)
{
if (facesStatMap.isEmpty())
{
return;
}
// For now, we only use the sum of confirmed and unconfirmed faces
d->fAlbumsCount.clear();
typedef QMap IntIntMap;
foreach(const IntIntMap& counts, facesStatMap)
{
QMap::const_iterator it;
for (it = counts.begin() ; it != counts.end() ; ++it)
{
d->fAlbumsCount[it.key()] += it.value();
}
}
emit signalFaceCountsDirty(d->fAlbumsCount);
}
void AlbumManager::slotTagsJobResult()
{
if (!d->tagListJob)
{
return;
}
if (d->tagListJob->hasErrors())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list face tags";
// Pop-up a message about the error.
DNotificationWrapper(QString(), d->personListJob->errorsList().first(),
0, i18n("digiKam"));
}
d->tagListJob = 0;
}
void AlbumManager::slotTagsJobData(const QMap& tagsStatMap)
{
if (tagsStatMap.isEmpty())
{
return;
}
d->tAlbumsCount = tagsStatMap;
emit signalTAlbumsDirty(tagsStatMap);
}
void AlbumManager::slotDatesJobResult()
{
if (!d->dateListJob)
{
return;
}
if (d->dateListJob->hasErrors())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list dates";
// Pop-up a message about the error.
DNotificationWrapper(QString(), d->dateListJob->errorsList().first(),
0, i18n("digiKam"));
}
d->dateListJob = 0;
emit signalAllDAlbumsLoaded();
}
void AlbumManager::slotDatesJobData(const QMap& datesStatMap)
{
if (datesStatMap.isEmpty() || !d->rootDAlbum)
{
return;
}
// insert all the DAlbums into a qmap for quick access
QMap mAlbumMap;
QMap yAlbumMap;
AlbumIterator it(d->rootDAlbum);
while (it.current())
{
DAlbum* const a = (DAlbum*)(*it);
if (a->range() == DAlbum::Month)
{
mAlbumMap.insert(a->date(), a);
}
else
{
yAlbumMap.insert(a->date().year(), a);
}
++it;
}
QMap yearMonthMap;
for (QMap::const_iterator it = datesStatMap.constBegin() ; it != datesStatMap.constEnd() ; ++it)
{
YearMonth yearMonth = YearMonth(it.key().date().year(), it.key().date().month());
QMap::iterator it2 = yearMonthMap.find(yearMonth);
if (it2 == yearMonthMap.end())
{
yearMonthMap.insert(yearMonth, *it);
}
else
{
*it2 += *it;
}
}
int year, month;
for (QMap::const_iterator iter = yearMonthMap.constBegin() ; iter != yearMonthMap.constEnd() ; ++iter)
{
year = iter.key().first;
month = iter.key().second;
QDate md(year, month, 1);
// Do we already have this Month album
if (mAlbumMap.contains(md))
{
// already there. remove Month album from map
mAlbumMap.remove(md);
if (yAlbumMap.contains(year))
{
// already there. remove from map
yAlbumMap.remove(year);
}
continue;
}
// Check if Year Album already exist.
DAlbum* yAlbum = 0;
AlbumIterator it(d->rootDAlbum);
while (it.current())
{
DAlbum* const a = (DAlbum*)(*it);
if (a->date() == QDate(year, 1, 1) && a->range() == DAlbum::Year)
{
yAlbum = a;
break;
}
++it;
}
// If no, create Year album.
if (!yAlbum)
{
yAlbum = new DAlbum(QDate(year, 1, 1), false, DAlbum::Year);
emit signalAlbumAboutToBeAdded(yAlbum, d->rootDAlbum, d->rootDAlbum->lastChild());
yAlbum->setParent(d->rootDAlbum);
d->allAlbumsIdHash.insert(yAlbum->globalID(), yAlbum);
emit signalAlbumAdded(yAlbum);
}
// Create Month album
DAlbum* mAlbum = new DAlbum(md);
emit signalAlbumAboutToBeAdded(mAlbum, yAlbum, yAlbum->lastChild());
mAlbum->setParent(yAlbum);
d->allAlbumsIdHash.insert(mAlbum->globalID(), mAlbum);
emit signalAlbumAdded(mAlbum);
}
// Now the items contained in the maps are the ones which
// have been deleted.
for (QMap::const_iterator it = mAlbumMap.constBegin() ; it != mAlbumMap.constEnd() ; ++it)
{
DAlbum* const album = it.value();
emit signalAlbumAboutToBeDeleted(album);
d->allAlbumsIdHash.remove(album->globalID());
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
}
for (QMap::const_iterator it = yAlbumMap.constBegin() ; it != yAlbumMap.constEnd() ; ++it)
{
DAlbum* const album = it.value();
emit signalAlbumAboutToBeDeleted(album);
d->allAlbumsIdHash.remove(album->globalID());
emit signalAlbumDeleted(album);
quintptr deletedAlbum = reinterpret_cast(album);
delete album;
emit signalAlbumHasBeenDeleted(deletedAlbum);
}
d->dAlbumsCount = yearMonthMap;
emit signalDAlbumsDirty(yearMonthMap);
emit signalDatesMapDirty(datesStatMap);
}
void AlbumManager::slotAlbumChange(const AlbumChangeset& changeset)
{
if (d->changingDB || !d->rootPAlbum)
{
return;
}
switch (changeset.operation())
{
case AlbumChangeset::Added:
case AlbumChangeset::Deleted:
if (!d->scanPAlbumsTimer->isActive())
{
d->scanPAlbumsTimer->start();
}
break;
case AlbumChangeset::Renamed:
case AlbumChangeset::PropertiesChanged:
// mark for rescan
d->changedPAlbums << changeset.albumId();
if (!d->updatePAlbumsTimer->isActive())
{
d->updatePAlbumsTimer->start();
}
break;
case AlbumChangeset::Unknown:
break;
}
}
void AlbumManager::slotTagChange(const TagChangeset& changeset)
{
if (d->changingDB || !d->rootTAlbum)
{
return;
}
switch (changeset.operation())
{
case TagChangeset::Added:
case TagChangeset::Moved:
case TagChangeset::Deleted:
case TagChangeset::Reparented:
if (!d->scanTAlbumsTimer->isActive())
{
d->scanTAlbumsTimer->start();
}
break;
case TagChangeset::Renamed:
case TagChangeset::IconChanged:
/**
* @todo what happens here?
*/
break;
case TagChangeset::PropertiesChanged:
{
TAlbum* tag = findTAlbum(changeset.tagId());
if (tag)
{
emit signalTagPropertiesChanged(tag);
}
break;
}
case TagChangeset::Unknown:
break;
}
}
void AlbumManager::slotSearchChange(const SearchChangeset& changeset)
{
if (d->changingDB || !d->rootSAlbum)
{
return;
}
switch (changeset.operation())
{
case SearchChangeset::Added:
case SearchChangeset::Deleted:
if (!d->scanSAlbumsTimer->isActive())
{
d->scanSAlbumsTimer->start();
}
break;
case SearchChangeset::Changed:
if (!d->currentAlbums.isEmpty())
{
Album* currentAlbum = d->currentAlbums.first();
if (currentAlbum && currentAlbum->type() == Album::SEARCH
&& currentAlbum->id() == changeset.searchId())
{
// the pointer is the same, but the contents changed
emit signalAlbumCurrentChanged(d->currentAlbums);
}
}
break;
case SearchChangeset::Unknown:
break;
}
}
void AlbumManager::slotCollectionImageChange(const CollectionImageChangeset& changeset)
{
if (!d->rootDAlbum)
{
return;
}
switch (changeset.operation())
{
case CollectionImageChangeset::Added:
case CollectionImageChangeset::Deleted:
case CollectionImageChangeset::Removed:
case CollectionImageChangeset::RemovedAll:
if (!d->scanDAlbumsTimer->isActive())
{
d->scanDAlbumsTimer->start();
}
if (!d->albumItemCountTimer->isActive())
{
d->albumItemCountTimer->start();
}
break;
default:
break;
}
}
void AlbumManager::slotImageTagChange(const ImageTagChangeset& changeset)
{
if (!d->rootTAlbum)
{
return;
}
switch (changeset.operation())
{
case ImageTagChangeset::Added:
case ImageTagChangeset::Removed:
case ImageTagChangeset::RemovedAll:
// Add properties changed.
// Reason: in people sidebar, the images are not
// connected with the ImageTag table but by
// ImageTagProperties entries.
// Thus, the count of entries in face tags are not
// updated. This adoption should fix the problem.
case ImageTagChangeset::PropertiesChanged:
if (!d->tagItemCountTimer->isActive())
{
d->tagItemCountTimer->start();
}
break;
default:
break;
}
}
void AlbumManager::slotImagesDeleted(const QList& imageIds)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Got image deletion notification from ImageViewUtilities for " << imageIds.size() << " images.";
QSet sAlbumsToUpdate;
QSet deletedImages = imageIds.toSet();
QList sAlbums = findSAlbumsBySearchType(DatabaseSearch::DuplicatesSearch);
foreach(SAlbum* const sAlbum, sAlbums)
{
// Read the search query XML and save the image ids
SearchXmlReader reader(sAlbum->query());
SearchXml::Element element;
QSet images;
while ((element = reader.readNext()) != SearchXml::End)
{
if ((element == SearchXml::Field) && (reader.fieldName().compare(QLatin1String("imageid")) == 0))
{
images = reader.valueToLongLongList().toSet();
}
}
// If the deleted images are part of the SAlbum,
// mark the album as ready for deletion and the images as ready for rescan.
#if QT_VERSION >= 0x050600
if (images.intersects(deletedImages))
#else
if (images.intersect(deletedImages).isEmpty())
#endif
{
sAlbumsToUpdate.insert(sAlbum);
}
}
if (!sAlbumsToUpdate.isEmpty())
{
emit signalUpdateDuplicatesAlbums(sAlbumsToUpdate.toList(), deletedImages.toList());
}
}
void AlbumManager::askUserForWriteChangedTAlbumToFiles(TAlbum* const album)
{
QList imageIds = CoreDbAccess().db()->getItemIDsInTag(album->id());
askUserForWriteChangedTAlbumToFiles(imageIds);
}
void AlbumManager::askUserForWriteChangedTAlbumToFiles(const QList& imageIds)
{
MetadataSettings* const settings = MetadataSettings::instance();
if ((!settings->settings().saveTags &&
!settings->settings().saveFaceTags) || imageIds.isEmpty())
{
return;
}
if (imageIds.count() > 100)
{
QPointer msgBox = new QMessageBox(QMessageBox::Warning,
qApp->applicationName(),
i18n("This operation can take a long time in the background.\n"
"Do you want to write the metadata to %1 files now?",
imageIds.count()),
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
int result = msgBox->exec();
delete msgBox;
if (result != QMessageBox::Yes)
{
return;
}
}
ImageInfoList infos(imageIds);
MetadataSynchronizer* const tool = new MetadataSynchronizer(infos, MetadataSynchronizer::WriteFromDatabaseToFile);
tool->start();
}
void AlbumManager::removeWatchedPAlbums(const PAlbum* const album)
{
d->albumWatch->removeWatchedPAlbums(album);
}
void AlbumManager::addFakeConnection()
{
if (!d->dbFakeConnection)
{
// workaround for the problem mariaDB >= 10.2 and QTBUG-63108
QSqlDatabase::addDatabase(QLatin1String("QMYSQL"), QLatin1String("FakeConnection"));
d->dbFakeConnection = true;
}
}
void AlbumManager::removeFakeConnection()
{
if (d->dbFakeConnection)
{
QSqlDatabase::removeDatabase(QLatin1String("FakeConnection"));
}
}
} // namespace Digikam
diff --git a/core/libs/database/utils/dbsettingswidget.cpp b/core/libs/database/utils/dbsettingswidget.cpp
index 435a5eddae..aec4167196 100644
--- a/core/libs/database/utils/dbsettingswidget.cpp
+++ b/core/libs/database/utils/dbsettingswidget.cpp
@@ -1,950 +1,950 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-11-14
* Description : database settings widget
*
* Copyright (C) 2009-2010 by Holger Foerster
* Copyright (C) 2010-2018 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 "dbsettingswidget.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "applicationsettings.h"
#include "dfileselector.h"
#include "dbengineparameters.h"
#include "dbinarysearch.h"
#include "dexpanderbox.h"
#include "digikam_config.h"
#include "digikam_debug.h"
#include "dlayoutbox.h"
#include "mysqlinitbinary.h"
#include "mysqlservbinary.h"
#include "albummanager.h"
namespace Digikam
{
class Q_DECL_HIDDEN DatabaseSettingsWidget::Private
{
public:
explicit Private()
{
mysqlCmdBox = 0;
dbType = 0;
dbPathLabel = 0;
expertSettings = 0;
dbNoticeBox = 0;
sqlInit = 0;
dbNameCore = 0;
dbNameThumbs = 0;
dbNameFace = 0;
dbNameSimilarity = 0;
hostName = 0;
connectOpts = 0;
userName = 0;
password = 0;
hostPort = 0;
dbPathEdit = 0;
dbBinariesWidget = 0;
tab = 0;
dbDetailsBox = 0;
ignoreDirectoriesBox = 0;
ignoreDirectoriesEdit = 0;
ignoreDirectoriesLabel = 0;
}
DVBox* mysqlCmdBox;
QLineEdit* dbNameCore;
QLineEdit* dbNameThumbs;
QLineEdit* dbNameFace;
QLineEdit* dbNameSimilarity;
QLineEdit* hostName;
QLineEdit* connectOpts;
QLineEdit* userName;
QLineEdit* password;
QSpinBox* hostPort;
QComboBox* dbType;
QLabel* dbPathLabel;
QTextBrowser* sqlInit;
QGroupBox* expertSettings;
QGroupBox* dbNoticeBox;
QGroupBox* dbDetailsBox;
QTabWidget* tab;
DFileSelector* dbPathEdit;
DBinarySearch* dbBinariesWidget;
MysqlInitBinary mysqlInitBin;
MysqlServBinary mysqlServBin;
DbEngineParameters orgPrms;
QMap dbTypeMap;
QGroupBox* ignoreDirectoriesBox;
QLineEdit* ignoreDirectoriesEdit;
QLabel* ignoreDirectoriesLabel;
};
DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* const parent)
: QWidget(parent),
d(new Private)
{
setupMainArea();
}
DatabaseSettingsWidget::~DatabaseSettingsWidget()
{
delete d;
}
void DatabaseSettingsWidget::setupMainArea()
{
QVBoxLayout* const layout = new QVBoxLayout();
setLayout(layout);
// --------------------------------------------------------
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
QGroupBox* const dbConfigBox = new QGroupBox(i18n("Database Configuration"), this);
QVBoxLayout* const vlay = new QVBoxLayout(dbConfigBox);
DHBox* const typeHbox = new DHBox();
QLabel* const databaseTypeLabel = new QLabel(typeHbox);
d->dbType = new QComboBox(typeHbox);
databaseTypeLabel->setText(i18n("Type:"));
// --------- fill with default values ---------------------
int dbTypeIdx = 0;
d->dbType->addItem(i18n("SQLite"), SQlite);
d->dbTypeMap[SQlite] = dbTypeIdx++;
#ifdef HAVE_MYSQLSUPPORT
# ifdef HAVE_INTERNALMYSQL
d->dbType->addItem(i18n("Mysql Internal (experimental)"), MysqlInternal);
d->dbTypeMap[MysqlInternal] = dbTypeIdx++;
# endif
d->dbType->addItem(i18n("MySQL Server (experimental)"), MysqlServer);
d->dbTypeMap[MysqlServer] = dbTypeIdx++;
#endif
d->dbType->setToolTip(i18n("Select here the type of database backend.
"
"SQlite backend is for local database storage with a small or medium collection sizes. "
"It is the default and recommended backend for collections with less than 100K items.
"
#ifdef HAVE_MYSQLSUPPORT
# ifdef HAVE_INTERNALMYSQL
"MySQL Internal backend is for local database storage with huge collection sizes. "
"This backend is recommend for local collections with more than 100K items.
"
"Be careful: this one still in experimental stage.
"
# endif
"MySQL Server backend is a more robust solution especially for remote and shared database storage. "
"It is also more efficient to manage huge collection sizes with more than 100K items.
"
"Be careful: this one still in experimental stage.
"
#endif
));
// --------------------------------------------------------
d->dbPathLabel = new QLabel(i18n("Set here the location where the database files will be stored on your system. "
"There are three databases: one for all collections properties, "
"one to store compressed thumbnails, "
"and one to store faces recognition metadata.
"
"Write access is required to be able to edit image properties.
"
"Databases are digiKam core engines. Take care to use a place hosted by fast "
"hardware (as SSD) with enough free space especially for thumbnails database.
"
"Note: a remote file system such as NFS, cannot be used here. "
"For performance reasons, it is also recommended not to use removable media.
"
""), dbConfigBox);
d->dbPathLabel->setWordWrap(true);
d->dbPathEdit = new DFileSelector(dbConfigBox);
d->dbPathEdit->setFileDlgMode(QFileDialog::Directory);
// --------------------------------------------------------
d->mysqlCmdBox = new DVBox(dbConfigBox);
d->mysqlCmdBox->layout()->setMargin(0);
new DLineWidget(Qt::Horizontal, d->mysqlCmdBox);
QLabel* const mysqlBinariesLabel = new QLabel(i18n("Here you can configure locations where MySQL binary tools are located. "
"digiKam will try to find these binaries automatically if they are "
"already installed on your computer.
"),
d->mysqlCmdBox);
mysqlBinariesLabel->setWordWrap(true);
QGroupBox* const binaryBox = new QGroupBox(d->mysqlCmdBox);
QGridLayout* const binaryLayout = new QGridLayout;
binaryBox->setLayout(binaryLayout);
binaryBox->setTitle(i18nc("@title:group", "MySQL Binaries"));
d->dbBinariesWidget = new DBinarySearch(binaryBox);
d->dbBinariesWidget->header()->setSectionHidden(2, true);
d->dbBinariesWidget->addBinary(d->mysqlInitBin);
d->dbBinariesWidget->addBinary(d->mysqlServBin);
#ifdef Q_OS_LINUX
d->dbBinariesWidget->addDirectory(QLatin1String("/usr/bin"));
d->dbBinariesWidget->addDirectory(QLatin1String("/usr/sbin"));
#endif
#ifdef Q_OS_OSX
// Std Macports install
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/bin"));
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/sbin"));
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/lib/mariadb/bin"));
// digiKam Bundle PKG install
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/bin"));
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/sbin"));
d->dbBinariesWidget->addDirectory(QLatin1String("/opt/digikam/lib/mariadb/bin"));
#endif
#ifdef Q_OS_WIN
d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files/MariaDB 10.1/bin"));
d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files (x86/MariaDB 10.1/bin"));
#endif
d->dbBinariesWidget->allBinariesFound();
// --------------------------------------------------------
d->tab = new QTabWidget(this);
QLabel* const hostNameLabel = new QLabel(i18n("Host Name:"));
d->hostName = new QLineEdit();
d->hostName->setPlaceholderText(i18n("Set the host computer name"));
d->hostName->setToolTip(i18n("This is the computer name running MySQL server.\nThis can be \"localhost\" for a local server, "
"or the network computer\n name (or IP address) in case of remote computer."));
QLabel* const connectOptsLabel = new QLabel(i18n("Connect options:"));
connectOptsLabel->setOpenExternalLinks(true);
d->connectOpts = new QLineEdit();
d->connectOpts->setPlaceholderText(i18n("Set the database connection options"));
d->connectOpts->setToolTip(i18n("Set the MySQL server connection options.\nFor advanced users only."));
QLabel* const userNameLabel = new QLabel(i18n("User:"));
d->userName = new QLineEdit();
d->userName->setPlaceholderText(i18n("Set the database account name"));
d->userName->setToolTip(i18n("Set the MySQL server account name used\nby digiKam to be connected to the server.\n"
"This account must be available on the remote MySQL server when database have been created."));
QLabel* const passwordLabel = new QLabel(i18n("Password:"));
d->password = new QLineEdit();
d->password->setToolTip(i18n("Set the MySQL server account password used\nby digiKam to be connected to the server.\n"
"You can left this field empty to use an account set without password."));
d->password->setEchoMode(QLineEdit::Password);
DHBox* const phbox = new DHBox();
QLabel* const hostPortLabel = new QLabel(i18n("Host Port:"));
d->hostPort = new QSpinBox(phbox);
d->hostPort->setToolTip(i18n("Set the host computer port.\nUsually, MySQL server use port number 3306 by default"));
d->hostPort->setMaximum(65535);
d->hostPort->setValue(3306);
QWidget* const space = new QWidget(phbox);
phbox->setStretchFactor(space, 10);
QPushButton* const checkDBConnectBtn = new QPushButton(i18n("Check Connection"), phbox);
checkDBConnectBtn->setToolTip(i18n("Run a basic database connection to see if current MySQL server settings is suitable."));
// Only accept printable Ascii char for database names.
QRegExp asciiRx(QLatin1String("[\x20-\x7F]+$"));
QValidator* const asciiValidator = new QRegExpValidator(asciiRx, this);
QLabel* const dbNameCoreLabel = new QLabel(i18n("Core Db Name:"));
d->dbNameCore = new QLineEdit();
d->dbNameCore->setPlaceholderText(i18n("Set the core database name"));
d->dbNameCore->setToolTip(i18n("The core database is lead digiKam container used to store\nalbums, items, and searches metadata."));
d->dbNameCore->setValidator(asciiValidator);
QLabel* const dbNameThumbsLabel = new QLabel(i18n("Thumbs Db Name:"));
d->dbNameThumbs = new QLineEdit();
d->dbNameThumbs->setPlaceholderText(i18n("Set the thumbnails database name"));
d->dbNameThumbs->setToolTip(i18n("The thumbnails database is used by digiKam to host\nimage thumbs with wavelets compression images.\n"
"This one can use quickly a lots of space,\nespecially if you have huge collections."));
d->dbNameThumbs->setValidator(asciiValidator);
QLabel* const dbNameFaceLabel = new QLabel(i18n("Face Db Name:"));
d->dbNameFace = new QLineEdit();
d->dbNameFace->setPlaceholderText(i18n("Set the face database name"));
d->dbNameFace->setToolTip(i18n("The face database is used by digiKam to host image histograms\ndedicated to faces recognition process.\n"
"This one can use quickly a lots of space, especially\nif you a lots of image with people faces detected "
"and tagged."));
d->dbNameFace->setValidator(asciiValidator);
QLabel* const dbNameSimilarityLabel = new QLabel(i18n("Similarity Db Name:"));
d->dbNameSimilarity = new QLineEdit();
d->dbNameSimilarity->setPlaceholderText(i18n("Set the similarity database name"));
d->dbNameSimilarity->setToolTip(i18n("The similarity database is used by digiKam to host image Haar matrix data for the similarity search."));
d->dbNameSimilarity->setValidator(asciiValidator);
QPushButton* const defaultValuesBtn = new QPushButton(i18n("Default Settings"));
defaultValuesBtn->setToolTip(i18n("Reset database names settings to common default values."));
d->expertSettings = new QGroupBox();
d->expertSettings->setFlat(true);
QFormLayout* const expertSettinglayout = new QFormLayout();
d->expertSettings->setLayout(expertSettinglayout);
expertSettinglayout->addRow(hostNameLabel, d->hostName);
expertSettinglayout->addRow(userNameLabel, d->userName);
expertSettinglayout->addRow(passwordLabel, d->password);
expertSettinglayout->addRow(connectOptsLabel, d->connectOpts);
expertSettinglayout->addRow(hostPortLabel, phbox);
expertSettinglayout->addRow(new DLineWidget(Qt::Horizontal, d->expertSettings));
expertSettinglayout->addRow(dbNameCoreLabel, d->dbNameCore);
expertSettinglayout->addRow(dbNameThumbsLabel, d->dbNameThumbs);
expertSettinglayout->addRow(dbNameFaceLabel, d->dbNameFace);
expertSettinglayout->addRow(dbNameSimilarityLabel, d->dbNameSimilarity);
expertSettinglayout->addRow(new QWidget(), defaultValuesBtn);
d->tab->addTab(d->expertSettings, i18n("Remote Server Settings"));
// --------------------------------------------------------
d->dbNoticeBox = new QGroupBox(i18n("Database Server Instructions"), this);
QVBoxLayout* const vlay2 = new QVBoxLayout(d->dbNoticeBox);
QLabel* const notice = new QLabel(i18n("digiKam expects that database is already created with a dedicated user account. "
"This user name digikam will require full access to the database.
"
"If your database is not already set up, you can use the following SQL commands "
- "(after replacing the password with the correct one).
"),
+ "(after replacing the password with the correct one).
"),
d->dbNoticeBox);
notice->setWordWrap(true);
d->sqlInit = new QTextBrowser(d->dbNoticeBox);
d->sqlInit->setOpenExternalLinks(false);
d->sqlInit->setOpenLinks(false);
d->sqlInit->setReadOnly(false);
QLabel* const notice2 = new QLabel(i18n("Note: with a Linux server, a database can be initialized following the commands below:
"
"# su
"
"# systemctl restart mysqld
"
"# mysql -u root
"
"...
"
"Enter SQL code to Mysql prompt in order to init digiKam databases with grant privileges (see behind)
"
"...
"
"quit
"
"NOTE: If you have an enormous collection, you should start MySQL server with "
"mysql --max_allowed_packet=128M OR in my.ini or ~/.my.cnf, change the settings
"),
d->dbNoticeBox);
notice2->setWordWrap(true);
vlay2->addWidget(notice);
vlay2->addWidget(d->sqlInit);
vlay2->setContentsMargins(spacing, spacing, spacing, spacing);
vlay2->setSpacing(spacing);
vlay2->addWidget(notice2);
d->tab->addTab(d->dbNoticeBox, i18n("Requirements"));
// --------------------------------------------------------
d->dbDetailsBox = new QGroupBox(i18n("Database Server Technical Details"), this);
QVBoxLayout* const vlay3 = new QVBoxLayout(d->dbDetailsBox);
QLabel* const details = new QLabel(i18n("Use this configuration view to set all information "
"to be connected to a remote "
"Mysql database server "
"(or MariaDB) "
"through a network. "
"As with Sqlite or MySQL internal server, 3 databases will be stored "
"on the remote server: one for all collections properties, "
"one to store compressed thumbnails, and one to store faces "
"recognition metadata.
"
"Unlike Sqlite or MySQL internal server, you can customize the "
"database names to simplify your backups.
"
"Databases are digiKam core engines. To prevent performance issues, "
"take a care to use a fast network link between the client and the server "
"computers. It is also recommended to host database files on "
"fast hardware (as SSD) "
"with enough free space, especially for thumbnails database, even if data are compressed using wavelets image format "
"PGF.
"
"The databases must be created previously on the remote server by the administrator. "
"Look in Requirements tab for details.
"),
d->dbDetailsBox);
details->setWordWrap(true);
vlay3->addWidget(details);
vlay3->setContentsMargins(spacing, spacing, spacing, spacing);
vlay3->setSpacing(spacing);
d->tab->addTab(d->dbDetailsBox, i18n("Documentation"));
// --------------------------------------------------------
vlay->addWidget(typeHbox);
vlay->addWidget(new DLineWidget(Qt::Horizontal));
vlay->addWidget(d->dbPathLabel);
vlay->addWidget(d->dbPathEdit);
vlay->addWidget(d->mysqlCmdBox);
vlay->addWidget(d->tab);
vlay->setContentsMargins(spacing, spacing, spacing, spacing);
vlay->setSpacing(spacing);
// --------------------------------------------------------
layout->setContentsMargins(QMargins());
layout->setSpacing(spacing);
layout->addWidget(dbConfigBox);
layout->addStretch();
// --------------------------------------------------------
connect(d->dbType, SIGNAL(currentIndexChanged(int)),
this, SLOT(slotHandleDBTypeIndexChanged(int)));
connect(checkDBConnectBtn, SIGNAL(clicked()),
this, SLOT(slotCheckMysqlServerConnection()));
connect(defaultValuesBtn, SIGNAL(clicked()),
this, SLOT(slotResetMysqlServerDBNames()));
connect(d->dbNameCore, SIGNAL(textChanged(QString)),
this, SLOT(slotUpdateSqlInit()));
connect(d->dbNameThumbs, SIGNAL(textChanged(QString)),
this, SLOT(slotUpdateSqlInit()));
connect(d->dbNameFace, SIGNAL(textChanged(QString)),
this, SLOT(slotUpdateSqlInit()));
connect(d->dbNameSimilarity, SIGNAL(textChanged(QString)),
this, SLOT(slotUpdateSqlInit()));
connect(d->userName, SIGNAL(textChanged(QString)),
this, SLOT(slotUpdateSqlInit()));
slotHandleDBTypeIndexChanged(d->dbType->currentIndex());
}
int DatabaseSettingsWidget::databaseType() const
{
return d->dbType->currentData().toInt();
}
QString DatabaseSettingsWidget::databasePath() const
{
return d->dbPathEdit->fileDlgPath();
}
void DatabaseSettingsWidget::setDatabasePath(const QString& path)
{
d->dbPathEdit->setFileDlgPath(path);
}
DbEngineParameters DatabaseSettingsWidget::orgDatabasePrm() const
{
return d->orgPrms;
}
QString DatabaseSettingsWidget::databaseBackend() const
{
switch (databaseType())
{
case MysqlInternal:
case MysqlServer:
{
return DbEngineParameters::MySQLDatabaseType();
}
default: // SQlite
{
return DbEngineParameters::SQLiteDatabaseType();
}
}
}
void DatabaseSettingsWidget::slotResetMysqlServerDBNames()
{
d->dbNameCore->setText(QLatin1String("digikam"));
d->dbNameThumbs->setText(QLatin1String("digikam"));
d->dbNameFace->setText(QLatin1String("digikam"));
d->dbNameSimilarity->setText(QLatin1String("digikam"));
}
void DatabaseSettingsWidget::slotHandleDBTypeIndexChanged(int index)
{
int dbType = d->dbType->itemData(index).toInt();
setDatabaseInputFields(dbType);
handleInternalServer(dbType);
slotUpdateSqlInit();
}
void DatabaseSettingsWidget::setDatabaseInputFields(int index)
{
switch (index)
{
case SQlite:
{
d->dbPathLabel->setVisible(true);
d->dbPathEdit->setVisible(true);
d->mysqlCmdBox->setVisible(false);
d->tab->setVisible(false);
connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(slotDatabasePathEditedDelayed()));
break;
}
case MysqlInternal:
{
d->dbPathLabel->setVisible(true);
d->dbPathEdit->setVisible(true);
d->mysqlCmdBox->setVisible(true);
d->tab->setVisible(false);
connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(slotDatabasePathEditedDelayed()));
break;
}
default: // MysqlServer
{
d->dbPathLabel->setVisible(false);
d->dbPathEdit->setVisible(false);
d->mysqlCmdBox->setVisible(false);
d->tab->setVisible(true);
disconnect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(slotDatabasePathEditedDelayed()));
break;
}
}
}
void DatabaseSettingsWidget::handleInternalServer(int index)
{
bool internal = (index == MysqlInternal);
d->hostName->setDisabled(internal);
d->hostPort->setDisabled(internal);
d->dbNameCore->setDisabled(internal);
d->dbNameThumbs->setDisabled(internal);
d->dbNameFace->setDisabled(internal);
d->dbNameSimilarity->setDisabled(internal);
d->userName->setDisabled(internal);
d->password->setDisabled(internal);
d->connectOpts->setDisabled(internal);
}
void DatabaseSettingsWidget::slotUpdateSqlInit()
{
QString sql = QString::fromLatin1("CREATE USER \'%1\'@\'%\' IDENTIFIED BY \'password\';
")
.arg(d->userName->text());
sql += QString::fromLatin1("GRANT ALL ON *.* TO \'%1\'@\'%\' IDENTIFIED BY \'password\';
")
.arg(d->userName->text());
sql += QString::fromLatin1("CREATE DATABASE %1;
"
"GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%\';
")
.arg(d->dbNameCore->text())
.arg(d->dbNameCore->text())
.arg(d->userName->text());
if (d->dbNameThumbs->text() != d->dbNameCore->text())
{
sql += QString::fromLatin1("CREATE DATABASE %1;
"
"GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%\';
")
.arg(d->dbNameThumbs->text())
.arg(d->dbNameThumbs->text())
.arg(d->userName->text());
}
if ((d->dbNameFace->text() != d->dbNameCore->text()) &&
(d->dbNameFace->text() != d->dbNameThumbs->text()))
{
sql += QString::fromLatin1("CREATE DATABASE %1;
"
"GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%\';
")
.arg(d->dbNameFace->text())
.arg(d->dbNameFace->text())
.arg(d->userName->text());
}
if ((d->dbNameSimilarity->text() != d->dbNameCore->text()) &&
(d->dbNameSimilarity->text() != d->dbNameThumbs->text()) &&
(d->dbNameSimilarity->text() != d->dbNameFace->text()))
{
sql += QString::fromLatin1("CREATE DATABASE %1;
"
"GRANT ALL PRIVILEGES ON %2.* TO \'%3\'@\'%\';
")
.arg(d->dbNameSimilarity->text())
.arg(d->dbNameSimilarity->text())
.arg(d->userName->text());
}
sql += QLatin1String("FLUSH PRIVILEGES;
");
d->sqlInit->setText(sql);
}
void DatabaseSettingsWidget::slotCheckMysqlServerConnection()
{
QString error;
if (checkMysqlServerConnection(error))
{
QMessageBox::information(qApp->activeWindow(), i18n("Database connection test"),
i18n("Database connection test successful."));
}
else
{
QMessageBox::critical(qApp->activeWindow(), i18n("Database connection test"),
i18n("Database connection test was not successful. Error was: %1
",
error));
}
}
bool DatabaseSettingsWidget::checkMysqlServerConnectionConfig(QString& error)
{
if (d->hostName->text().isEmpty())
{
error = i18n("The server hostname is empty");
return false;
}
if (d->userName->text().isEmpty())
{
error = i18n("The server user name is empty");
return false;
}
return true;
}
bool DatabaseSettingsWidget::checkMysqlServerDbNamesConfig(QString& error)
{
if (d->dbNameCore->text().isEmpty())
{
error = i18n("The core database name is empty");
return false;
}
if (d->dbNameThumbs->text().isEmpty())
{
error = i18n("The thumbnails database name is empty");
return false;
}
if (d->dbNameFace->text().isEmpty())
{
error = i18n("The face database name is empty");
return false;
}
if (d->dbNameSimilarity->text().isEmpty())
{
error = i18n("The similarity database name is empty");
return false;
}
return true;
}
bool DatabaseSettingsWidget::checkMysqlServerConnection(QString& error)
{
if (!checkMysqlServerConnectionConfig(error))
{
return false;
}
bool result = false;
qApp->setOverrideCursor(Qt::WaitCursor);
AlbumManager::instance()->addFakeConnection();
QString databaseID(QLatin1String("ConnectionTest"));
{
QSqlDatabase testDatabase = QSqlDatabase::addDatabase(databaseBackend(), databaseID);
DbEngineParameters prm = getDbEngineParameters();
qCDebug(DIGIKAM_DATABASE_LOG) << "Testing DB connection (" << databaseID << ") with these settings:";
qCDebug(DIGIKAM_DATABASE_LOG) << prm;
testDatabase.setHostName(prm.hostName);
testDatabase.setPort(prm.port);
testDatabase.setUserName(prm.userName);
testDatabase.setPassword(prm.password);
testDatabase.setConnectOptions(prm.connectOptions);
result = testDatabase.open();
error = testDatabase.lastError().text();
testDatabase.close();
}
QSqlDatabase::removeDatabase(databaseID);
qApp->restoreOverrideCursor();
return result;
}
void DatabaseSettingsWidget::setParametersFromSettings(const ApplicationSettings* const settings,
const bool& migration)
{
d->orgPrms = settings->getDbEngineParameters();
if (d->orgPrms.databaseType == DbEngineParameters::SQLiteDatabaseType())
{
d->dbPathEdit->setFileDlgPath(d->orgPrms.getCoreDatabaseNameOrDir());
d->dbType->setCurrentIndex(d->dbTypeMap[SQlite]);
slotResetMysqlServerDBNames();
if (settings->getDatabaseDirSetAtCmd() && !migration)
{
d->dbType->setEnabled(false);
d->dbPathEdit->setEnabled(false);
d->dbPathLabel->setText(d->dbPathLabel->text() +
i18n("This path was set as a command line"
"option (--database-directory)."));
}
}
#ifdef HAVE_MYSQLSUPPORT
# ifdef HAVE_INTERNALMYSQL
else if (d->orgPrms.databaseType == DbEngineParameters::MySQLDatabaseType() && d->orgPrms.internalServer)
{
d->dbPathEdit->setFileDlgPath(d->orgPrms.internalServerPath());
d->dbType->setCurrentIndex(d->dbTypeMap[MysqlInternal]);
d->mysqlInitBin.setup(QFileInfo(d->orgPrms.internalServerMysqlInitCmd).absoluteFilePath());
d->mysqlServBin.setup(QFileInfo(d->orgPrms.internalServerMysqlServCmd).absoluteFilePath());
d->dbBinariesWidget->allBinariesFound();
slotResetMysqlServerDBNames();
}
# endif
else
{
d->dbType->setCurrentIndex(d->dbTypeMap[MysqlServer]);
d->dbNameCore->setText(d->orgPrms.databaseNameCore);
d->dbNameThumbs->setText(d->orgPrms.databaseNameThumbnails);
d->dbNameFace->setText(d->orgPrms.databaseNameFace);
d->dbNameSimilarity->setText(d->orgPrms.databaseNameSimilarity);
d->hostName->setText(d->orgPrms.hostName);
d->hostPort->setValue((d->orgPrms.port == -1) ? 3306 : d->orgPrms.port);
d->connectOpts->setText(d->orgPrms.connectOptions);
d->userName->setText(d->orgPrms.userName);
d->password->setText(d->orgPrms.password);
}
#endif
slotHandleDBTypeIndexChanged(d->dbType->currentIndex());
}
DbEngineParameters DatabaseSettingsWidget::getDbEngineParameters() const
{
DbEngineParameters prm;
switch(databaseType())
{
case SQlite:
prm = DbEngineParameters::parametersForSQLiteDefaultFile(databasePath());
break;
case MysqlInternal:
prm = DbEngineParameters::defaultParameters(databaseBackend());
prm.setInternalServerPath(databasePath());
prm.internalServerMysqlInitCmd = d->mysqlInitBin.path();
prm.internalServerMysqlServCmd = d->mysqlServBin.path();
break;
default: // MysqlServer
prm.internalServer = false;
prm.databaseType = databaseBackend();
prm.databaseNameCore = d->dbNameCore->text();
prm.databaseNameThumbnails = d->dbNameThumbs->text();
prm.databaseNameFace = d->dbNameFace->text();
prm.databaseNameSimilarity = d->dbNameSimilarity->text();
prm.connectOptions = d->connectOpts->text();
prm.hostName = d->hostName->text();
prm.port = d->hostPort->value();
prm.userName = d->userName->text();
prm.password = d->password->text();
break;
}
return prm;
}
void DatabaseSettingsWidget::slotDatabasePathEditedDelayed()
{
QTimer::singleShot(300, this, SLOT(slotDatabasePathEdited()));
}
void DatabaseSettingsWidget::slotDatabasePathEdited()
{
QString newPath = databasePath();
#ifndef Q_OS_WIN
if (!newPath.isEmpty() && !QDir::isAbsolutePath(newPath))
{
d->dbPathEdit->setFileDlgPath(QDir::homePath() + QLatin1Char('/') + newPath);
}
#endif
d->dbPathEdit->setFileDlgPath(newPath);
}
bool DatabaseSettingsWidget::checkDatabaseSettings()
{
switch (databaseType())
{
case SQlite:
{
return checkDatabasePath();
}
case MysqlInternal:
{
if (!checkDatabasePath())
return false;
if (!d->dbBinariesWidget->allBinariesFound())
return false;
return true;
}
default: // MysqlServer
{
QString error;
if (!checkMysqlServerDbNamesConfig(error))
{
QMessageBox::critical(qApp->activeWindow(), i18n("Database Configuration"),
i18n("The database names configuration is not valid. Error is
%1
"
"Please check your configuration.",
error));
return false;
}
if (!checkMysqlServerConnection(error))
{
QMessageBox::critical(qApp->activeWindow(), i18n("Database Connection Test"),
i18n("Testing database connection has failed with error
%1
"
"Please check your configuration.",
error));
return false;
}
}
}
return true;
}
bool DatabaseSettingsWidget::checkDatabasePath()
{
QString dbFolder = databasePath();
qCDebug(DIGIKAM_DATABASE_LOG) << "Database directory is : " << dbFolder;
if (dbFolder.isEmpty())
{
QMessageBox::information(qApp->activeWindow(), qApp->applicationName(),
i18n("You must select a folder for digiKam to "
"store information and metadata in a database file."));
return false;
}
QDir targetPath(dbFolder);
if (!targetPath.exists())
{
int rc = QMessageBox::question(qApp->activeWindow(), i18n("Create Database Folder?"),
i18n("The folder to put your database in does not seem to exist:
"
"%1
"
"Would you like digiKam to create it for you?", dbFolder));
if (rc == QMessageBox::No)
{
return false;
}
if (!targetPath.mkpath(dbFolder))
{
QMessageBox::information(qApp->activeWindow(), i18n("Create Database Folder Failed"),
i18n("digiKam could not create the folder to host your database file.\n"
"Please select a different location.
"
"%1
", dbFolder));
return false;
}
}
QFileInfo path(dbFolder);
#ifdef Q_OS_WIN
// Work around bug #189168
QTemporaryFile temp;
temp.setFileTemplate(dbFolder + QLatin1String("XXXXXX"));
if (!temp.open())
#else
if (!path.isWritable())
#endif
{
QMessageBox::information(qApp->activeWindow(), i18n("No Database Write Access"),
i18n("You do not seem to have write access "
"for the folder to host the database file.
"
"Please select a different location.
"
"%1
", dbFolder));
return false;
}
return true;
}
} // namespace Digikam
diff --git a/core/libs/dialogs/rawcameradlg.cpp b/core/libs/dialogs/rawcameradlg.cpp
index 786406f6af..6cfa7c10f4 100644
--- a/core/libs/dialogs/rawcameradlg.cpp
+++ b/core/libs/dialogs/rawcameradlg.cpp
@@ -1,158 +1,158 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-04-07
* Description : Raw camera list dialog
*
* Copyright (C) 2008-2018 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 "rawcameradlg.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "drawdecoder.h"
namespace Digikam
{
class Q_DECL_HIDDEN RawCameraDlg::Private
{
public:
explicit Private()
: header(0),
searchBar(0)
{
}
QLabel* header;
SearchTextBar* searchBar;
};
RawCameraDlg::RawCameraDlg(QWidget* const parent)
: InfoDlg(parent),
d(new Private)
{
setWindowTitle(i18n("List of supported RAW cameras"));
QStringList list = DRawDecoder::supportedCamera();
// --------------------------------------------------------
d->header = new QLabel(this);
d->searchBar = new SearchTextBar(this, QLatin1String("RawCameraDlgSearchBar"));
updateHeader();
listView()->setColumnCount(1);
listView()->setHeaderLabels(QStringList() << QLatin1String("Camera Model")); // Header is hidden. No i18n here.
listView()->header()->hide();
for (QStringList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it)
{
new QTreeWidgetItem(listView(), QStringList() << *it);
}
// --------------------------------------------------------
QGridLayout* const grid = dynamic_cast(mainWidget()->layout());
grid->addWidget(d->header, 1, 0, 1, -1);
grid->addWidget(d->searchBar, 3, 0, 1, -1);
// --------------------------------------------------------
connect(d->searchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)),
this, SLOT(slotSearchTextChanged(SearchTextSettings)));
}
RawCameraDlg::~RawCameraDlg()
{
delete d;
}
void RawCameraDlg::slotSearchTextChanged(const SearchTextSettings& settings)
{
bool query = false;
int results = 0;
QString search = settings.text.toLower();
QTreeWidgetItemIterator it(listView());
while (*it)
{
QTreeWidgetItem* const item = *it;
if (item->text(0).toLower().contains(search, settings.caseSensitive))
{
++results;
query = true;
item->setHidden(false);
}
else
{
item->setHidden(true);
}
++it;
}
updateHeader(results);
d->searchBar->slotSearchResult(query);
}
void RawCameraDlg::updateHeader(int results)
{
QString librawVer = DRawDecoder::librawVersion();
QStringList list = DRawDecoder::supportedCamera();
if (!results)
{
d->header->setText(i18np("Using LibRaw version %2
"
- "1 model on the list",
+ "1 model on the list",
"Using LibRaw version %2
"
- "%1 models on the list",
+ "%1 models on the list",
list.count(),
librawVer
));
}
else
{
d->header->setText(i18np("Using LibRaw version %2
"
- "1 model on the list (found: %3)",
+ "1 model on the list (found: %3)",
"Using LibRaw version %2
"
- "%1 models on the list (found: %3)",
+ "%1 models on the list (found: %3)",
list.count(),
librawVer,
results));
}
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/lens/lensfunfilter.cpp b/core/libs/dimg/filters/lens/lensfunfilter.cpp
index 99be5a76e0..ef6376594d 100644
--- a/core/libs/dimg/filters/lens/lensfunfilter.cpp
+++ b/core/libs/dimg/filters/lens/lensfunfilter.cpp
@@ -1,395 +1,395 @@
/* ============================================================
*
* Date : 2008-02-10
* Description : a tool to fix automatically camera lens aberrations
*
* Copyright (C) 2008 by Adrian Schroeter
* Copyright (C) 2008-2018 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 "lensfunfilter.h"
// Qt includes
#include
#include
#include
#include // krazy:exclude=includes
// Local includes
#include "digikam_debug.h"
#include "lensfuniface.h"
#include "dmetadata.h"
namespace Digikam
{
class Q_DECL_HIDDEN LensFunFilter::Private
{
public:
explicit Private()
{
iface = 0;
modifier = 0;
loop = 0;
}
DImg tempImage;
LensFunIface* iface;
lfModifier* modifier;
int loop;
};
LensFunFilter::LensFunFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
d->iface = new LensFunIface;
initFilter();
}
LensFunFilter::LensFunFilter(DImg* const orgImage, QObject* const parent, const LensFunContainer& settings)
: DImgThreadedFilter(orgImage, parent, QLatin1String("LensCorrection")),
d(new Private)
{
d->iface = new LensFunIface;
d->iface->setSettings(settings);
initFilter();
}
LensFunFilter::~LensFunFilter()
{
cancelFilter();
if (d->modifier)
{
d->modifier->Destroy();
}
delete d->iface;
delete d;
}
void LensFunFilter::filterCCAMultithreaded(uint start, uint stop)
{
QScopedArrayPointer pos(new float[m_orgImage.width() * 2 * 3]);
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplySubpixelDistortion(0.0, y, m_orgImage.width(), 1, pos.data()))
{
float* src = pos.data();
for (unsigned x = 0; runningFlag() && (x < m_destImage.width()); ++x)
{
DColor destPixel(0, 0, 0, 0xFFFF, m_destImage.sixteenBit());
destPixel.setRed(m_orgImage.getSubPixelColorFast(src[0], src[1]).red());
destPixel.setGreen(m_orgImage.getSubPixelColorFast(src[2], src[3]).green());
destPixel.setBlue(m_orgImage.getSubPixelColorFast(src[4], src[5]).blue());
m_destImage.setPixelColor(x, y, destPixel);
src += 2 * 3;
}
}
}
}
void LensFunFilter::filterVIGMultithreaded(uint start, uint stop)
{
uchar* data = m_destImage.bits();
data += m_destImage.width() * m_destImage.bytesDepth() * start;
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplyColorModification(data,
0.0,
y,
m_destImage.width(),
1,
LF_CR_4(RED, GREEN, BLUE, UNKNOWN),
0))
{
data += m_destImage.width() * m_destImage.bytesDepth();
}
}
}
void LensFunFilter::filterDSTMultithreaded(uint start, uint stop)
{
QScopedArrayPointer pos(new float[m_orgImage.width() * 2 * 3]);
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplyGeometryDistortion(0.0, y, d->tempImage.width(), 1, pos.data()))
{
float* src = pos.data();
for (unsigned int x = 0; runningFlag() && (x < d->tempImage.width()); ++x, ++d->loop)
{
d->tempImage.setPixelColor(x, y, m_destImage.getSubPixelColor(src[0], src[1]));
src += 2;
}
}
}
}
void LensFunFilter::filterImage()
{
m_destImage.bitBltImage(&m_orgImage, 0, 0);
if (!d->iface)
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface is null.";
return;
}
if (!d->iface->usedLens())
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface Lens device is null.";
return;
}
// Lensfun Modifier flags to process
int modifyFlags = 0;
if (d->iface->settings().filterDST)
{
modifyFlags |= LF_MODIFY_DISTORTION;
}
if (d->iface->settings().filterGEO)
{
modifyFlags |= LF_MODIFY_GEOMETRY;
}
if (d->iface->settings().filterCCA)
{
modifyFlags |= LF_MODIFY_TCA;
}
if (d->iface->settings().filterVIG)
{
modifyFlags |= LF_MODIFY_VIGNETTING;
}
// Init lensfun lib, we are working on the full image.
lfPixelFormat colorDepth = m_orgImage.bytesDepth() == 4 ? LF_PF_U8 : LF_PF_U16;
d->modifier = lfModifier::Create(d->iface->usedLens(),
d->iface->settings().cropFactor,
m_orgImage.width(),
m_orgImage.height());
int modflags = d->modifier->Initialize(d->iface->usedLens(),
colorDepth,
d->iface->settings().focalLength,
d->iface->settings().aperture,
d->iface->settings().subjectDistance,
1.0, /* no scaling */
LF_RECTILINEAR,
modifyFlags,
0 /*no inverse*/);
if (!d->modifier)
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: cannot initialize LensFun Modifier.";
return;
}
// Calc necessary steps for progress bar
int steps = ((d->iface->settings().filterCCA) ? 1 : 0) +
((d->iface->settings().filterVIG) ? 1 : 0) +
((d->iface->settings().filterDST || d->iface->settings().filterGEO) ? 1 : 0);
qCDebug(DIGIKAM_DIMG_LOG) << "LensFun Modifier Flags: " << modflags << " Steps:" << steps;
if (steps < 1)
{
qCDebug(DIGIKAM_DIMG_LOG) << "No LensFun Modifier steps. There is nothing to process...";
return;
}
qCDebug(DIGIKAM_DIMG_LOG) << "Image size to process: ("
<< m_orgImage.width() << ", "
<< m_orgImage.height() << ")";
QList vals = multithreadedSteps(m_destImage.height());
// Stage 1: Chromatic Aberation Corrections
if (d->iface->settings().filterCCA)
{
m_orgImage.prepareSubPixelAccess(); // init lanczos kernel
QList > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterCCAMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture t, tasks)
t.waitForFinished();
qCDebug(DIGIKAM_DIMG_LOG) << "Chromatic Aberation Corrections applied.";
}
postProgress(30);
// Stage 2: Color Corrections: Vignetting and Color Contribution Index
if (d->iface->settings().filterVIG)
{
QList > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterVIGMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture t, tasks)
t.waitForFinished();
qCDebug(DIGIKAM_DIMG_LOG) << "Vignetting and Color Corrections applied.";
}
postProgress(60);
// Stage 3: Distortion and Geometry Corrections
if (d->iface->settings().filterDST || d->iface->settings().filterGEO)
{
d->loop = 0;
// we need a deep copy first
d->tempImage = DImg(m_destImage.width(),
m_destImage.height(),
m_destImage.sixteenBit(),
m_destImage.hasAlpha());
m_destImage.prepareSubPixelAccess(); // init lanczos kernel
QList > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterDSTMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture t, tasks)
t.waitForFinished();
qCDebug(DIGIKAM_DIMG_LOG) << "Distortion and Geometry Corrections applied.";
if (d->loop)
{
m_destImage = d->tempImage;
}
}
postProgress(90);
}
bool LensFunFilter::registerSettingsToXmp(MetaEngineData& data) const
{
// Register in digiKam Xmp namespace all information about Lens corrections.
QString str;
LensFunContainer prm = d->iface->settings();
str.append(i18n("Camera: %1-%2", prm.cameraMake, prm.cameraModel));
str.append(QLatin1Char('\n'));
str.append(i18n("Lens: %1", prm.lensModel));
str.append(QLatin1Char('\n'));
- str.append(i18n("Subject Distance: %1", QString::number(prm.subjectDistance)));
+ str.append(i18n("Subject Distance: %1", prm.subjectDistance));
str.append(QLatin1Char('\n'));
- str.append(i18n("Aperture: %1", QString::number(prm.aperture)));
+ str.append(i18n("Aperture: %1", prm.aperture));
str.append(QLatin1Char('\n'));
- str.append(i18n("Focal Length: %1", QString::number(prm.focalLength)));
+ str.append(i18n("Focal Length: %1", prm.focalLength));
str.append(QLatin1Char('\n'));
- str.append(i18n("Crop Factor: %1", QString::number(prm.cropFactor)));
+ str.append(i18n("Crop Factor: %1", prm.cropFactor));
str.append(QLatin1Char('\n'));
str.append(i18n("CCA Correction: %1", prm.filterCCA && d->iface->supportsCCA() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("VIG Correction: %1", prm.filterVIG && d->iface->supportsVig() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("DST Correction: %1", prm.filterDST && d->iface->supportsDistortion() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("GEO Correction: %1", prm.filterGEO && d->iface->supportsGeometry() ? i18n("enabled") : i18n("disabled")));
DMetadata meta(data);
bool ret = meta.setXmpTagString("Xmp.digiKam.LensCorrectionSettings",
str.replace(QLatin1Char('\n'), QLatin1String(" ; ")));
data = meta.data();
return ret;
}
FilterAction LensFunFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
LensFunContainer prm = d->iface->settings();
action.addParameter(QLatin1String("ccaCorrection"), prm.filterCCA);
action.addParameter(QLatin1String("vigCorrection"), prm.filterVIG);
action.addParameter(QLatin1String("dstCorrection"), prm.filterDST);
action.addParameter(QLatin1String("geoCorrection"), prm.filterGEO);
action.addParameter(QLatin1String("cropFactor"), prm.cropFactor);
action.addParameter(QLatin1String("focalLength"), prm.focalLength);
action.addParameter(QLatin1String("aperture"), prm.aperture);
action.addParameter(QLatin1String("subjectDistance"), prm.subjectDistance);
action.addParameter(QLatin1String("cameraMake"), prm.cameraMake);
action.addParameter(QLatin1String("cameraModel"), prm.cameraModel);
action.addParameter(QLatin1String("lensModel"), prm.lensModel);
return action;
}
void LensFunFilter::readParameters(const Digikam::FilterAction& action)
{
LensFunContainer prm = d->iface->settings();
prm.filterCCA = action.parameter(QLatin1String("ccaCorrection")).toBool();
prm.filterVIG = action.parameter(QLatin1String("vigCorrection")).toBool();
prm.filterDST = action.parameter(QLatin1String("dstCorrection")).toBool();
prm.filterGEO = action.parameter(QLatin1String("geoCorrection")).toBool();
prm.cropFactor = action.parameter(QLatin1String("cropFactor")).toDouble();
prm.focalLength = action.parameter(QLatin1String("focalLength")).toDouble();
prm.aperture = action.parameter(QLatin1String("aperture")).toDouble();
prm.subjectDistance = action.parameter(QLatin1String("subjectDistance")).toDouble();
prm.cameraMake = action.parameter(QLatin1String("cameraMake")).toString();
prm.cameraModel = action.parameter(QLatin1String("cameraModel")).toString();
prm.lensModel = action.parameter(QLatin1String("lensModel")).toString();
d->iface->setSettings(prm);
}
} // namespace Digikam
diff --git a/core/libs/tags/tagsmanager/tagsmanager.cpp b/core/libs/tags/tagsmanager/tagsmanager.cpp
index e718bbd58d..0da2d66165 100644
--- a/core/libs/tags/tagsmanager/tagsmanager.cpp
+++ b/core/libs/tags/tagsmanager/tagsmanager.cpp
@@ -1,990 +1,990 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 20013-07-03
* Description : Tag Manager main class
*
* Copyright (C) 2013 by Veaceslav Munteanu
* Copyright (C) 2014 by Michael G. Hansen
*
* 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 "tagsmanager.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
#include
// Local includes
#include "digikam_debug.h"
#include "digikam_config.h"
#include "dmessagebox.h"
#include "tagpropwidget.h"
#include "tagmngrtreeview.h"
#include "taglist.h"
#include "tagfolderview.h"
#include "ddragobjects.h"
#include "searchtextbar.h"
#include "tageditdlg.h"
#include "coredb.h"
#include "sidebar.h"
#include "dlogoaction.h"
#include "metadatasynchronizer.h"
#include "fileactionmngr.h"
#include "metadatasettings.h"
namespace Digikam
{
QPointer TagsManager::internalPtr = QPointer();
class Q_DECL_HIDDEN TagsManager::Private
{
public:
explicit Private()
{
tagPixmap = 0;
searchBar = 0;
splitter = 0;
treeWindow = 0;
mainToolbar = 0;
rightToolBar = 0;
organizeAction = 0;
syncexportAction = 0;
tagProperties = 0;
addAction = 0;
delAction = 0;
titleEdit = 0;
listView = 0;
tagPropWidget = 0;
tagMngrView = 0;
tagModel = 0;
tagPropVisible = false;
}
TagMngrTreeView* tagMngrView;
QLabel* tagPixmap;
SearchTextBar* searchBar;
QSplitter* splitter;
KMainWindow* treeWindow;
KToolBar* mainToolbar;
DMultiTabBar* rightToolBar;
QMenu* organizeAction;
QMenu* syncexportAction;
QAction* tagProperties;
QAction* addAction;
QAction* delAction;
QAction* titleEdit;
/** Options unavailable for root tag **/
QList rootDisabledOptions;
TagList* listView;
TagPropWidget* tagPropWidget;
TagModel* tagModel;
bool tagPropVisible;
};
TagsManager::TagsManager()
: KMainWindow(0),
StateSavingObject(this),
d(new Private())
{
setObjectName(QLatin1String("Tags Manager"));
d->tagModel = new TagModel(AbstractAlbumModel::IncludeRootAlbum, this);;
d->tagModel->setCheckable(false);
setupUi(this);
/*----------------------------Connects---------------------------*/
connect(d->tagMngrView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotSelectionChanged()));
connect(d->addAction, SIGNAL(triggered()),
this, SLOT(slotAddAction()));
connect(d->delAction, SIGNAL(triggered()),
this, SLOT(slotDeleteAction()));
d->tagMngrView->setCurrentIndex(d->tagMngrView->model()->index(0, 0));
StateSavingObject::loadState();
/** Set KMainWindow in center of the screen **/
this->move(QApplication::desktop()->screen()->rect().center() - this->rect().center());
}
TagsManager::~TagsManager()
{
StateSavingObject::saveState();
delete d->listView;
delete d->tagMngrView;
delete d->tagModel;
delete d;
}
TagsManager* TagsManager::instance()
{
if (TagsManager::internalPtr.isNull())
{
TagsManager::internalPtr = new TagsManager();
}
return TagsManager::internalPtr;
}
void TagsManager::setupUi(KMainWindow* const Dialog)
{
Dialog->resize(972, 722);
Dialog->setWindowTitle(i18n("Tags Manager"));
QHBoxLayout* const mainLayout = new QHBoxLayout();
d->tagPixmap = new QLabel();
d->tagPixmap->setText(QLatin1String("Tag Pixmap"));
d->tagPixmap->setMaximumWidth(40);
d->tagPixmap->setPixmap(QIcon::fromTheme(QLatin1String("tag")).pixmap(30, 30));
d->tagMngrView = new TagMngrTreeView(this, d->tagModel);
d->tagMngrView->setConfigGroup(getConfigGroup());
d->searchBar = new SearchTextBar(this, QLatin1String("DigikamViewTagSearchBar"));
d->searchBar->setHighlightOnResult(true);
d->searchBar->setModel(d->tagMngrView->filteredModel(),
AbstractAlbumModel::AlbumIdRole,
AbstractAlbumModel::AlbumTitleRole);
d->searchBar->setMaximumWidth(200);
d->searchBar->setFilterModel(d->tagMngrView->albumFilterModel());
/** Tree Widget & Actions + Tag Properties sidebar **/
d->treeWindow = new KMainWindow(this);
setupActions();
d->splitter = new QSplitter();
d->listView = new TagList(d->tagMngrView,Dialog);
d->splitter->addWidget(d->listView);
d->splitter->addWidget(d->tagMngrView);
d->tagPropWidget = new TagPropWidget(d->treeWindow);
d->splitter->addWidget(d->tagPropWidget);
d->tagPropWidget->hide();
connect(d->tagPropWidget, SIGNAL(signalTitleEditReady()),
this, SLOT(slotTitleEditReady()));
d->splitter->setStretchFactor(0, 0);
d->splitter->setStretchFactor(1, 1);
d->splitter->setStretchFactor(2, 0);
d->treeWindow->setCentralWidget(d->splitter);
mainLayout->addWidget(d->treeWindow);
mainLayout->addWidget(d->rightToolBar);
QWidget* const centraW = new QWidget(this);
centraW->setLayout(mainLayout);
this->setCentralWidget(centraW);
}
void TagsManager::slotOpenProperties()
{
DMultiTabBarTab* const sender = dynamic_cast(QObject::sender());
if (sender->isChecked())
{
d->tagPropWidget->show();
}
else
{
d->tagPropWidget->hide();
}
d->tagPropVisible = d->tagPropWidget->isVisible();
}
void TagsManager::slotSelectionChanged()
{
QList selectedTags = d->tagMngrView->selectedTags();
if (selectedTags.isEmpty() || (selectedTags.size() == 1 && selectedTags.at(0)->isRoot()))
{
enableRootTagActions(false);
d->listView->enableAddButton(false);
}
else
{
enableRootTagActions(true);
d->listView->enableAddButton(true);
d->titleEdit->setEnabled((selectedTags.size() == 1));
}
d->tagPropWidget->slotSelectionChanged(selectedTags);
}
void TagsManager::slotItemChanged()
{
}
void TagsManager::slotAddAction()
{
TAlbum* parent = d->tagMngrView->currentAlbum();
QString title, icon;
QKeySequence ks;
if (!parent)
{
parent = static_cast(d->tagMngrView->albumForIndex(d->tagMngrView->model()->index(0, 0)));
}
if (!TagEditDlg::tagCreate(qApp->activeWindow(), parent, title, icon, ks))
{
return;
}
QMap errMap;
AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, ks, errMap);
TagEditDlg::showtagsListCreationError(qApp->activeWindow(), errMap);
}
namespace
{
QString JoinTagNamesToList(const QStringList& stringList)
{
const QString joinedStringList = stringList.join(QLatin1String("', '"));
return QLatin1Char('\'') + joinedStringList + QLatin1Char('\'');
}
} // namespace
void TagsManager::slotDeleteAction()
{
const QModelIndexList selected = d->tagMngrView->selectionModel()->selectedIndexes();
QStringList tagNames;
QStringList tagsWithChildren;
QStringList tagsWithImages;
QMultiMap sortedTags;
foreach(const QModelIndex& index, selected)
{
if (!index.isValid())
{
return;
}
TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index));
if (!t || t->isRoot())
{
return;
}
AlbumPointer tag(t);
tagNames.append(tag->title());
// find number of subtags
int children = 0;
AlbumIterator iter(tag);
while (iter.current())
{
++children;
++iter;
}
if (children)
{
tagsWithChildren.append(tag->title());
}
QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id());
if (!assignedItems.isEmpty())
{
tagsWithImages.append(tag->title());
}
/**
* Tags must be deleted from children to parents, if we don't want
* to step on invalid index. Use QMultiMap to order them by distance
* to root tag
*/
Album* parent = t;
int depth = 0;
while (!parent->isRoot())
{
parent = parent->parent();
depth++;
}
sortedTags.insert(depth, tag);
}
// ask for deletion of children
if (!tagsWithChildren.isEmpty())
{
const int result = QMessageBox::warning(this, qApp->applicationName(),
i18ncp("%2 is a comma separated list of tags to be deleted.",
"Tag %2 has one or more subtags. "
"Deleting it will also delete "
"the subtags. "
"Do you want to continue?",
"Tags %2 have one or more subtags. "
"Deleting them will also delete "
"the subtags. "
"Do you want to continue?",
tagsWithChildren.count(),
JoinTagNamesToList(tagsWithChildren)),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
}
QString message;
if (!tagsWithImages.isEmpty())
{
message = i18ncp("%2 is a comma separated list of tags to be deleted.",
"Tag %2 is assigned to one or more items. "
"Do you want to delete it?",
"Tags %2 are assigned to one or more items. "
"Do you want to delete them?",
tagsWithImages.count(),
JoinTagNamesToList(tagsWithImages));
}
else
{
message = i18ncp("%2 is a comma separated list of tags to be deleted.",
"Delete tag %2?",
"Delete tags %2?",
tagNames.count(),
JoinTagNamesToList(tagNames));
}
const int result = QMessageBox::warning(this, i18np("Delete tag", "Delete tags", tagNames.count()),
message, QMessageBox::Yes | QMessageBox::Cancel);
if (result == QMessageBox::Yes)
{
QMultiMap::iterator it;
/**
* QMultimap doesn't provide reverse iterator, -1 is required
* because end() points after the last element
*/
for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it)
{
QString errMsg;
if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg))
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg);
}
}
}
}
void TagsManager::slotEditTagTitle()
{
QList selectedTags = d->tagMngrView->selectedTags();
if (selectedTags.size() == 1 && !selectedTags.at(0)->isRoot())
{
d->tagPropWidget->show();
d->tagPropWidget->slotFocusTitleEdit();
d->rightToolBar->tab(0)->setChecked(true);
}
}
void TagsManager::slotTitleEditReady()
{
if (!d->tagPropVisible)
{
d->tagPropWidget->hide();
d->rightToolBar->tab(0)->setChecked(false);
}
d->tagMngrView->setFocus();
}
void TagsManager::slotResetTagIcon()
{
QString errMsg;
const QList selected = d->tagMngrView->selectedTagAlbums();
const QString icon = QLatin1String("tag");
for (QList::const_iterator it = selected.constBegin() ; it != selected.constEnd() ; ++it )
{
TAlbum* const tag = *it;
if (tag)
{
if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg))
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg);
}
}
}
}
void TagsManager::slotCreateTagAddr()
{
}
void TagsManager::slotInvertSel()
{
QModelIndex root = d->tagMngrView->model()->index(0, 0);
QItemSelectionModel* const model = d->tagMngrView->selectionModel();
QModelIndexList selected = model->selectedIndexes();
QQueue greyNodes;
bool currentSet = false;
greyNodes.append(root);
model->clearSelection();
while (!greyNodes.isEmpty())
{
QModelIndex current = greyNodes.dequeue();
if (!(current.isValid()))
{
continue;
}
int it = 0;
QModelIndex child = current.child(it++, 0);
while (child.isValid())
{
if (!selected.contains(child))
{
if (!currentSet)
{
/**
* Must set a new current item when inverting selection
* it should be done only once
*/
d->tagMngrView->setCurrentIndex(child);
currentSet = true;
}
model->select(child, model->Select);
}
if (d->tagMngrView->isExpanded(child))
{
greyNodes.enqueue(child);
}
child = current.child(it++, 0);
}
}
}
void TagsManager::slotWriteToImg()
{
int result = QMessageBox::warning(this, qApp->applicationName(),
i18n("digiKam will clean up tag metadata before setting "
- "tags from database.
You may lose tags if you did not "
+ "tags from database.
You may lose tags if you did not "
"read tags before (by calling Read Tags from Image).
"
- "Do you want to continue?"),
+ "Do you want to continue?"),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
result = QMessageBox::warning(this, qApp->applicationName(),
i18n("This operation can take long time "
"depending on collection size.\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(),
MetadataSynchronizer::WriteFromDatabaseToFile);
tool->setTagsOnly(true);
tool->start();
}
void TagsManager::slotReadFromImg()
{
int result = QMessageBox::warning(this, qApp->applicationName(),
i18n("This operation can take long time "
"depending on collection size.\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(),
MetadataSynchronizer::ReadFromFileToDatabase);
tool->setTagsOnly(true);
tool->start();
}
void TagsManager::slotWipeAll()
{
const int result = QMessageBox::warning(this, qApp->applicationName(),
i18n("This operation will wipe all tags from database only.\n"
"To apply changes to files, "
"you must choose write metadata to file later.\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
/** Disable writing tags to images **/
MetadataSettings* metaSettings = MetadataSettings::instance();
MetadataSettingsContainer backUpContainer = metaSettings->settings();
MetadataSettingsContainer newContainer = backUpContainer;
bool settingsChanged = false;
if (backUpContainer.saveTags == true || backUpContainer.saveFaceTags == true)
{
settingsChanged = true;
newContainer.saveTags = false;
newContainer.saveFaceTags = false;
metaSettings->setSettings(newContainer);
}
AlbumPointerList tagList;
const QModelIndex root = d->tagMngrView->model()->index(0, 0);
int iter = 0;
QModelIndex child = root.child(iter++, 0);
while (child.isValid())
{
tagList << AlbumPointer(d->tagMngrView->albumForIndex(child));
child = root.child(iter++, 0);
}
AlbumPointerList::iterator it;
for (it = tagList.begin() ; it != tagList.end() ; ++it)
{
QString errMsg;
if (!AlbumManager::instance()->deleteTAlbum(*it, errMsg))
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg);
}
}
/** Restore settings after tag deletion **/
if (settingsChanged)
{
metaSettings->setSettings(backUpContainer);
}
}
void TagsManager::slotRemoveTagsFromImgs()
{
const QModelIndexList selList = d->tagMngrView->selectionModel()->selectedIndexes();
const int result = QMessageBox::warning(this, qApp->applicationName(),
i18np("Do you really want to remove the selected tag from all images?",
"Do you really want to remove the selected tags from all images?",
selList.count()),
QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
return;
}
foreach (const QModelIndex& index, selList)
{
TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index));
AlbumPointer tag(t);
if (tag->isRoot())
{
continue;
}
QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id());
ImageInfoList imgList(assignedItems);
FileActionMngr::instance()->removeTag(imgList, tag->id());
}
}
void TagsManager::closeEvent(QCloseEvent* event)
{
d->listView->saveSettings();
KMainWindow::closeEvent(event);
}
void TagsManager::setupActions()
{
d->mainToolbar = new KToolBar(d->treeWindow, true);
d->mainToolbar->layout()->setContentsMargins(QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin),
QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin),
QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin),
QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin));
QWidgetAction* const pixMapAction = new QWidgetAction(this);
pixMapAction->setDefaultWidget(d->tagPixmap);
QWidgetAction* const searchAction = new QWidgetAction(this);
searchAction->setDefaultWidget(d->searchBar);
d->mainToolbar->addAction(pixMapAction);
d->mainToolbar->addAction(searchAction);
d->mainToolbar->addSeparator();
d->addAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), QLatin1String(""), d->treeWindow);
d->delAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), QLatin1String(""), d->treeWindow);
/** organize group **/
d->organizeAction = new QMenu(i18nc("@title:menu", "Organize"), this);
d->organizeAction->setIcon(QIcon::fromTheme(QLatin1String("autocorrection")));
d->titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")),
i18n("Edit Tag Title"), this);
d->titleEdit->setShortcut(QKeySequence(Qt::Key_F2));
QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")),
i18n("Reset Tag Icon"), this);
QAction* const createTagAddr = new QAction(QIcon::fromTheme(QLatin1String("tag-addressbook")),
i18n("Create Tag from Address Book"), this);
QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")),
i18n("Invert Selection"), this);
QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")),
i18n("Expand Tag Tree"), this);
QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")),
i18n("Expand Selected Nodes"), this);
QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")),
i18n("Remove Tag from Images"), this);
QAction* const deleteUnused = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")),
i18n("Delete Unassigned Tags"), this);
/** Tool tips **/
setHelpText(d->addAction, i18n("Add new tag to current tag. "
"Current tag is last clicked tag."));
setHelpText(d->delAction, i18n("Delete selected items. "
"Also work with multiple items, "
"but will not delete the root tag."));
setHelpText(d->titleEdit, i18n("Edit title from selected tag."));
setHelpText(resetIcon, i18n("Reset icon to selected tags. "
"Works with multiple selection."));
setHelpText(invSel, i18n("Invert selection. "
"Only visible items will be selected"));
setHelpText(expandTree, i18n("Expand tag tree by one level"));
setHelpText(expandSel, i18n("Selected items will be expanded"));
setHelpText(delTagFromImg, i18n("Delete selected tag(s) from images. "
"Works with multiple selection."));
setHelpText(deleteUnused, i18n("Delete all tags that are not assigned to images. "
"Use with caution."));
connect(d->titleEdit, SIGNAL(triggered()),
this, SLOT(slotEditTagTitle()));
connect(resetIcon, SIGNAL(triggered()),
this, SLOT(slotResetTagIcon()));
connect(createTagAddr, SIGNAL(triggered()),
this, SLOT(slotCreateTagAddr()));
connect(invSel, SIGNAL(triggered()),
this, SLOT(slotInvertSel()));
connect(expandTree, SIGNAL(triggered()),
d->tagMngrView, SLOT(slotExpandTree()));
connect(expandSel, SIGNAL(triggered()),
d->tagMngrView, SLOT(slotExpandSelected()));
connect(delTagFromImg, SIGNAL(triggered()),
this, SLOT(slotRemoveTagsFromImgs()));
connect(deleteUnused, SIGNAL(triggered()),
this, SLOT(slotRemoveNotAssignedTags()));
d->organizeAction->addAction(d->titleEdit);
d->organizeAction->addAction(resetIcon);
d->organizeAction->addAction(createTagAddr);
d->organizeAction->addAction(invSel);
d->organizeAction->addAction(expandTree);
d->organizeAction->addAction(expandSel);
d->organizeAction->addAction(delTagFromImg);
d->organizeAction->addAction(deleteUnused);
/** Sync & Export Group **/
d->syncexportAction = new QMenu(i18n("Sync &Export"), this);
d->syncexportAction->setIcon(QIcon::fromTheme(QLatin1String("network-server-database")));
QAction* const wrDbImg = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")),
i18n("Write Tags from Database "
"to Image"), this);
QAction* const readTags = new QAction(QIcon::fromTheme(QLatin1String("tag-new")),
i18n("Read Tags from Image"), this);
QAction* const wipeAll = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")),
i18n("Wipe all tags from Database only"), this);
setHelpText(wrDbImg, i18n("Write Tags Metadata to Image."));
setHelpText(readTags, i18n("Read tags from Images into Database. "
"Existing tags will not be affected"));
setHelpText(wipeAll, i18n("Delete all tags from database only. Will not sync with files. "
"Proceed with caution."));
connect(wrDbImg, SIGNAL(triggered()),
this, SLOT(slotWriteToImg()));
connect(readTags, SIGNAL(triggered()),
this, SLOT(slotReadFromImg()));
connect(wipeAll, SIGNAL(triggered()),
this, SLOT(slotWipeAll()));
d->syncexportAction->addAction(wrDbImg);
d->syncexportAction->addAction(readTags);
d->syncexportAction->addAction(wipeAll);
d->mainToolbar->addAction(d->addAction);
d->mainToolbar->addAction(d->delAction);
d->mainToolbar->addAction(d->organizeAction->menuAction());
d->mainToolbar->addAction(d->syncexportAction->menuAction());
d->mainToolbar->addAction(new DLogoAction(this));
this->addToolBar(d->mainToolbar);
/**
* Right Toolbar with vertical properties button
*/
d->rightToolBar = new DMultiTabBar(Qt::RightEdge);
d->rightToolBar->appendTab(QIcon::fromTheme(QLatin1String("tag-properties"))
.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), 0, i18n("Tag Properties"));
d->rightToolBar->setStyle(DMultiTabBar::AllIconsText);
connect(d->rightToolBar->tab(0), SIGNAL(clicked()),
this, SLOT(slotOpenProperties()));
d->rootDisabledOptions.append(d->delAction);
d->rootDisabledOptions.append(d->titleEdit);
d->rootDisabledOptions.append(resetIcon);
d->rootDisabledOptions.append(delTagFromImg);
}
// helper based on KAction::setHelpText
void TagsManager::setHelpText(QAction *action, const QString& text)
{
action->setStatusTip(text);
action->setToolTip(text);
if (action->whatsThis().isEmpty())
{
action->setWhatsThis(text);
}
}
void TagsManager::enableRootTagActions(bool value)
{
foreach(QAction* const action, d->rootDisabledOptions)
{
if (value)
action->setEnabled(true);
else
action->setEnabled(false);
}
}
void TagsManager::doLoadState()
{
KConfigGroup group = getConfigGroup();
d->tagMngrView->doLoadState();
group.sync();
}
void TagsManager::doSaveState()
{
KConfigGroup group = getConfigGroup();
d->tagMngrView->doSaveState();
group.sync();
}
void TagsManager::slotRemoveNotAssignedTags()
{
const int result = DMessageBox::showContinueCancel(QMessageBox::Warning,
this,
i18n("Warning"),
i18n("This option will remove all tags which\n"
"are not assigned to any image.\n "
"Do you want to continue?"));
if (result != QMessageBox::Yes)
{
return;
}
QModelIndex root = d->tagMngrView->model()->index(0, 0);
QQueue greyNodes;
QList redNodes;
QSet greenNodes;
int iter = 0;
while (root.child(iter, 0).isValid())
{
greyNodes.append(root.child(iter++, 0));
}
while (!greyNodes.isEmpty())
{
QModelIndex current = greyNodes.dequeue();
if (!(current.isValid()))
{
continue;
}
if (current.child(0, 0).isValid())
{
// Add in the list
int iterator = 0;
while (current.child(iterator, 0).isValid())
{
greyNodes.append(current.child(iterator++, 0));
}
}
else
{
TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current));
if (t && !t->isRoot() && !t->isInternalTag())
{
QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id());
if (assignedItems.isEmpty())
{
redNodes.append(current);
}
else
{
QModelIndex tmp = current.parent();
while (tmp.isValid())
{
greenNodes.insert(tmp);
tmp = tmp.parent();
}
}
}
}
}
QList toRemove;
foreach(QModelIndex toDelete, redNodes)
{
QModelIndex current = toDelete;
while (current.isValid() && !greenNodes.contains(current))
{
TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current));
if (t && !t->isRoot() && !t->isInternalTag())
{
QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id());
if (assignedItems.isEmpty() && !toRemove.contains(t))
{
toRemove.append(t);
}
else
{
break;
}
}
current = current.parent();
}
}
foreach(TAlbum* const elem, toRemove)
{
qCDebug(DIGIKAM_GENERAL_LOG) << elem->title();
QString errMsg;
if (!AlbumManager::instance()->deleteTAlbum(elem, errMsg))
{
QMessageBox::critical(this, qApp->applicationName(), errMsg);
}
}
}
} // namespace Digikam
diff --git a/core/libs/template/templatepanel.cpp b/core/libs/template/templatepanel.cpp
index 9d7fdb8603..a882412727 100644
--- a/core/libs/template/templatepanel.cpp
+++ b/core/libs/template/templatepanel.cpp
@@ -1,515 +1,515 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-07-06
* Description : metadata template settings panel.
*
* Copyright (C) 2009-2018 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 "templatepanel.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "templatelist.h"
#include "subjectedit.h"
#include "altlangstredit.h"
#include "countryselector.h"
namespace Digikam
{
class Q_DECL_HIDDEN TemplatePanel::Private
{
public:
explicit Private()
{
authorsEdit = 0;
authorsPositionEdit = 0;
creditEdit = 0;
sourceEdit = 0;
copyrightEdit = 0;
rightUsageEdit = 0;
instructionsEdit = 0;
locationCountryCodeEdit = 0;
locationProvinceStateEdit = 0;
locationCityEdit = 0;
locationSublocationEdit = 0;
contactCityEdit = 0;
contactCountryEdit = 0;
contactAddressEdit = 0;
contactPostalCodeEdit = 0;
contactProvinceStateEdit = 0;
contactEmailEdit = 0;
contactPhoneEdit = 0;
contactWebUrlEdit = 0;
subjects = 0;
}
// Rights template information panel.
QLineEdit* authorsEdit;
QLineEdit* authorsPositionEdit;
QLineEdit* creditEdit;
QLineEdit* sourceEdit;
QLineEdit* instructionsEdit;
AltLangStrEdit* copyrightEdit;
AltLangStrEdit* rightUsageEdit;
// Location template information panel.
CountrySelector* locationCountryCodeEdit;
QLineEdit* locationProvinceStateEdit;
QLineEdit* locationCityEdit;
QLineEdit* locationSublocationEdit;
// Contact template information panel.
QLineEdit* contactCityEdit;
QLineEdit* contactCountryEdit;
QLineEdit* contactAddressEdit;
QLineEdit* contactPostalCodeEdit;
QLineEdit* contactProvinceStateEdit;
QLineEdit* contactEmailEdit;
QLineEdit* contactPhoneEdit;
QLineEdit* contactWebUrlEdit;
// Subjects template information panel.
SubjectEdit* subjects;
};
TemplatePanel::TemplatePanel(QWidget* const parent)
: QTabWidget(parent),
d(new Private)
{
// -- Rights Template information panel -------------------------------------------------------------
QWidget* const page1 = new QWidget(this);
QGridLayout* const grid1 = new QGridLayout(page1);
QLabel* const label1 = new QLabel(i18n("Author Names:"), page1);
d->authorsEdit = new QLineEdit(page1);
d->authorsEdit->setClearButtonEnabled(true);
d->authorsEdit->setPlaceholderText(i18n("Enter the names of the photograph's creators. Use semi-colons as separator here."));
label1->setBuddy(d->authorsEdit);
d->authorsEdit->setWhatsThis(i18n("This field should contain the names of the persons who created the photograph. "
"If it is not appropriate to add the name of the photographer (for example, if the identity of "
"the photographer needs to be protected) the name of a company or organization can also be used. "
- "Once saved, this field should not be changed by anyone. "
+ "Once saved, this field should not be changed by anyone.
"
"To enter more than one name, use semi-colons as separators.
"
"With IPTC, this field is limited to 32 ASCII characters.
"));
// --------------------------------------------------------
QLabel* const label2 = new QLabel(i18n("Authors' Positions:"), page1);
d->authorsPositionEdit = new QLineEdit(page1);
d->authorsPositionEdit->setClearButtonEnabled(true);
d->authorsPositionEdit->setPlaceholderText(i18n("Enter the job titles of the authors here."));
label2->setBuddy(d->authorsPositionEdit);
d->authorsPositionEdit->setWhatsThis(i18n("This field should contain the job titles of the authors. Examples might include "
"titles such as: Staff Photographer, Freelance Photographer, or Independent Commercial "
"Photographer. Since this is a qualifier for the Author field, the Author field must also "
"be filled out.
"
"With IPTC, this field is limited to 32 ASCII characters.
"));
// --------------------------------------------------------
QLabel* const label3 = new QLabel(i18n("Credit:"), page1);
d->creditEdit = new QLineEdit(page1);
d->creditEdit->setClearButtonEnabled(true);
d->creditEdit->setPlaceholderText(i18n("Enter the photograph credit here."));
label3->setBuddy(d->creditEdit);
d->creditEdit->setWhatsThis(i18n("(synonymous to Provider): Use the Provider field to identify who is providing the photograph. "
"This does not necessarily have to be the author. If a photographer is working for a news agency "
"such as Reuters or the Associated Press, these organizations could be listed here as they are "
"\"providing\" the image for use by others. If the image is a stock photograph, then the group "
"(agency) involved in supplying the image should be listed here.
"
"With IPTC, this field is limited to 32 ASCII characters.
"));
// --------------------------------------------------------
d->copyrightEdit = new AltLangStrEdit(page1);
d->copyrightEdit->setTitle(i18n("Copyright:"));
d->copyrightEdit->setFixedHeight(75);
d->copyrightEdit->setPlaceholderText(i18n("Enter the copyright notice to identify the current owner(s) of the copyright here."));
d->copyrightEdit->setWhatsThis(i18n("The Copyright Notice should contain any necessary copyright notice for claiming the intellectual "
"property, and should identify the current owner(s) of the copyright for the photograph. Usually, "
"this would be the photographer, but if the image was done by an employee or as work-for-hire, "
"then the agency or company should be listed. Use the form appropriate to your country. USA: "
"© {date of first publication} name of copyright owner, as in \"©2005 John Doe.\" "
"Note, the word \"copyright\" or the abbreviation \"copr\" may be used in place of the © symbol. "
"In some foreign countries only the copyright symbol is recognized and the abbreviation does not work. "
"Furthermore the copyright symbol must be a full circle with a \"c\" inside; using something like (c) "
"where the parentheses form a partial circle is not sufficient. For additional protection worldwide, "
"use of the phrase, \"all rights reserved\" following the notice above is encouraged. \nIn Europe "
"you would use: Copyright {Year} {Copyright owner}, all rights reserved. \nIn Japan, for maximum "
"protection, the following three items should appear in the copyright field of the IPTC Core: "
"(a) the word, Copyright; (b) year of the first publication; and (c) name of the author. "
"You may also wish to include the phrase \"all rights reserved\".
"
"With XMP, you can include more than one copyright string using different languages.
"
"With IPTC, this field is limited to 128 ASCII characters.
"));
// --------------------------------------------------------
d->rightUsageEdit = new AltLangStrEdit(page1);
d->rightUsageEdit->setTitle(i18n("Right Usage Terms:"));
d->rightUsageEdit->setFixedHeight(75);
d->rightUsageEdit->setPlaceholderText(i18n("Enter the list of instructions on how a resource can be legally used here."));
d->rightUsageEdit->setWhatsThis(i18n("The Right Usage Terms field should be used to list instructions on how "
- "a resource can be legally used."
+ "a resource can be legally used.
"
"With XMP, you can include more than one right usage terms string using "
"different languages.
"
"This field does not exist with IPTC.
"));
// --------------------------------------------------------
QLabel* const label6 = new QLabel(i18n("Source:"), page1);
d->sourceEdit = new QLineEdit(page1);
d->sourceEdit->setClearButtonEnabled(true);
d->sourceEdit->setPlaceholderText(i18n("Enter the original owner of the photograph here."));
label6->setBuddy(d->sourceEdit);
d->sourceEdit->setWhatsThis(i18n("The Source field should be used to identify the original owner or copyright holder of the "
"photograph. The value of this field should never be changed after the information is entered "
"following the image's creation. While not yet enforced by the custom panels, you should consider "
"this to be a \"write-once\" field. The source could be an individual, an agency, or a "
"member of an agency. To aid in later searches, it is suggested to separate any slashes "
"\"/\" with a blank space. Use the form \"photographer / agency\" rather than "
"\"photographer/agency.\" Source may also be different from Creator and from the names "
"listed in the Copyright Notice.
"
"With IPTC, this field is limited to 32 ASCII characters.
"));
// --------------------------------------------------------
QLabel* const label7 = new QLabel(i18n("Instructions:"), page1);
d->instructionsEdit = new QLineEdit(page1);
d->instructionsEdit->setClearButtonEnabled(true);
d->instructionsEdit->setPlaceholderText(i18n("Enter the editorial notice here."));
label7->setBuddy(d->instructionsEdit);
d->instructionsEdit->setWhatsThis(i18n("The Instructions field should be used to list editorial "
"instructions concerning the use of photograph.
"
"With IPTC, this field is limited to 256 ASCII characters.
"));
// --------------------------------------------------------
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
grid1->setContentsMargins(spacing, spacing, spacing, spacing);
grid1->setSpacing(spacing);
grid1->setAlignment(Qt::AlignTop);
grid1->setColumnStretch(1, 10);
grid1->addWidget(label1, 0, 0, 1, 1);
grid1->addWidget(d->authorsEdit, 0, 1, 1, 2);
grid1->addWidget(label2, 1, 0, 1, 1);
grid1->addWidget(d->authorsPositionEdit, 1, 1, 1, 2);
grid1->addWidget(label3, 2, 0, 1, 1);
grid1->addWidget(d->creditEdit, 2, 1, 1, 2);
grid1->addWidget(d->copyrightEdit, 3, 0, 1, 2);
grid1->addWidget(d->rightUsageEdit, 4, 0, 1, 2);
grid1->addWidget(label6, 5, 0, 1, 1);
grid1->addWidget(d->sourceEdit, 5, 1, 1, 2);
grid1->addWidget(label7, 6, 0, 1, 1);
grid1->addWidget(d->instructionsEdit, 6, 1, 1, 2);
insertTab(RIGHTS, page1, QIcon::fromTheme(QLatin1String("flag")), i18n("Rights"));
// -- Location Template information panel -------------------------------------------------------------
QWidget* const page2 = new QWidget(this);
// --------------------------------------------------------
QLabel* const label9 = new QLabel(i18n("City:"));
d->locationCityEdit = new QLineEdit;
d->locationCityEdit->setClearButtonEnabled(true);
d->locationCityEdit->setPlaceholderText(i18n("Enter the city of contents here."));
label9->setBuddy(d->locationCityEdit);
d->locationCityEdit->setWhatsThis(i18n("This field should contain the name of the city "
"where the photograph was taken.
"));
// --------------------------------------------------------
QLabel* const label10 = new QLabel(i18n("Sublocation:"));
d->locationSublocationEdit = new QLineEdit;
d->locationSublocationEdit->setClearButtonEnabled(true);
d->locationSublocationEdit->setPlaceholderText(i18n("Enter the city sublocation of contents here."));
label10->setBuddy(d->locationSublocationEdit);
d->locationSublocationEdit->setWhatsThis(i18n("This field should contain the sublocation of the city "
"where the photograph was taken.
"));
// --------------------------------------------------------
QLabel* const label11 = new QLabel(i18n("Province/State:"));
d->locationProvinceStateEdit = new QLineEdit;
d->locationProvinceStateEdit->setClearButtonEnabled(true);
d->locationProvinceStateEdit->setPlaceholderText(i18n("Enter the province or state of contents here."));
label11->setBuddy(d->locationProvinceStateEdit);
d->locationProvinceStateEdit->setWhatsThis(i18n("This field should contain the province or state "
"where the photograph was taken.
"));
// --------------------------------------------------------
QLabel* const label12 = new QLabel(i18n("Country:"));
d->locationCountryCodeEdit = new CountrySelector(page2);
label12->setBuddy(d->locationCountryCodeEdit);
d->locationCountryCodeEdit->setWhatsThis(i18n("Select here the country "
"where the photograph was taken.
"));
// --------------------------------------------------------
QGridLayout* grid2 = new QGridLayout;
grid2->addWidget(label9, 0, 0, 1, 1);
grid2->addWidget(d->locationCityEdit, 0, 1, 1, 2);
grid2->addWidget(label10, 1, 0, 1, 1);
grid2->addWidget(d->locationSublocationEdit, 1, 1, 1, 2);
grid2->addWidget(label11, 2, 0, 1, 1);
grid2->addWidget(d->locationProvinceStateEdit, 2, 1, 1, 2);
grid2->addWidget(label12, 3, 0, 1, 1);
grid2->addWidget(d->locationCountryCodeEdit, 3, 1, 1, 2);
grid2->setContentsMargins(spacing, spacing, spacing, spacing);
grid2->setSpacing(spacing);
grid2->setAlignment(Qt::AlignTop);
grid2->setColumnStretch(1, 10);
grid2->setRowStretch(4, 10);
page2->setLayout(grid2);
page2->setTabOrder(d->locationCityEdit, d->locationSublocationEdit);
page2->setTabOrder(d->locationSublocationEdit, d->locationProvinceStateEdit);
page2->setTabOrder(d->locationProvinceStateEdit, d->locationCountryCodeEdit);
insertTab(LOCATION, page2, QIcon::fromTheme(QLatin1String("globe")), i18n("Location"));
// -- Contact Template information panel -------------------------------------------------------------
QWidget* const page3 = new QWidget(this);
QLabel* const label13 = new QLabel(i18n("City:"), page3);
d->contactCityEdit = new QLineEdit(page3);
d->contactCityEdit->setClearButtonEnabled(true);
d->contactCityEdit->setPlaceholderText(i18n("Enter the city name of the lead author here."));
label13->setBuddy(d->contactCityEdit);
d->contactCityEdit->setWhatsThis(i18n("This field should contain the city name "
"where the lead author lives.
"));
// --------------------------------------------------------
QLabel* const label14 = new QLabel(i18n("Country:"), page3);
d->contactCountryEdit = new QLineEdit(page3);
d->contactCountryEdit->setClearButtonEnabled(true);
d->contactCountryEdit->setPlaceholderText(i18n("Enter the country name of the lead author here."));
label14->setBuddy(d->contactCountryEdit);
d->contactCountryEdit->setWhatsThis(i18n("This field should contain the country name "
"where the lead author lives.
"));
// --------------------------------------------------------
QLabel* const label15 = new QLabel(i18n("Address:"), page3);
d->contactAddressEdit = new QLineEdit(page3);
d->contactAddressEdit->setClearButtonEnabled(true);
d->contactAddressEdit->setPlaceholderText(i18n("Enter the address of the lead author here."));
label15->setBuddy(d->contactAddressEdit);
d->contactAddressEdit->setWhatsThis(i18n("This field should contain the address "
"where the lead author lives.
"));
// --------------------------------------------------------
QLabel* const label16 = new QLabel(i18n("Postal Code:"), page3);
d->contactPostalCodeEdit = new QLineEdit(page3);
d->contactPostalCodeEdit->setClearButtonEnabled(true);
d->contactPostalCodeEdit->setPlaceholderText(i18n("Enter the postal code of the lead author here."));
label16->setBuddy(d->contactPostalCodeEdit);
d->contactPostalCodeEdit->setWhatsThis(i18n("This field should contain the postal code "
"where the lead author lives.
"));
// --------------------------------------------------------
QLabel* const label17 = new QLabel(i18n("Province:"), page3);
d->contactProvinceStateEdit = new QLineEdit(page3);
d->contactProvinceStateEdit->setClearButtonEnabled(true);
d->contactProvinceStateEdit->setPlaceholderText(i18n("Enter the province of the lead author here."));
label17->setBuddy(d->contactProvinceStateEdit);
d->contactProvinceStateEdit->setWhatsThis(i18n("This field should contain the province "
"where the lead author lives.
"));
// --------------------------------------------------------
QLabel* const label18 = new QLabel(i18n("Email:"), page3);
d->contactEmailEdit = new QLineEdit(page3);
d->contactEmailEdit->setClearButtonEnabled(true);
d->contactEmailEdit->setPlaceholderText(i18n("Enter the email of the lead author here."));
label18->setBuddy(d->contactEmailEdit);
d->contactEmailEdit->setWhatsThis(i18n("This field should contain the email "
"of the lead author.
"));
// --------------------------------------------------------
QLabel* const label19 = new QLabel(i18n("Phone:"), page3);
d->contactPhoneEdit = new QLineEdit(page3);
d->contactPhoneEdit->setClearButtonEnabled(true);
d->contactPhoneEdit->setPlaceholderText(i18n("Enter the phone number of the lead author here."));
label19->setBuddy(d->contactPhoneEdit);
d->contactPhoneEdit->setWhatsThis(i18n("This field should contain the phone number "
"of the lead author.
"));
// --------------------------------------------------------
QLabel* const label20 = new QLabel(i18n("URL:"), page3);
d->contactWebUrlEdit = new QLineEdit(page3);
d->contactWebUrlEdit->setClearButtonEnabled(true);
d->contactWebUrlEdit->setPlaceholderText(i18n("Enter the web site URL of the lead author here."));
label20->setBuddy(d->contactWebUrlEdit);
d->contactWebUrlEdit->setWhatsThis(i18n("This field should contain the web site URL "
"of the lead author.
"));
// --------------------------------------------------------
QGridLayout* const grid3 = new QGridLayout;
grid3->addWidget(label15, 0, 0, 1, 1);
grid3->addWidget(d->contactAddressEdit, 0, 1, 1, 2);
grid3->addWidget(label16, 1, 0, 1, 1);
grid3->addWidget(d->contactPostalCodeEdit, 1, 1, 1, 2);
grid3->addWidget(label13, 2, 0, 1, 1);
grid3->addWidget(d->contactCityEdit, 2, 1, 1, 2);
grid3->addWidget(label17, 3, 0, 1, 1);
grid3->addWidget(d->contactProvinceStateEdit, 3, 1, 1, 2);
grid3->addWidget(label14, 4, 0, 1, 1);
grid3->addWidget(d->contactCountryEdit, 4, 1, 1, 2);
grid3->addWidget(label19, 5, 0, 1, 1);
grid3->addWidget(d->contactPhoneEdit, 5, 1, 1, 2);
grid3->addWidget(label18, 6, 0, 1, 1);
grid3->addWidget(d->contactEmailEdit, 6, 1, 1, 2);
grid3->addWidget(label20, 7, 0, 1, 1);
grid3->addWidget(d->contactWebUrlEdit, 7, 1, 1, 2);
grid3->setContentsMargins(spacing, spacing, spacing, spacing);
grid3->setSpacing(spacing);
grid3->setAlignment(Qt::AlignTop);
grid3->setColumnStretch(1, 10);
grid3->setRowStretch(8, 10);
page3->setLayout(grid3);
page3->setTabOrder(d->contactAddressEdit, d->contactPostalCodeEdit);
page3->setTabOrder(d->contactPostalCodeEdit, d->contactCityEdit);
page3->setTabOrder(d->contactCityEdit, d->contactProvinceStateEdit);
page3->setTabOrder(d->contactProvinceStateEdit, d->contactCountryEdit);
page3->setTabOrder(d->contactCountryEdit, d->contactPhoneEdit);
page3->setTabOrder(d->contactPhoneEdit, d->contactEmailEdit);
page3->setTabOrder(d->contactEmailEdit, d->contactWebUrlEdit);
insertTab(CONTACT, page3, QIcon::fromTheme(QLatin1String("view-pim-contacts")), i18n("Contact"));
// -- Subjects Template information panel -------------------------------------------------------------
QWidget* const page4 = new QWidget(this);
QGridLayout* const grid4 = new QGridLayout(page4);
d->subjects = new SubjectEdit(page4);
grid4->setContentsMargins(spacing, spacing, spacing, spacing);
grid4->setSpacing(spacing);
grid4->setAlignment(Qt::AlignTop);
grid4->addWidget(d->subjects, 0, 0, 1, 1);
grid4->setRowStretch(1, 10);
insertTab(SUBJECTS, page4, QIcon::fromTheme(QLatin1String("feed-subscribe")), i18n("Subjects"));
}
TemplatePanel::~TemplatePanel()
{
delete d;
}
void TemplatePanel::setTemplate(const Template& t)
{
d->authorsEdit->setText(t.authors().join(QLatin1Char(';')));
d->authorsPositionEdit->setText(t.authorsPosition());
d->creditEdit->setText(t.credit());
d->copyrightEdit->setValues(t.copyright());
d->rightUsageEdit->setValues(t.rightUsageTerms());
d->sourceEdit->setText(t.source());
d->instructionsEdit->setText(t.instructions());
d->locationCountryCodeEdit->setCountry(t.locationInfo().countryCode);
d->locationProvinceStateEdit->setText(t.locationInfo().provinceState);
d->locationCityEdit->setText(t.locationInfo().city);
d->locationSublocationEdit->setText(t.locationInfo().location);
d->contactCityEdit->setText(t.contactInfo().city);
d->contactCountryEdit->setText(t.contactInfo().country);
d->contactAddressEdit->setText(t.contactInfo().address);
d->contactPostalCodeEdit->setText(t.contactInfo().postalCode);
d->contactProvinceStateEdit->setText(t.contactInfo().provinceState);
d->contactEmailEdit->setText(t.contactInfo().email);
d->contactPhoneEdit->setText(t.contactInfo().phone);
d->contactWebUrlEdit->setText(t.contactInfo().webUrl);
d->subjects->setSubjectsList(t.IptcSubjects());
}
Template TemplatePanel::getTemplate() const
{
Template t;
t.setAuthors(d->authorsEdit->text().split(QLatin1Char(';'), QString::SkipEmptyParts));
t.setAuthorsPosition(d->authorsPositionEdit->text());
t.setCredit(d->creditEdit->text());
t.setCopyright(d->copyrightEdit->values());
t.setRightUsageTerms(d->rightUsageEdit->values());
t.setSource(d->sourceEdit->text());
t.setInstructions(d->instructionsEdit->text());
IptcCoreLocationInfo inf1;
d->locationCountryCodeEdit->country(inf1.countryCode, inf1.country);
inf1.provinceState = d->locationProvinceStateEdit->text();
inf1.city = d->locationCityEdit->text();
inf1.location = d->locationSublocationEdit->text();
t.setLocationInfo(inf1);
IptcCoreContactInfo inf2;
inf2.city = d->contactCityEdit->text();
inf2.country = d->contactCountryEdit->text();
inf2.address = d->contactAddressEdit->text();
inf2.postalCode = d->contactPostalCodeEdit->text();
inf2.provinceState = d->contactProvinceStateEdit->text();
inf2.email = d->contactEmailEdit->text();
inf2.phone = d->contactPhoneEdit->text();
inf2.webUrl = d->contactWebUrlEdit->text();
t.setContactInfo(inf2);
t.setIptcSubjects(d->subjects->subjectsList());
return t;
}
void TemplatePanel::apply()
{
}
} // namespace Digikam
diff --git a/core/utilities/assistants/expoblending/blendingdlg/enfusesettings.cpp b/core/utilities/assistants/expoblending/blendingdlg/enfusesettings.cpp
index d22309afc8..0077c90262 100644
--- a/core/utilities/assistants/expoblending/blendingdlg/enfusesettings.cpp
+++ b/core/utilities/assistants/expoblending/blendingdlg/enfusesettings.cpp
@@ -1,285 +1,285 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-11-13
* Description : a tool to blend bracketed images.
*
* Copyright (C) 2009-2018 by Gilles Caulier
* Copyright (C) 2015 by Benjamin Girault,
*
* 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 "enfusesettings.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
#include
#include
namespace Digikam
{
QString EnfuseSettings::asCommentString() const
{
QString ret;
ret.append(hardMask ? i18n("Hardmask: enabled") : i18n("Hardmask: disabled"));
ret.append(QLatin1Char('\n'));
ret.append(ciecam02 ? i18n("CIECAM02: enabled") : i18n("CIECAM02: disabled"));
ret.append(QLatin1Char('\n'));
ret.append(autoLevels ? i18n("Levels: auto") : i18n("Levels: %1", QString::number(levels)));
ret.append(QLatin1Char('\n'));
- ret.append(i18n("Exposure: %1", QString::number(exposure)));
+ ret.append(i18n("Exposure: %1", exposure));
ret.append(QLatin1Char('\n'));
- ret.append(i18n("Saturation: %1", QString::number(saturation)));
+ ret.append(i18n("Saturation: %1", saturation));
ret.append(QLatin1Char('\n'));
- ret.append(i18n("Contrast: %1", QString::number(contrast)));
+ ret.append(i18n("Contrast: %1", contrast));
return ret;
}
QString EnfuseSettings::inputImagesList() const
{
QString ret;
foreach(const QUrl& url, inputUrls)
{
ret.append(url.fileName() + QLatin1String(" ; "));
}
ret.truncate(ret.length()-3);
return ret;
}
class Q_DECL_HIDDEN EnfuseSettingsWidget::Private
{
public:
explicit Private()
: autoLevelsCB(0),
hardMaskCB(0),
ciecam02CB(0),
levelsLabel(0),
exposureLabel(0),
saturationLabel(0),
contrastLabel(0),
levelsInput(0),
exposureInput(0),
saturationInput(0),
contrastInput(0)
{
}
public:
QCheckBox* autoLevelsCB;
QCheckBox* hardMaskCB;
QCheckBox* ciecam02CB;
QLabel* levelsLabel;
QLabel* exposureLabel;
QLabel* saturationLabel;
QLabel* contrastLabel;
QSpinBox* levelsInput;
QDoubleSpinBox* exposureInput;
QDoubleSpinBox* saturationInput;
QDoubleSpinBox* contrastInput;
};
EnfuseSettingsWidget::EnfuseSettingsWidget(QWidget* const parent)
: QWidget(parent),
d(new Private)
{
setAttribute(Qt::WA_DeleteOnClose);
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
QGridLayout* const grid = new QGridLayout(this);
// ------------------------------------------------------------------------
d->autoLevelsCB = new QCheckBox(i18nc("@option:check Enfuse setting", "Automatic Local/Global Image Features Balance (Levels)"), this);
d->autoLevelsCB->setToolTip(i18nc("@info:tooltip",
"Optimize image features (contrast, saturation, . . .) to be as global as possible."));
d->autoLevelsCB->setWhatsThis(i18nc("@info:whatsthis",
"Set automatic level selection (maximized) for pyramid blending, "
"i.e. optimize image features (contrast, saturation, . . .) to be as global as possible."));
d->levelsLabel = new QLabel(i18nc("@label:slider Enfuse settings", "Image Features Balance:"));
d->levelsInput = new QSpinBox(this);
d->levelsInput->setRange(1, 29);
d->levelsInput->setSingleStep(1);
d->levelsInput->setToolTip(i18nc("@info:tooltip",
"Balances between local features (small number) or global features (high number)."));
d->levelsInput->setWhatsThis(i18nc("@info:whatsthis",
"Set the number of levels for pyramid blending. "
"Balances towards local features (small number) or global features (high number). "
"Additionally, a low number trades off quality of results for faster "
"execution time and lower memory usage."));
d->hardMaskCB = new QCheckBox(i18nc("@option:check", "Hard Mask"), this);
d->hardMaskCB->setToolTip(i18nc("@info:tooltip",
"Useful only for focus stack to improve sharpness."));
d->hardMaskCB->setWhatsThis(i18nc("@info:whatsthis",
"Force hard blend masks without averaging on finest "
"scale. This is only useful for focus "
"stacks with thin and high contrast features. "
"It improves sharpness at the expense of increased noise."));
d->exposureLabel = new QLabel(i18nc("@label:slider Enfuse settings", "Well-Exposedness Contribution:"));
d->exposureInput = new QDoubleSpinBox(this);
d->exposureInput->setRange(0.0, 1.0);
d->exposureInput->setSingleStep(0.01);
d->exposureInput->setToolTip(i18nc("@info:tooltip",
"Contribution of well exposed pixels to the blending process."));
d->exposureInput->setWhatsThis(i18nc("@info:whatsthis",
"Set the well-exposedness criterion contribution for the blending process. "
"Higher values will favor well-exposed pixels."));
d->saturationLabel = new QLabel(i18nc("@label:slider enfuse settings", "High-Saturation Contribution:"));
d->saturationInput = new QDoubleSpinBox(this);
d->saturationInput->setDecimals(2);
d->saturationInput->setRange(0.0, 1.0);
d->saturationInput->setSingleStep(0.01);
d->saturationInput->setToolTip(i18nc("@info:tooltip",
"Contribution of highly saturated pixels to the blending process."));
d->saturationInput->setWhatsThis(i18nc("@info:whatsthis",
"Increasing this value makes pixels with high "
"saturation contribute more to the final output."));
d->contrastLabel = new QLabel(i18nc("@label:slider enfuse settings", "High-Contrast Contribution:"));
d->contrastInput = new QDoubleSpinBox(this);
d->contrastInput->setDecimals(2);
d->contrastInput->setRange(0.0, 1.0);
d->contrastInput->setSingleStep(0.01);
d->contrastInput->setToolTip(i18nc("@info:tooltip",
"Contribution of highly contrasted pixels to the blending process."));
d->contrastInput->setWhatsThis(i18nc("@info:whatsthis",
"Sets the relative weight of high-contrast pixels. "
"Increasing this weight makes pixels with neighboring differently colored "
"pixels contribute more to the final output. Particularly useful for focus stacks."));
d->ciecam02CB = new QCheckBox(i18nc("@option:check", "Use Color Appearance Model (CIECAM02)"), this);
d->ciecam02CB->setToolTip(i18nc("@info:tooltip",
"Convert to CIECAM02 color appearance model during the blending process instead of RGB."));
d->ciecam02CB->setWhatsThis(i18nc("@info:whatsthis",
"Use Color Appearance Modelling (CIECAM02) to render detailed colors. "
"Your input files should have embedded ICC profiles. If no ICC profile is present, "
"sRGB color space will be assumed. The difference between using this option "
"and default color blending algorithm is very slight, and will be most noticeable "
"when you need to blend areas of different primary colors together."));
// ------------------------------------------------------------------------
grid->addWidget(d->autoLevelsCB, 0, 0, 1, 2);
grid->addWidget(d->levelsLabel, 1, 0, 1, 1);
grid->addWidget(d->levelsInput, 1, 1, 1, 1);
grid->addWidget(d->hardMaskCB, 2, 0, 1, 2);
grid->addWidget(d->exposureLabel, 3, 0, 1, 1);
grid->addWidget(d->exposureInput, 3, 1, 1, 1);
grid->addWidget(d->saturationLabel, 4, 0, 1, 1);
grid->addWidget(d->saturationInput, 4, 1, 1, 1);
grid->addWidget(d->contrastLabel, 5, 0, 1, 1);
grid->addWidget(d->contrastInput, 5, 1, 1, 1);
grid->addWidget(d->ciecam02CB, 6, 0, 1, 2);
grid->setRowStretch(7, 10);
grid->setContentsMargins(spacing, spacing, spacing, spacing);
grid->setSpacing(spacing);
// ------------------------------------------------------------------------
connect(d->autoLevelsCB, SIGNAL(toggled(bool)),
d->levelsLabel, SLOT(setDisabled(bool)));
connect(d->autoLevelsCB, SIGNAL(toggled(bool)),
d->levelsInput, SLOT(setDisabled(bool)));
}
EnfuseSettingsWidget::~EnfuseSettingsWidget()
{
delete d;
}
void EnfuseSettingsWidget::resetToDefault()
{
d->autoLevelsCB->setChecked(true);
d->levelsInput->setValue(20);
d->hardMaskCB->setChecked(false);
d->exposureInput->setValue(1.0);
d->saturationInput->setValue(0.2);
d->contrastInput->setValue(0.0);
d->ciecam02CB->setChecked(false);
}
void EnfuseSettingsWidget::setSettings(const EnfuseSettings& settings)
{
d->autoLevelsCB->setChecked(settings.autoLevels);
d->levelsInput->setValue(settings.levels);
d->hardMaskCB->setChecked(settings.hardMask);
d->exposureInput->setValue(settings.exposure);
d->saturationInput->setValue(settings.saturation);
d->contrastInput->setValue(settings.contrast);
d->ciecam02CB->setChecked(settings.ciecam02);
}
EnfuseSettings EnfuseSettingsWidget::settings() const
{
EnfuseSettings settings;
settings.autoLevels = d->autoLevelsCB->isChecked();
settings.levels = d->levelsInput->value();
settings.hardMask = d->hardMaskCB->isChecked();
settings.exposure = d->exposureInput->value();
settings.saturation = d->saturationInput->value();
settings.contrast = d->contrastInput->value();
settings.ciecam02 = d->ciecam02CB->isChecked();
return settings;
}
void EnfuseSettingsWidget::readSettings(KConfigGroup& group)
{
d->autoLevelsCB->setChecked(group.readEntry("Auto Levels", true));
d->levelsInput->setValue(group.readEntry("Levels Value", 20));
d->hardMaskCB->setChecked(group.readEntry("Hard Mask", false));
d->exposureInput->setValue(group.readEntry("Exposure Value", 1.0));
d->saturationInput->setValue(group.readEntry("Saturation Value", 0.2));
d->contrastInput->setValue(group.readEntry("Contrast Value", 0.0));
d->ciecam02CB->setChecked(group.readEntry("CIECAM02", false));
}
void EnfuseSettingsWidget::writeSettings(KConfigGroup& group)
{
group.writeEntry("Auto Levels", d->autoLevelsCB->isChecked());
group.writeEntry("Levels Value", d->levelsInput->value());
group.writeEntry("Hard Mask", d->hardMaskCB->isChecked());
group.writeEntry("Exposure Value", d->exposureInput->value());
group.writeEntry("Saturation Value", d->saturationInput->value());
group.writeEntry("Contrast Value", d->contrastInput->value());
group.writeEntry("CIECAM02", d->ciecam02CB->isChecked());
}
} // namespace Digikam
diff --git a/core/utilities/assistants/panorama/wizard/panopreviewpage.cpp b/core/utilities/assistants/panorama/wizard/panopreviewpage.cpp
index f69c745a3e..a16b3d195d 100644
--- a/core/utilities/assistants/panorama/wizard/panopreviewpage.cpp
+++ b/core/utilities/assistants/panorama/wizard/panopreviewpage.cpp
@@ -1,640 +1,640 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-05-23
* Description : a tool to create panorama by fusion of several images.
*
* Copyright (C) 2011-2016 by Benjamin Girault
* Copyright (C) 2011-2018 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 "panopreviewpage.h"
// Qt includes
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "digikam_debug.h"
#include "dpreviewmanager.h"
#include "dprogresswdg.h"
#include "dhistoryview.h"
#include "panomanager.h"
#include "panoactionthread.h"
#include "enblendbinary.h"
#include "makebinary.h"
#include "nonabinary.h"
#include "pto2mkbinary.h"
#include "huginexecutorbinary.h"
#include "dlayoutbox.h"
namespace Digikam
{
class Q_DECL_HIDDEN PanoPreviewPage::Private
{
public:
explicit Private(PanoManager* const m)
: title(0),
previewWidget(0),
previewBusy(false),
previewDone(false),
stitchingBusy(false),
stitchingDone(false),
postProcessing(0),
progressBar(0),
curProgress(0),
totalProgress(0),
canceled(false),
mngr(m),
dlg(0)
{
}
QLabel* title;
DPreviewManager* previewWidget;
bool previewBusy;
bool previewDone;
bool stitchingBusy;
bool stitchingDone;
DHistoryView* postProcessing;
DProgressWdg* progressBar;
int curProgress, totalProgress;
QMutex previewBusyMutex; // This is a precaution in case the user does a back / next action at the wrong moment
bool canceled;
QString output;
PanoManager* mngr;
QWizard* dlg;
};
PanoPreviewPage::PanoPreviewPage(PanoManager* const mngr, QWizard* const dlg)
: DWizardPage(dlg, i18nc("@title:window", "Preview and Post-Processing")),
d(new Private(mngr))
{
d->dlg = dlg;
DVBox* const vbox = new DVBox(this);
d->title = new QLabel(vbox);
d->title->setOpenExternalLinks(true);
d->title->setWordWrap(true);
d->previewWidget = new DPreviewManager(vbox);
d->previewWidget->setButtonText(i18nc("@action:button", "Details..."));
d->postProcessing = new DHistoryView(vbox);
d->progressBar = new DProgressWdg(vbox);
setPageWidget(vbox);
QPixmap leftPix(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/assistant-hugin.png")));
setLeftBottomPix(leftPix.scaledToWidth(128, Qt::SmoothTransformation));
connect(d->progressBar, SIGNAL(signalProgressCanceled()),
this, SLOT(slotCancel()));
}
PanoPreviewPage::~PanoPreviewPage()
{
delete d;
}
void PanoPreviewPage::computePreview()
{
// Cancel any stitching being processed
if (d->stitchingBusy)
{
cleanupPage();
}
QMutexLocker lock(&d->previewBusyMutex);
connect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
connect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->canceled = false;
d->previewWidget->setBusy(true, i18n("Processing Panorama Preview..."));
d->previewDone = false;
d->previewBusy = true;
d->mngr->resetPreviewPto();
d->mngr->resetPreviewUrl();
d->mngr->resetPreviewMkUrl();
d->mngr->thread()->generatePanoramaPreview(d->mngr->viewAndCropOptimisePtoData(),
d->mngr->previewPtoUrl(),
d->mngr->previewMkUrl(),
d->mngr->previewUrl(),
d->mngr->preProcessedMap(),
d->mngr->makeBinary().path(),
d->mngr->pto2MkBinary().path(),
d->mngr->huginExecutorBinary().path(),
d->mngr->hugin2015(),
d->mngr->enblendBinary().path(),
d->mngr->nonaBinary().path());
}
void PanoPreviewPage::startStitching()
{
QMutexLocker lock(&d->previewBusyMutex);
if (d->previewBusy)
{
// The real beginning of the stitching starts after preview has finished / failed
connect(this, SIGNAL(signalPreviewFinished()), this, SLOT(slotStartStitching()));
cleanupPage(lock);
return;
}
connect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
connect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
connect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->canceled = false;
d->stitchingBusy = true;
d->curProgress = 0;
if (d->mngr->hugin2015())
{
d->totalProgress = 1;
}
else
{
d->totalProgress = d->mngr->preProcessedMap().size() + 1;
}
d->previewWidget->hide();
QSize panoSize = d->mngr->viewAndCropOptimisePtoData()->project.size;
QRect panoSelection = d->mngr->viewAndCropOptimisePtoData()->project.crop;
if (d->previewDone)
{
QSize previewSize = d->mngr->previewPtoData()->project.size;
QRectF selection = d->previewWidget->getSelectionArea();
QRectF proportionSelection(selection.x() / previewSize.width(),
selection.y() / previewSize.height(),
selection.width() / previewSize.width(),
selection.height() / previewSize.height());
// At this point, if no selection area was created, proportionSelection is null,
// hence panoSelection becomes a null rectangle
panoSelection = QRect(proportionSelection.x() * panoSize.width(),
proportionSelection.y() * panoSize.height(),
proportionSelection.width() * panoSize.width(),
proportionSelection.height() * panoSize.height());
}
d->title->setText(i18n(""
"Panorama Post-Processing
"
""));
d->progressBar->reset();
d->progressBar->setMaximum(d->totalProgress);
d->progressBar->progressScheduled(i18nc("@title:group", "Panorama Post-Processing"), true, true);
d->progressBar->progressThumbnailChanged(QIcon::fromTheme(QLatin1String("panorama")).pixmap(22, 22));
d->progressBar->show();
d->postProcessing->show();
d->mngr->resetPanoPto();
d->mngr->resetMkUrl();
d->mngr->resetPanoUrl();
d->mngr->thread()->compileProject(d->mngr->viewAndCropOptimisePtoData(),
d->mngr->panoPtoUrl(),
d->mngr->mkUrl(),
d->mngr->panoUrl(),
d->mngr->preProcessedMap(),
d->mngr->format(),
panoSelection,
d->mngr->makeBinary().path(),
d->mngr->pto2MkBinary().path(),
d->mngr->huginExecutorBinary().path(),
d->mngr->hugin2015(),
d->mngr->enblendBinary().path(),
d->mngr->nonaBinary().path());
}
void PanoPreviewPage::preInitializePage()
{
d->title->setText(QString());
d->previewWidget->show();
d->progressBar->progressCompleted();
d->progressBar->hide();
d->postProcessing->hide();
setComplete(true);
emit completeChanged();
}
void PanoPreviewPage::initializePage()
{
preInitializePage();
computePreview();
}
bool PanoPreviewPage::validatePage()
{
if (d->stitchingDone)
return true;
setComplete(false);
startStitching();
return false;
}
void PanoPreviewPage::cleanupPage()
{
QMutexLocker lock(&d->previewBusyMutex);
cleanupPage(lock);
}
void PanoPreviewPage::cleanupPage(QMutexLocker& /*lock*/)
{
d->canceled = true;
d->mngr->thread()->cancel();
d->progressBar->progressCompleted();
if (d->previewBusy)
{
d->previewBusy = false;
d->previewWidget->setBusy(false);
d->previewWidget->setText(i18n("Preview Processing Cancelled."));
}
else if (d->stitchingBusy)
{
d->stitchingBusy = false;
}
}
void PanoPreviewPage::slotCancel()
{
d->dlg->reject();
}
void PanoPreviewPage::slotStartStitching()
{
disconnect(this, SIGNAL(signalPreviewFinished()),
this, SLOT(slotStartStitching()));
startStitching();
}
void PanoPreviewPage::slotPanoAction(const Digikam::PanoActionData& ad)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "SlotPanoAction (preview)";
qCDebug(DIGIKAM_GENERAL_LOG) << "\tstarting, success, canceled, action: " << ad.starting << ad.success << d->canceled << ad.action;
QString text;
QMutexLocker lock(&d->previewBusyMutex);
qCDebug(DIGIKAM_GENERAL_LOG) << "\tbusy (preview / stitch):" << d->previewBusy << d->stitchingBusy;
if (!ad.starting) // Something is complete...
{
if (!ad.success) // Something is failed...
{
switch (ad.action)
{
case PANO_CREATEPREVIEWPTO:
case PANO_NONAFILEPREVIEW:
case PANO_STITCHPREVIEW:
case PANO_CREATEMKPREVIEW:
case PANO_HUGINEXECUTORPREVIEW:
{
if (!d->previewBusy)
{
lock.unlock();
emit signalPreviewFinished();
return;
}
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->output = ad.message;
d->previewWidget->setBusy(false);
d->previewBusy = false;
qCWarning(DIGIKAM_GENERAL_LOG) << "Preview compilation failed: " << ad.message;
QString errorString(i18n("Error
%1
",
ad.message));
d->previewWidget->setText(errorString);
d->previewWidget->setSelectionAreaPossible(false);
setComplete(false);
emit completeChanged();
lock.unlock();
emit signalPreviewFinished();
break;
}
case PANO_CREATEMK:
{
if (!d->stitchingBusy)
{
return;
}
disconnect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->stitchingBusy = false;
QString message = i18nc("Here a makefile is a script for GNU Make",
"Cannot create makefile:
%1
",
ad.message);
qCWarning(DIGIKAM_GENERAL_LOG) << "pto2mk call failed";
d->postProcessing->addEntry(message, DHistoryView::ErrorEntry);
setComplete(false);
emit completeChanged();
break;
}
case PANO_CREATEFINALPTO:
{
if (!d->stitchingBusy)
{
return;
}
disconnect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->stitchingBusy = false;
QString message = i18nc("a project file is a .pto file, as given to hugin to build a panorama",
"Cannot create project file:
%1
",
ad.message);
qCWarning(DIGIKAM_GENERAL_LOG) << "pto creation failed";
d->postProcessing->addEntry(message, DHistoryView::ErrorEntry);
setComplete(false);
emit completeChanged();
break;
}
case PANO_NONAFILE:
{
if (!d->stitchingBusy)
{
return;
}
disconnect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->stitchingBusy = false;
QString message = i18nc("Error message for image file number %1 out of %2",
"Processing file %1 / %2:
%3
",
- QString::number(ad.id + 1),
- QString::number(d->totalProgress - 1),
+ ad.id + 1,
+ d->totalProgress - 1,
ad.message);
qCWarning(DIGIKAM_GENERAL_LOG) << "Nona call failed for file #" << ad.id;
d->postProcessing->addEntry(message, DHistoryView::ErrorEntry);
setComplete(false);
emit completeChanged();
break;
}
case PANO_STITCH:
case PANO_HUGINEXECUTOR:
{
if (!d->stitchingBusy)
{
return;
}
disconnect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->stitchingBusy = false;
d->postProcessing->addEntry(i18nc("Error message for panorama compilation",
"Panorama compilation:
%1
",
ad.message.toHtmlEscaped()), DHistoryView::ErrorEntry);
qCWarning(DIGIKAM_GENERAL_LOG) << "Enblend call failed";
setComplete(false);
emit completeChanged();
break;
}
default:
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Unknown action (preview) " << ad.action;
break;
}
}
}
else // Something is done...
{
switch (ad.action)
{
case PANO_CREATEPREVIEWPTO:
case PANO_CREATEMKPREVIEW:
case PANO_NONAFILEPREVIEW:
case PANO_CREATEFINALPTO:
case PANO_CREATEMK:
{
// Nothing to do yet, a step is finished, that's all
break;
}
case PANO_STITCHPREVIEW:
case PANO_HUGINEXECUTORPREVIEW:
{
if (d->previewBusy)
{
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
}
d->previewBusy = false;
d->previewDone = true;
lock.unlock();
emit signalPreviewFinished();
d->title->setText(i18n(""
"Panorama Preview
"
"Draw a rectangle if you want to crop the image.
"
"Pressing the Next button will then launch the final "
"stitching process.
"
""));
d->previewWidget->setSelectionAreaPossible(true);
// d->previewWidget->load(QUrl::fromLocalFile(d->mngr->previewUrl().toLocalFile()), true);
d->previewWidget->load(d->mngr->previewUrl(), true);
QSize panoSize = d->mngr->viewAndCropOptimisePtoData()->project.size;
QRect panoCrop = d->mngr->viewAndCropOptimisePtoData()->project.crop;
QSize previewSize = d->mngr->previewPtoData()->project.size;
d->previewWidget->setSelectionArea(QRectF(
((double) panoCrop.left()) / panoSize.width() * previewSize.width(),
((double) panoCrop.top()) / panoSize.height() * previewSize.height(),
((double) panoCrop.width()) / panoSize.width() * previewSize.width(),
((double) panoCrop.height()) / panoSize.height() * previewSize.height()
));
break;
}
case PANO_NONAFILE:
{
QString message = i18nc("Success for image file number %1 out of %2",
"Processing file %1 / %2",
- QString::number(ad.id + 1),
- QString::number(d->totalProgress - 1));
+ ad.id + 1,
+ d->totalProgress - 1);
d->postProcessing->addEntry(message, DHistoryView::SuccessEntry);
d->curProgress++;
d->progressBar->setValue(d->curProgress);
d->progressBar->setMaximum(d->totalProgress);
break;
}
case PANO_STITCH:
case PANO_HUGINEXECUTOR:
{
if (!d->stitchingBusy)
{
return;
}
disconnect(d->mngr->thread(), SIGNAL(starting(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(stepFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
disconnect(d->mngr->thread(), SIGNAL(jobCollectionFinished(Digikam::PanoActionData)),
this, SLOT(slotPanoAction(Digikam::PanoActionData)));
d->stitchingBusy = false;
d->postProcessing->addEntry(i18nc("Success for panorama compilation", "Panorama compilation"), DHistoryView::SuccessEntry);
d->curProgress++;
d->progressBar->setValue(d->curProgress);
d->progressBar->setMaximum(d->totalProgress);
d->progressBar->progressCompleted();
d->progressBar->hide();
d->postProcessing->hide();
d->stitchingDone = true;
emit signalStitchingFinished();
preInitializePage();
break;
}
default:
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Unknown action (preview) " << ad.action;
break;
}
}
}
}
else // Some step is started...
{
switch (ad.action)
{
case PANO_CREATEPREVIEWPTO:
case PANO_CREATEMKPREVIEW:
case PANO_NONAFILEPREVIEW:
case PANO_STITCHPREVIEW:
case PANO_CREATEFINALPTO:
case PANO_CREATEMK:
case PANO_HUGINEXECUTORPREVIEW:
{
// Nothing to do...
break;
}
case PANO_NONAFILE:
{
QString message = i18nc("Compilation started for image file number %1 out of %2",
"Processing file %1 / %2",
- QString::number(ad.id + 1),
- QString::number(d->totalProgress - 1));
+ ad.id + 1,
+ d->totalProgress - 1);
d->postProcessing->addEntry(message, DHistoryView::StartingEntry);
break;
}
case PANO_STITCH:
case PANO_HUGINEXECUTOR:
{
d->postProcessing->addEntry(i18nc("Panorama compilation started", "Panorama compilation"), DHistoryView::StartingEntry);
break;
}
default:
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Unknown starting action (preview) " << ad.action;
break;
}
}
}
}
} // namespace Digikam
diff --git a/core/utilities/facemanagement/facescandialog.cpp b/core/utilities/facemanagement/facescandialog.cpp
index 8e14d3222c..5381473970 100644
--- a/core/utilities/facemanagement/facescandialog.cpp
+++ b/core/utilities/facemanagement/facescandialog.cpp
@@ -1,476 +1,476 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-10-09
* Description : Dialog to choose options for face scanning
*
* Copyright (C) 2010-2012 by Marcel Wiesweg
* Copyright (C) 2012-2018 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 "facescandialog.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
// Local includes
#include "dlayoutbox.h"
#include "dnuminput.h"
#include "digikam_debug.h"
#include "albummodel.h"
#include "albumselectors.h"
#include "applicationsettings.h"
#include "dexpanderbox.h"
namespace Digikam
{
class Q_DECL_HIDDEN FaceScanDialog::Private
{
public:
explicit Private()
: configName(QLatin1String("Face Detection Dialog")),
configMainTask(QLatin1String("Face Scan Main Task")),
configValueDetect(QLatin1String("Detect")),
configValueDetectAndRecognize(QLatin1String("Detect and Recognize Faces")),
configValueRecognizedMarkedFaces(QLatin1String("Recognize Marked Faces")),
configAlreadyScannedHandling(QLatin1String("Already Scanned Handling")),
configUseFullCpu(QLatin1String("Use Full CPU")),
configSettingsVisible(QLatin1String("Settings Widget Visible")),
configRecognizeAlgorithm(QLatin1String("Recognize Algorithm"))
{
buttons = 0;
optionGroupBox = 0;
detectAndRecognizeButton = 0;
detectButton = 0;
alreadyScannedBox = 0;
reRecognizeButton = 0;
tabWidget = 0;
albumSelectors = 0;
accuracyInput = 0;
useFullCpuButton = 0;
retrainAllButton = 0;
recognizeBox = 0;
}
QDialogButtonBox* buttons;
QGroupBox* optionGroupBox;
QRadioButton* detectAndRecognizeButton;
QRadioButton* detectButton;
QComboBox* alreadyScannedBox;
QRadioButton* reRecognizeButton;
QTabWidget* tabWidget;
AlbumSelectors* albumSelectors;
DIntNumInput* accuracyInput;
QCheckBox* useFullCpuButton;
QCheckBox* retrainAllButton;
QComboBox* recognizeBox;
const QString configName;
const QString configMainTask;
const QString configValueDetect;
const QString configValueDetectAndRecognize;
const QString configValueRecognizedMarkedFaces;
const QString configAlreadyScannedHandling;
const QString configUseFullCpu;
const QString configSettingsVisible;
const QString configRecognizeAlgorithm;
};
FaceScanDialog::FaceScanDialog(QWidget* const parent)
: QDialog(parent),
StateSavingObject(this),
d(new Private)
{
setWindowTitle(i18nc("@title:window", "Scanning faces"));
d->buttons = new QDialogButtonBox(QDialogButtonBox::Reset | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
d->buttons->button(QDialogButtonBox::Ok)->setDefault(true);
d->buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Scan"));
setupUi();
setupConnections();
setObjectName(d->configName);
loadState();
}
FaceScanDialog::~FaceScanDialog()
{
delete d;
}
void FaceScanDialog::doLoadState()
{
qCDebug(DIGIKAM_GENERAL_LOG) << getConfigGroup().name();
KConfigGroup group = getConfigGroup();
QString mainTask = group.readEntry(entryName(d->configMainTask),
d->configValueDetect);
if (mainTask == d->configValueRecognizedMarkedFaces)
{
d->reRecognizeButton->setChecked(true);
}
else if (mainTask == d->configValueDetectAndRecognize)
{
d->detectAndRecognizeButton->setChecked(true);
}
else
{
d->detectButton->setChecked(true);
}
FaceScanSettings::AlreadyScannedHandling handling;
QString skipHandling = group.readEntry(entryName(d->configAlreadyScannedHandling),
QString::fromLatin1("Skip"));
if (skipHandling == QLatin1String("Rescan"))
{
handling = FaceScanSettings::Rescan;
}
else if (skipHandling == QLatin1String("Merge"))
{
handling = FaceScanSettings::Merge;
}
else // Skip
{
handling = FaceScanSettings::Skip;
}
d->alreadyScannedBox->setCurrentIndex(d->alreadyScannedBox->findData(handling));
d->accuracyInput->setValue(ApplicationSettings::instance()->getFaceDetectionAccuracy() * 100);
d->albumSelectors->loadState();
d->useFullCpuButton->setChecked(group.readEntry(entryName(d->configUseFullCpu), false));
RecognitionDatabase::RecognizeAlgorithm algo =
(RecognitionDatabase::RecognizeAlgorithm)group.readEntry(entryName(d->configRecognizeAlgorithm),
(int)RecognitionDatabase::RecognizeAlgorithm::LBP);
d->recognizeBox->setCurrentIndex(d->recognizeBox->findData(algo));
// do not load retrainAllButton state from config, dangerous
d->tabWidget->setVisible(group.readEntry(entryName(d->configSettingsVisible), false));
adjustDetailsButton(d->tabWidget->isVisible());
}
void FaceScanDialog::doSaveState()
{
qCDebug(DIGIKAM_GENERAL_LOG) << getConfigGroup().name();
KConfigGroup group = getConfigGroup();
QString mainTask;
if (d->detectButton->isChecked())
{
mainTask = d->configValueDetect;
}
else if (d->detectAndRecognizeButton->isChecked())
{
mainTask = d->configValueDetectAndRecognize;
}
else // d->reRecognizeButton
{
mainTask = d->configValueRecognizedMarkedFaces;
}
group.writeEntry(entryName(d->configMainTask), mainTask);
QString handling;
switch ((FaceScanSettings::AlreadyScannedHandling)(d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt()))
{
case FaceScanSettings::Skip:
handling = QLatin1String("Skip");
break;
case FaceScanSettings::Rescan:
handling = QLatin1String("Rescan");
break;
case FaceScanSettings::Merge:
handling = QLatin1String("Merge");
break;
}
group.writeEntry(entryName(d->configAlreadyScannedHandling), handling);
ApplicationSettings::instance()->setFaceDetectionAccuracy(double(d->accuracyInput->value()) / 100);
d->albumSelectors->saveState();
group.writeEntry(entryName(d->configUseFullCpu), d->useFullCpuButton->isChecked());
group.writeEntry(entryName(d->configSettingsVisible), d->tabWidget->isVisible());
group.writeEntry(entryName(d->configRecognizeAlgorithm), d->recognizeBox->itemData(d->recognizeBox->currentIndex()));
}
void FaceScanDialog::setupUi()
{
QWidget* const mainWidget = new QWidget;
QGridLayout* const mainLayout = new QGridLayout;
d->tabWidget = new QTabWidget;
// ---- Introductory labels ----
QLabel* const personIcon = new QLabel;
personIcon->setPixmap(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(48));
QLabel* const introduction = new QLabel;
introduction->setTextFormat(Qt::RichText);
introduction->setText(i18nc("@info",
"digiKam can search for faces in your photos.
"
- "When you have identified your friends on a number of photos,
"
+ "When you have identified your friends on a number of photos,
"
"it can also recognize the people shown on your photos."));
// ---- Main option box --------
d->optionGroupBox = new QGroupBox;
QGridLayout* const optionLayout = new QGridLayout;
d->detectButton = new QRadioButton(i18nc("@option:radio", "Detect faces"));
d->detectButton->setToolTip(i18nc("@info", "Find all faces in your photos"));
d->detectAndRecognizeButton = new QRadioButton(i18nc("@option:radio", "Detect and recognize faces (experimental)"));
d->detectAndRecognizeButton->setToolTip(i18nc("@info", "Find all faces in your photos and try to recognize which person is depicted"));
d->alreadyScannedBox = new QComboBox;
d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Skip images already scanned"), FaceScanSettings::Skip);
d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Scan again and merge results"), FaceScanSettings::Merge);
d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Clear unconfirmed results and rescan"), FaceScanSettings::Rescan);
d->alreadyScannedBox->setCurrentIndex(FaceScanSettings::Skip);
d->reRecognizeButton = new QRadioButton(i18nc("@option:radio", "Recognize faces (experimental)"));
d->reRecognizeButton->setToolTip(i18nc("@info", "Try again to recognize the people depicted on marked but yet unconfirmed faces."));
optionLayout->addWidget(d->alreadyScannedBox, 0, 0, 1, 2);
optionLayout->addWidget(d->detectButton, 1, 0, 1, 2);
optionLayout->addWidget(d->detectAndRecognizeButton, 2, 0, 1, 2);
optionLayout->addWidget(d->reRecognizeButton, 3, 0, 1, 2);
QStyleOptionButton buttonOption;
buttonOption.initFrom(d->detectAndRecognizeButton);
int indent = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &buttonOption, d->detectAndRecognizeButton).width();
optionLayout->setColumnMinimumWidth(0, indent);
d->optionGroupBox->setLayout(optionLayout);
// ------------------------
mainLayout->addWidget(personIcon, 0, 0);
mainLayout->addWidget(introduction, 0, 1);
mainLayout->addWidget(d->optionGroupBox, 1, 0, 1, -1);
mainLayout->setColumnStretch(1, 1);
mainLayout->setRowStretch(2, 1);
mainWidget->setLayout(mainLayout);
// ---- Album tab ---------
d->albumSelectors = new AlbumSelectors(i18nc("@label", "Search in:"), d->configName, d->tabWidget);
d->tabWidget->addTab(d->albumSelectors, i18nc("@title:tab", "Albums"));
// ---- Parameters tab ----
QWidget* const parametersTab = new QWidget(d->tabWidget);
QGridLayout* const parametersLayout = new QGridLayout(parametersTab);
QLabel* const detectionLabel = new QLabel(i18nc("@label", "Parameters for face detection and Recognition"), parametersTab);
QLabel* const accuracyLabel = new QLabel(i18nc("@label Two extremities of a scale", "Fast - Accurate"), parametersTab);
accuracyLabel->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
d->accuracyInput = new DIntNumInput(parametersTab);
d->accuracyInput->setDefaultValue(80);
d->accuracyInput->setRange(0, 100, 10);
d->accuracyInput->setToolTip(i18nc("@info:tooltip",
"Adjust speed versus accuracy: The higher the value, the more accurate the results "
"will be, but it will take more time."));
parametersLayout->addWidget(detectionLabel, 0, 0, 1, 1);
parametersLayout->addWidget(d->accuracyInput, 1, 0, 1, 1);
parametersLayout->addWidget(accuracyLabel, 2, 0, 1, 1);
parametersLayout->setColumnStretch(0, 10);
parametersLayout->setRowStretch(3, 10);
d->tabWidget->addTab(parametersTab, i18nc("@title:tab", "Parameters"));
// ---- Advanced tab ------
QWidget* const advancedTab = new QWidget(d->tabWidget);
QGridLayout* const advancedLayout = new QGridLayout(advancedTab);
QLabel* const cpuExplanation = new QLabel(advancedTab);
cpuExplanation->setText(i18nc("@info",
"Face detection is a time-consuming task. "
"You can choose if you wish to employ all processor cores "
"on your system, or work in the background only on one core. "
"Warning: this features still experimental and it is disabled by default."));
cpuExplanation->setWordWrap(true);
d->useFullCpuButton = new QCheckBox(advancedTab);
d->useFullCpuButton->setText(i18nc("@option:check", "Work on all processor cores (experimental)"));
// ---- Recognize algorithm ComboBox -----
d->recognizeBox = new QComboBox;
d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using LBP algorithm"), RecognitionDatabase::RecognizeAlgorithm::LBP);
d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using EigenFaces algorithm"), RecognitionDatabase::RecognizeAlgorithm::EigenFace);
d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using FisherFaces algorithm"), RecognitionDatabase::RecognizeAlgorithm::FisherFace);
d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using Deep Learning algorithm"), RecognitionDatabase::RecognizeAlgorithm::DNN);
d->recognizeBox->setCurrentIndex(RecognitionDatabase::RecognizeAlgorithm::LBP);
d->retrainAllButton = new QCheckBox(advancedTab);
d->retrainAllButton->setText(i18nc("@option:check", "Clear and rebuild all training data"));
d->retrainAllButton->setToolTip(i18nc("@info:tooltip",
"This will clear all training data for recognition "
"and rebuild it from all available faces."));
advancedLayout->addWidget(cpuExplanation, 0, 0);
advancedLayout->addWidget(d->useFullCpuButton, 1, 0);
advancedLayout->addWidget(new DLineWidget(Qt::Horizontal), 2, 0);
advancedLayout->addWidget(d->retrainAllButton, 3, 0);
advancedLayout->addWidget(d->recognizeBox, 4, 0);
advancedLayout->setRowStretch(5, 10);
d->tabWidget->addTab(advancedTab, i18nc("@title:tab", "Advanced"));
// ------------------------
QVBoxLayout* const vbx = new QVBoxLayout(this);
vbx->addWidget(mainWidget);
vbx->addWidget(d->tabWidget);
vbx->addWidget(d->buttons);
setLayout(vbx);
}
void FaceScanDialog::setupConnections()
{
connect(d->detectButton, SIGNAL(toggled(bool)),
d->alreadyScannedBox, SLOT(setEnabled(bool)));
connect(d->detectAndRecognizeButton, SIGNAL(toggled(bool)),
d->alreadyScannedBox, SLOT(setEnabled(bool)));
connect(d->retrainAllButton, SIGNAL(toggled(bool)),
this, SLOT(retrainAllButtonToggled(bool)));
connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
this, SLOT(slotOk()));
connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
this, SLOT(reject()));
connect(d->buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked()),
this, SLOT(slotDetails()));
}
void FaceScanDialog::slotDetails()
{
bool on = !d->tabWidget->isVisible();
d->tabWidget->setVisible(on);
adjustSize();
adjustDetailsButton(on);
}
void FaceScanDialog::adjustDetailsButton(bool on)
{
d->buttons->button(QDialogButtonBox::Reset)->setText(on ? i18nc("@action:button", "Options <<")
: i18nc("@action:button", "Options >>"));
}
void FaceScanDialog::slotOk()
{
accept();
saveState();
}
void FaceScanDialog::retrainAllButtonToggled(bool on)
{
d->optionGroupBox->setEnabled(!on);
d->albumSelectors->setEnabled(!on);
d->recognizeBox->setEnabled(!on);
}
FaceScanSettings FaceScanDialog::settings() const
{
FaceScanSettings settings;
if (d->retrainAllButton->isChecked())
{
settings.task = FaceScanSettings::RetrainAll;
}
else if(d->detectButton->isChecked())
{
settings.task = FaceScanSettings::Detect;
}
else
{
if (d->detectAndRecognizeButton->isChecked())
{
settings.task = FaceScanSettings::DetectAndRecognize;
}
else
{
settings.task = FaceScanSettings::RecognizeMarkedFaces;
}
}
settings.alreadyScannedHandling = (FaceScanSettings::AlreadyScannedHandling)
d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt();
settings.accuracy = double(d->accuracyInput->value()) / 100;
settings.albums << d->albumSelectors->selectedAlbumsAndTags();
settings.useFullCpu = d->useFullCpuButton->isChecked();
settings.recognizeAlgorithm = (RecognitionDatabase::RecognizeAlgorithm)
d->recognizeBox->itemData(d->recognizeBox->currentIndex()).toInt();
return settings;
}
} // namespace Digikam
diff --git a/core/utilities/geolocation/editor/kmlexport/kmlwidget.cpp b/core/utilities/geolocation/editor/kmlexport/kmlwidget.cpp
index f9aa0c0276..1874ea949c 100644
--- a/core/utilities/geolocation/editor/kmlexport/kmlwidget.cpp
+++ b/core/utilities/geolocation/editor/kmlexport/kmlwidget.cpp
@@ -1,467 +1,467 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-05-16
* Description : a tool to export GPS data to KML file.
*
* Copyright (C) 2006-2007 by Stephane Pontier
* Copyright (C) 2008-2018 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 "kmlwidget.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include