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 #include #include // KDE includes #include #include #include namespace Digikam { KmlWidget::KmlWidget(GeolocationEdit* const dlg, GPSImageModel* const imageModel, DInfoInterface* const iface) : QWidget(dlg), m_model(imageModel), m_dlg(dlg), m_kmlExport(iface) { KMLExportConfigLayout = new QGridLayout(this); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); // -------------------------------------------------------------- // Target preferences TargetPreferenceGroupBox = new QGroupBox(i18n("Target Preferences"), this); TargetPreferenceGroupBoxLayout = new QGridLayout(TargetPreferenceGroupBox); // target type TargetTypeGroupBox = new QGroupBox(i18n("Target Type"), this); buttonGroupTargetTypeLayout = new QGridLayout(TargetTypeGroupBox); buttonGroupTargetType = new QButtonGroup(TargetTypeGroupBox); LocalTargetRadioButton_ = new QRadioButton(i18n("&Local or web target used by GoogleEarth"), TargetTypeGroupBox); LocalTargetRadioButton_->setChecked(true); GoogleMapTargetRadioButton_ = new QRadioButton(i18n("Web target used by GoogleMaps"), TargetTypeGroupBox); GoogleMapTargetRadioButton_->setToolTip(i18n("When using GoogleMaps, all images must have complete URLs, icons are " "squared, and when drawing a track, only line track is exported.")); buttonGroupTargetTypeLayout->addWidget(LocalTargetRadioButton_, 0, 0, 1, 1); buttonGroupTargetTypeLayout->addWidget(GoogleMapTargetRadioButton_, 1, 0, 1, 1); buttonGroupTargetTypeLayout->setContentsMargins(spacing, spacing, spacing, spacing); buttonGroupTargetTypeLayout->setAlignment(Qt::AlignTop); // -------------------------------------------------------------- // target preference, suite QLabel* const AltitudeLabel_ = new QLabel(i18n("Picture Altitude:"), TargetPreferenceGroupBox); AltitudeCB_ = new QComboBox(TargetPreferenceGroupBox); AltitudeCB_->addItem(i18n("clamp to ground")); AltitudeCB_->addItem(i18n("relative to ground")); AltitudeCB_->addItem(i18n("absolute")); AltitudeCB_->setWhatsThis(i18n("

Specifies how pictures are displayed" "

clamp to ground (default)
" "
Indicates to ignore an altitude specification
" "
relative to ground
" "
Sets the altitude of the element relative to the actual ground " "elevation of a particular location.
" "
absolute
" "
Sets the altitude of the coordinate relative to sea level, regardless " - "of the actual elevation of the terrain beneath the element.
")); + "of the actual elevation of the terrain beneath the element.

")); destinationDirectoryLabel_ = new QLabel(i18n("Destination Directory:"), TargetPreferenceGroupBox); DestinationDirectory_= new DFileSelector(TargetPreferenceGroupBox); DestinationDirectory_->setFileDlgMode(QFileDialog::Directory); DestinationDirectory_->setFileDlgTitle(i18n("Select a directory in which to save the kml file and pictures")); DestinationUrlLabel_ = new QLabel(i18n("Destination Path:"), TargetPreferenceGroupBox); DestinationUrl_ = new QLineEdit(TargetPreferenceGroupBox); FileNameLabel_ = new QLabel(i18n("Filename:"), TargetPreferenceGroupBox); FileName_ = new QLineEdit(TargetPreferenceGroupBox); TargetPreferenceGroupBoxLayout->addWidget(TargetTypeGroupBox, 0, 0, 2, 5); TargetPreferenceGroupBoxLayout->addWidget(AltitudeLabel_, 2, 0, 1, 1); TargetPreferenceGroupBoxLayout->addWidget(AltitudeCB_, 2, 1, 1, 4); TargetPreferenceGroupBoxLayout->addWidget(destinationDirectoryLabel_, 3, 0, 1, 1); TargetPreferenceGroupBoxLayout->addWidget(DestinationDirectory_, 3, 1, 1, 4); TargetPreferenceGroupBoxLayout->addWidget(DestinationUrlLabel_, 4, 0, 1, 1); TargetPreferenceGroupBoxLayout->addWidget(DestinationUrl_, 4, 1, 1, 4); TargetPreferenceGroupBoxLayout->addWidget(FileNameLabel_, 5, 0, 1, 1); TargetPreferenceGroupBoxLayout->addWidget(FileName_, 5, 1, 1, 4); TargetPreferenceGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); TargetPreferenceGroupBoxLayout->setAlignment(Qt::AlignTop); // -------------------------------------------------------------- // Sizes QGroupBox* const SizeGroupBox = new QGroupBox(i18n("Sizes"), this); SizeGroupBoxLayout = new QGridLayout(SizeGroupBox); IconSizeLabel = new QLabel(i18n("Icon Size:"), SizeGroupBox); IconSizeInput_ = new QSpinBox(SizeGroupBox); IconSizeInput_->setRange(1, 100); IconSizeInput_->setValue(33); ImageSizeLabel = new QLabel(i18n("Image Size:"), SizeGroupBox); ImageSizeInput_ = new QSpinBox(SizeGroupBox); ImageSizeInput_->setRange(1, 10000); ImageSizeInput_->setValue(320); SizeGroupBoxLayout->addWidget(IconSizeLabel, 0, 0, 1, 1); SizeGroupBoxLayout->addWidget(IconSizeInput_, 0, 1, 1, 1); SizeGroupBoxLayout->addWidget(ImageSizeLabel, 0, 2, 1, 1); SizeGroupBoxLayout->addWidget(ImageSizeInput_, 0, 3, 1, 1); SizeGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); SizeGroupBoxLayout->setAlignment(Qt::AlignTop); // -------------------------------------------------------------- // GPX Tracks QGroupBox* const GPXTracksGroupBox = new QGroupBox(i18n("GPX Tracks"), this); QGridLayout* const GPXTracksGroupBoxLayout = new QGridLayout(GPXTracksGroupBox); // add a gpx track checkbox GPXTracksCheckBox_ = new QCheckBox(i18n("Draw GPX Track"), GPXTracksGroupBox); // file selector GPXFileLabel_ = new QLabel(i18n("GPX file:"), GPXTracksGroupBox); GPXFileUrlRequester_ = new DFileSelector(GPXTracksGroupBox); GPXFileUrlRequester_->setFileDlgFilter(i18n("GPS Exchange Format (*.gpx)")); GPXFileUrlRequester_->setFileDlgTitle(i18n("Select GPX File to Load")); GPXFileUrlRequester_->setFileDlgMode(QFileDialog::ExistingFile); timeZoneLabel_ = new QLabel(i18n("Time Zone:"), GPXTracksGroupBox); timeZoneCB = new QComboBox(GPXTracksGroupBox); timeZoneCB->addItem(i18n("GMT-12:00"), 0); timeZoneCB->addItem(i18n("GMT-11:00"), 1); timeZoneCB->addItem(i18n("GMT-10:00"), 2); timeZoneCB->addItem(i18n("GMT-09:00"), 3); timeZoneCB->addItem(i18n("GMT-08:00"), 4); timeZoneCB->addItem(i18n("GMT-07:00"), 5); timeZoneCB->addItem(i18n("GMT-06:00"), 6); timeZoneCB->addItem(i18n("GMT-05:00"), 7); timeZoneCB->addItem(i18n("GMT-04:00"), 8); timeZoneCB->addItem(i18n("GMT-03:00"), 9); timeZoneCB->addItem(i18n("GMT-02:00"), 10); timeZoneCB->addItem(i18n("GMT-01:00"), 11); timeZoneCB->addItem(i18n("GMT"), 12); timeZoneCB->addItem(i18n("GMT+01:00"), 13); timeZoneCB->addItem(i18n("GMT+02:00"), 14); timeZoneCB->addItem(i18n("GMT+03:00"), 15); timeZoneCB->addItem(i18n("GMT+04:00"), 16); timeZoneCB->addItem(i18n("GMT+05:00"), 17); timeZoneCB->addItem(i18n("GMT+06:00"), 18); timeZoneCB->addItem(i18n("GMT+07:00"), 19); timeZoneCB->addItem(i18n("GMT+08:00"), 20); timeZoneCB->addItem(i18n("GMT+09:00"), 21); timeZoneCB->addItem(i18n("GMT+10:00"), 22); timeZoneCB->addItem(i18n("GMT+11:00"), 23); timeZoneCB->addItem(i18n("GMT+12:00"), 24); timeZoneCB->addItem(i18n("GMT+13:00"), 25); timeZoneCB->addItem(i18n("GMT+14:00"), 26); timeZoneCB->setWhatsThis(i18n("Sets the time zone of the camera during " "picture shooting, so that the time stamps of the GPS " "can be converted to match the local time")); GPXLineWidthLabel_ = new QLabel(i18n("Track Width:"), GPXTracksGroupBox); GPXLineWidthInput_ = new QSpinBox(GPXTracksGroupBox); GPXLineWidthInput_->setValue(4); GPXColorLabel_ = new QLabel(i18n("Track Color:"), GPXTracksGroupBox); GPXTrackColor_ = new DColorSelector(GPXTracksGroupBox); GPXTrackColor_->setColor(QColor("#ffffff")); GPXTracksOpacityLabel_ = new QLabel(i18n("Opacity (%):"), GPXTracksGroupBox); GPXTracksOpacityInput_ = new QSpinBox(GPXTracksGroupBox); GPXTracksOpacityInput_->setRange(0, 100); GPXTracksOpacityInput_->setSingleStep(1); GPXTracksOpacityInput_->setValue(100); GPXAltitudeLabel_ = new QLabel(i18n("Track Altitude:"), GPXTracksGroupBox); GPXAltitudeCB_ = new QComboBox(GPXTracksGroupBox); GPXAltitudeCB_->addItem(i18n("clamp to ground")); GPXAltitudeCB_->addItem(i18n("relative to ground")); GPXAltitudeCB_->addItem(i18n("absolute")); GPXAltitudeCB_->setWhatsThis(i18n("

Specifies how the points are displayed" "

clamp to ground (default)
" "
Indicates to ignore an altitude specification
" "
relative to ground
" "
Sets the altitude of the element relative to the actual ground " "elevation of a particular location.
" "
absolute
" "
Sets the altitude of the coordinate relative to sea level, " "regardless of the actual elevation of the terrain beneath " "the element.
")); GPXTracksGroupBoxLayout->addWidget(GPXTracksCheckBox_, 0, 0, 1, 4); GPXTracksGroupBoxLayout->addWidget(GPXFileLabel_, 1, 0, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXFileUrlRequester_, 1, 1, 1, 3); GPXTracksGroupBoxLayout->addWidget(timeZoneLabel_, 2, 0, 1, 1); GPXTracksGroupBoxLayout->addWidget(timeZoneCB, 2, 1, 1, 3); GPXTracksGroupBoxLayout->addWidget(GPXLineWidthLabel_, 3, 0, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXLineWidthInput_, 3, 1, 1, 3); GPXTracksGroupBoxLayout->addWidget(GPXColorLabel_, 4, 0, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXTrackColor_, 4, 1, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXTracksOpacityLabel_, 4, 2, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXTracksOpacityInput_, 4, 3, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXAltitudeLabel_, 5, 0, 1, 1); GPXTracksGroupBoxLayout->addWidget(GPXAltitudeCB_, 5, 1, 1, 3); GPXTracksGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); GPXTracksGroupBoxLayout->setAlignment(Qt::AlignTop); // -------------------------------------------------------------- m_geneBtn = new QPushButton(i18n("Generate KML file"), this); KMLExportConfigLayout->addWidget(TargetPreferenceGroupBox, 0, 0); KMLExportConfigLayout->addWidget(SizeGroupBox, 1, 0); KMLExportConfigLayout->addWidget(GPXTracksGroupBox, 2, 0); KMLExportConfigLayout->addWidget(m_geneBtn, 3, 0); KMLExportConfigLayout->setContentsMargins(spacing, spacing, spacing, spacing); // -------------------------------------------------------------- connect(m_geneBtn, SIGNAL(clicked()), this, SLOT(slotKMLGenerate())); connect(GoogleMapTargetRadioButton_, SIGNAL(toggled(bool)), this, SLOT(slotGoogleMapTargetRadioButtonToggled(bool))); connect(GPXTracksCheckBox_, SIGNAL(toggled(bool)), this, SLOT(slotKMLTracksCheckButtonToggled(bool))); connect(this, SIGNAL(signalSetUIEnabled(bool)), m_dlg, SLOT(slotSetUIEnabled(bool))); connect(this, SIGNAL(signalProgressSetup(int,QString)), m_dlg, SLOT(slotProgressSetup(int,QString))); connect(&m_kmlExport, SIGNAL(signalProgressChanged(int)), m_dlg, SLOT(slotProgressChanged(int))); // -------------------------------------------------------------- readSettings(); slotGoogleMapTargetRadioButtonToggled(true); slotKMLTracksCheckButtonToggled(false); } KmlWidget::~KmlWidget() { saveSettings(); } void KmlWidget::slotKMLGenerate() { emit(signalSetUIEnabled(false)); m_geneBtn->setEnabled(false); emit(signalProgressSetup(m_model->rowCount(), i18n("Generate KML file"))); saveSettings(); QList urls; for (int i = 0; i < m_model->rowCount(); ++i) { GPSImageItem* const item = m_model->itemFromIndex(m_model->index(i, 0)); if (item) { urls << item->url(); } } m_kmlExport.setUrls(urls); m_kmlExport.generate(); m_geneBtn->setEnabled(true); emit(signalSetUIEnabled(true)); } void KmlWidget::slotGoogleMapTargetRadioButtonToggled(bool) { if (GoogleMapTargetRadioButton_->isChecked()) { DestinationUrlLabel_->setEnabled(true); DestinationUrl_->setEnabled(true); IconSizeLabel->setEnabled(false); IconSizeInput_->setEnabled(false); } else { DestinationUrlLabel_->setEnabled(false); DestinationUrl_->setEnabled(false); IconSizeLabel->setEnabled(true); IconSizeInput_->setEnabled(true); } } void KmlWidget::slotKMLTracksCheckButtonToggled(bool) { if (GPXTracksCheckBox_->isChecked()) { GPXFileUrlRequester_->setEnabled(true); GPXFileLabel_->setEnabled(true); timeZoneCB->setEnabled(true); GPXColorLabel_->setEnabled(true); GPXAltitudeLabel_->setEnabled(true); timeZoneLabel_->setEnabled(true); GPXAltitudeCB_->setEnabled(true); GPXTrackColor_->setEnabled(true); GPXLineWidthLabel_->setEnabled(true); GPXLineWidthInput_->setEnabled(true); GPXTracksOpacityInput_->setEnabled(true); } else { GPXFileUrlRequester_->setEnabled(false); GPXFileLabel_->setEnabled(false); timeZoneCB->setEnabled(false); GPXColorLabel_->setEnabled(false); GPXAltitudeLabel_->setEnabled(false); timeZoneLabel_->setEnabled(false); GPXAltitudeCB_->setEnabled(false); GPXTrackColor_->setEnabled(false); GPXLineWidthLabel_->setEnabled(false); GPXLineWidthInput_->setEnabled(false); GPXTracksOpacityInput_->setEnabled(false); } } void KmlWidget::readSettings() { bool localTarget; bool optimize_googlemap; int iconSize; // int googlemapSize; int size; QString UrlDestDir; QString baseDestDir; QString KMLFileName; int AltitudeMode; bool GPXtracks; QString GPXFile; int TimeZone; int LineWidth; QString GPXColor; int GPXOpacity; int GPXAltitudeMode; KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("KMLExport Settings")); localTarget = group.readEntry(QLatin1String("localTarget"), true); optimize_googlemap = group.readEntry(QLatin1String("optimize_googlemap"), false); iconSize = group.readEntry(QLatin1String("iconSize"), 33); // not saving this size as it should not change // googlemapSize = group.readNumEntry("googlemapSize", 32); size = group.readEntry(QLatin1String("size"), 320); // UrlDestDir have to have the trailing / baseDestDir = group.readEntry(QLatin1String("baseDestDir"), QString::fromUtf8("/tmp/")); UrlDestDir = group.readEntry(QLatin1String("UrlDestDir"), QString::fromUtf8("http://www.example.com/")); KMLFileName = group.readEntry(QLatin1String("KMLFileName"), QString::fromUtf8("kmldocument")); AltitudeMode = group.readEntry(QLatin1String("Altitude Mode"), 0); GPXtracks = group.readEntry(QLatin1String("UseGPXTracks"), false); GPXFile = group.readEntry(QLatin1String("GPXFile"), QString()); TimeZone = group.readEntry(QLatin1String("Time Zone"), 12); LineWidth = group.readEntry(QLatin1String("Line Width"), 4); GPXColor = group.readEntry(QLatin1String("Track Color"), QString::fromUtf8("#17eeee")); GPXOpacity = group.readEntry(QLatin1String("Track Opacity"), 64); GPXAltitudeMode = group.readEntry(QLatin1String("GPX Altitude Mode"), 0); // -- Apply Settings to widgets ------------------------------ LocalTargetRadioButton_->setChecked(localTarget); GoogleMapTargetRadioButton_->setChecked(optimize_googlemap); IconSizeInput_->setValue(iconSize); ImageSizeInput_->setValue(size); AltitudeCB_->setCurrentIndex(AltitudeMode); DestinationDirectory_->lineEdit()->setText(baseDestDir); DestinationUrl_->setText(UrlDestDir); FileName_->setText(KMLFileName); GPXTracksCheckBox_->setChecked(GPXtracks); timeZoneCB->setCurrentIndex(TimeZone); GPXLineWidthInput_->setValue(LineWidth); GPXTrackColor_->setColor(GPXColor); GPXTracksOpacityInput_->setValue(GPXOpacity); GPXAltitudeCB_->setCurrentIndex(GPXAltitudeMode); } void KmlWidget::saveSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("KMLExport Settings")); group.writeEntry(QLatin1String("localTarget"), LocalTargetRadioButton_->isChecked()); group.writeEntry(QLatin1String("optimize_googlemap"), GoogleMapTargetRadioButton_->isChecked()); group.writeEntry(QLatin1String("iconSize"), IconSizeInput_->value()); group.writeEntry(QLatin1String("size"), ImageSizeInput_->value()); QString destination = DestinationDirectory_->lineEdit()->text(); if (!destination.endsWith(QLatin1Char('/'))) { destination.append(QLatin1Char('/')); } group.writeEntry(QLatin1String("baseDestDir"), destination); QString url = DestinationUrl_->text(); if (!url.endsWith(QLatin1Char('/'))) { url.append(QLatin1Char('/')); } group.writeEntry(QLatin1String("UrlDestDir"), url); group.writeEntry(QLatin1String("KMLFileName"), FileName_->text()); group.writeEntry(QLatin1String("Altitude Mode"), AltitudeCB_->currentIndex()); group.writeEntry(QLatin1String("UseGPXTracks"), GPXTracksCheckBox_->isChecked()); group.writeEntry(QLatin1String("GPXFile"), GPXFileUrlRequester_->lineEdit()->text()); group.writeEntry(QLatin1String("Time Zone"), timeZoneCB->currentIndex()); group.writeEntry(QLatin1String("Line Width"), GPXLineWidthInput_->value()); group.writeEntry(QLatin1String("Track Color"), GPXTrackColor_->color().name()); group.writeEntry(QLatin1String("Track Opacity"), GPXTracksOpacityInput_->value()); group.writeEntry(QLatin1String("GPX Altitude Mode"), GPXAltitudeCB_->currentIndex()); config->sync(); } } // namespace Digikam diff --git a/core/utilities/imageeditor/dialogs/softproofdialog.cpp b/core/utilities/imageeditor/dialogs/softproofdialog.cpp index 2a6f078ff1..ee9f0c55c0 100644 --- a/core/utilities/imageeditor/dialogs/softproofdialog.cpp +++ b/core/utilities/imageeditor/dialogs/softproofdialog.cpp @@ -1,249 +1,249 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-09-16 * Description : Dialog to adjust soft proofing settings * * Copyright (C) 2009-2012 by Marcel Wiesweg * Copyright (C) 2013-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 "softproofdialog.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" #include "iccprofilescombobox.h" #include "iccsettingscontainer.h" #include "iccsettings.h" #include "iccprofileinfodlg.h" #include "dexpanderbox.h" #include "dcolorselector.h" namespace Digikam { class Q_DECL_HIDDEN SoftProofDialog::Private { public: explicit Private() : switchOn(false), deviceProfileBox(0), infoProofProfiles(0), buttons(0), gamutCheckBox(0), maskColorLabel(0), maskColorBtn(0), proofingIntentBox(0) { } bool switchOn; IccProfilesComboBox* deviceProfileBox; QPushButton* infoProofProfiles; QDialogButtonBox* buttons; QCheckBox* gamutCheckBox; QLabel* maskColorLabel; DColorSelector* maskColorBtn; IccRenderingIntentComboBox* proofingIntentBox; }; SoftProofDialog::SoftProofDialog(QWidget* const parent) : QDialog(parent), d(new Private) { setModal(true); setWindowTitle(i18n("Soft Proofing Options")); d->buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); d->buttons->button(QDialogButtonBox::Ok)->setText(i18n("Soft Proofing On")); d->buttons->button(QDialogButtonBox::Ok)->setToolTip(i18n("Enable soft-proofing color managed view")); d->buttons->button(QDialogButtonBox::Cancel)->setText(i18n("Soft Proofing Off")); d->buttons->button(QDialogButtonBox::Cancel)->setToolTip(i18n("Disable soft-proofing color managed view")); QWidget* const page = new QWidget(this); QVBoxLayout* const mainLayout = new QVBoxLayout(page); // --- QLabel* const headerLabel = new QLabel(i18n("Configure the Soft Proofing View")); DLineWidget* const separator = new DLineWidget(Qt::Horizontal); // ------------------------------------------------------------- QGridLayout* const profileGrid = new QGridLayout; QLabel* const proofIcon = new QLabel; proofIcon->setPixmap(QIcon::fromTheme(QLatin1String("printer")).pixmap(22)); QLabel* const proofLabel = new QLabel(i18n("Profile of the output device to simulate:")); d->deviceProfileBox = new IccProfilesComboBox; proofLabel->setBuddy(d->deviceProfileBox); d->deviceProfileBox->setWhatsThis(i18n("

Select the profile for your output device " "(usually, your printer). This profile will be used to do a soft proof, so you will " "be able to preview how an image will be rendered via an output device.

")); d->infoProofProfiles = new QPushButton; d->infoProofProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); - d->infoProofProfiles->setWhatsThis(i18n("Press this button to get detailed " + d->infoProofProfiles->setWhatsThis(i18n("

Press this button to get detailed " "information about the selected proofing profile.

")); d->deviceProfileBox->replaceProfilesSqueezed(IccSettings::instance()->outputProfiles()); profileGrid->addWidget(proofIcon, 0, 0); profileGrid->addWidget(proofLabel, 0, 1, 1, 2); profileGrid->addWidget(d->deviceProfileBox, 1, 0, 1, 2); profileGrid->addWidget(d->infoProofProfiles, 1, 2); profileGrid->setColumnStretch(1, 10); // -------------------------------------------------------------- QGroupBox* const optionsBox = new QGroupBox; QGridLayout* const optionsGrid = new QGridLayout; QLabel* const intentLabel = new QLabel(i18n("Rendering intent:")); d->proofingIntentBox = new IccRenderingIntentComboBox; //TODO d->proofingIntentBox->setWhatsThis(i18n("")); intentLabel->setBuddy(d->proofingIntentBox); d->gamutCheckBox = new QCheckBox(i18n("Highlight out-of-gamut colors")); d->maskColorLabel = new QLabel(i18n("Highlighting color:")); d->maskColorBtn = new DColorSelector; d->maskColorLabel->setBuddy(d->maskColorBtn); optionsGrid->addWidget(intentLabel, 0, 0, 1, 2); optionsGrid->addWidget(d->proofingIntentBox, 0, 2, 1, 2); optionsGrid->addWidget(d->gamutCheckBox, 1, 0, 1, 4); optionsGrid->addWidget(d->maskColorLabel, 2, 1, 1, 1); optionsGrid->addWidget(d->maskColorBtn, 2, 2, 1, 2, Qt::AlignLeft); optionsGrid->setColumnMinimumWidth(0, 10); optionsGrid->setColumnStretch(2, 1); optionsBox->setLayout(optionsGrid); // ------------------------------------------------------- mainLayout->addWidget(headerLabel); mainLayout->addWidget(separator); mainLayout->addLayout(profileGrid); mainLayout->addWidget(optionsBox); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(page); vbx->addWidget(d->buttons); setLayout(vbx); connect(d->deviceProfileBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOkButtonState())); connect(d->gamutCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateGamutCheckState())); connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotOk())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); connect(d->infoProofProfiles, SIGNAL(clicked()), this, SLOT(slotProfileInfo())); readSettings(); updateOkButtonState(); updateGamutCheckState(); } SoftProofDialog::~SoftProofDialog() { delete d; } bool SoftProofDialog::shallEnableSoftProofView() const { return d->switchOn; } void SoftProofDialog::updateGamutCheckState() { bool on = d->gamutCheckBox->isChecked(); d->maskColorLabel->setEnabled(on); d->maskColorBtn->setEnabled(on); } void SoftProofDialog::updateOkButtonState() { d->buttons->button(QDialogButtonBox::Ok)->setEnabled(d->deviceProfileBox->currentIndex() != -1); } void SoftProofDialog::readSettings() { ICCSettingsContainer settings = IccSettings::instance()->settings(); d->deviceProfileBox->setCurrentProfile(IccProfile(settings.defaultProofProfile)); d->proofingIntentBox->setIntent(settings.proofingRenderingIntent); d->gamutCheckBox->setChecked(settings.doGamutCheck); d->maskColorBtn->setColor(settings.gamutCheckMaskColor); } void SoftProofDialog::writeSettings() { ICCSettingsContainer settings = IccSettings::instance()->settings(); settings.defaultProofProfile = d->deviceProfileBox->currentProfile().filePath(); settings.proofingRenderingIntent = d->proofingIntentBox->intent(); settings.doGamutCheck = d->gamutCheckBox->isChecked(); settings.gamutCheckMaskColor = d->maskColorBtn->color(); IccSettings::instance()->setSettings(settings); } void SoftProofDialog::slotProfileInfo() { IccProfile profile = d->deviceProfileBox->currentProfile(); if (profile.isNull()) { QMessageBox::critical(this, i18n("Profile Error"), i18n("No profile is selected.")); return; } ICCProfileInfoDlg infoDlg(this, profile.filePath(), profile); infoDlg.exec(); } void SoftProofDialog::slotOk() { d->switchOn = true; writeSettings(); accept(); } } // namespace Digikam diff --git a/core/utilities/imageeditor/dialogs/versioningpromptusersavedlg.cpp b/core/utilities/imageeditor/dialogs/versioningpromptusersavedlg.cpp index 8d0f41ed2c..2275329c8e 100644 --- a/core/utilities/imageeditor/dialogs/versioningpromptusersavedlg.cpp +++ b/core/utilities/imageeditor/dialogs/versioningpromptusersavedlg.cpp @@ -1,156 +1,156 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-09-16 * Description : Dialog to prompt users about versioning * * Copyright (C) 2010-2012 by Marcel Wiesweg * Copyright (C) 2013-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 "versioningpromptusersavedlg.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include namespace Digikam { class Q_DECL_HIDDEN VersioningPromptUserSaveDialog::Private { public: explicit Private() : clicked(QDialogButtonBox::NoButton), buttons(0) { } public: QDialogButtonBox::StandardButton clicked; QDialogButtonBox* buttons; }; VersioningPromptUserSaveDialog::VersioningPromptUserSaveDialog(QWidget* const parent) : QDialog(parent), d(new Private) { setWindowTitle(i18nc("@title:window", "Save?")); d->buttons = new QDialogButtonBox(QDialogButtonBox::Ok | // Save Changes QDialogButtonBox::Apply | // Save Changes as a New Version QDialogButtonBox::Discard | // Discard Changes QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Cancel)->setDefault(true); d->buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Save Changes")); d->buttons->button(QDialogButtonBox::Ok)->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply"))); d->buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info:tooltip", "Save the current changes. Note: The original image will never be overwritten.")); d->buttons->button(QDialogButtonBox::Apply)->setText(i18nc("@action:button", "Save Changes as a New Version")); d->buttons->button(QDialogButtonBox::Apply)->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); d->buttons->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip", "Save the current changes as a new version. " "The loaded file will remain unchanged, a new file will be created.")); d->buttons->button(QDialogButtonBox::Discard)->setText(i18nc("@action:button", "Discard Changes")); d->buttons->button(QDialogButtonBox::Discard)->setIcon(QIcon::fromTheme(QLatin1String("task-reject"))); d->buttons->button(QDialogButtonBox::Discard)->setToolTip(i18nc("@info:tooltip", "Discard the changes applied to the image during this editing session.")); connect(d->buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonClicked(QAbstractButton*))); QWidget* const mainWidget = new QWidget; // -- Icon and Header -- QLabel* const warningIcon = new QLabel; warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this))); QLabel* const question = new QLabel; question->setTextFormat(Qt::RichText); question->setText(i18nc("@label", - "The current image has been changed.
" + "The current image has been changed.
" "Do you wish to save your changes?
")); QLabel* const editIcon = new QLabel; editIcon->setPixmap(QIcon::fromTheme(QLatin1String("document-edit")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this))); QHBoxLayout* const headerLayout = new QHBoxLayout; headerLayout->addWidget(warningIcon); headerLayout->addWidget(question, 10, Qt::AlignCenter); headerLayout->addWidget(editIcon); mainWidget->setLayout(headerLayout); // -- Layouts -- QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(mainWidget); vbx->addWidget(d->buttons); setLayout(vbx); } VersioningPromptUserSaveDialog::~VersioningPromptUserSaveDialog() { delete d; } void VersioningPromptUserSaveDialog::slotButtonClicked(QAbstractButton* button) { d->clicked = d->buttons->standardButton(button); if (d->clicked == QDialogButtonBox::Cancel) { reject(); return; } accept(); } bool VersioningPromptUserSaveDialog::shallSave() const { return (d->clicked == QDialogButtonBox::Ok); } bool VersioningPromptUserSaveDialog::newVersion() const { return (d->clicked == QDialogButtonBox::Apply); } bool VersioningPromptUserSaveDialog::shallDiscard() const { return (d->clicked == QDialogButtonBox::Discard); } } // namespace Digikam diff --git a/core/utilities/imageeditor/main/imagewindow.cpp b/core/utilities/imageeditor/main/imagewindow.cpp index 25ddfc58f8..dac475b880 100644 --- a/core/utilities/imageeditor/main/imagewindow.cpp +++ b/core/utilities/imageeditor/main/imagewindow.cpp @@ -1,1302 +1,1302 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-02-12 * Description : digiKam image editor GUI * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2004-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 "imagewindow.h" #include "imagewindow_p.h" namespace Digikam { ImageWindow* ImageWindow::m_instance = 0; ImageWindow* ImageWindow::imageWindow() { if (!m_instance) { new ImageWindow(); } return m_instance; } bool ImageWindow::imageWindowCreated() { return m_instance; } ImageWindow::ImageWindow() : EditorWindow(QLatin1String("Image Editor")), d(new Private) { setXMLFile(QLatin1String("imageeditorui5.rc")); m_instance = this; // We don't want to be deleted on close setAttribute(Qt::WA_DeleteOnClose, false); setAcceptDrops(true); // -- Build the GUI ------------------------------- setupUserArea(); setupActions(); setupStatusBar(); createGUI(xmlFile()); cleanupActions(); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 // Create tool selection view setupSelectToolsAction(); // Create context menu. setupContextMenu(); // Make signals/slots connections setupConnections(); // -- Read settings -------------------------------- readSettings(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); applyMainWindowSettings(group); d->thumbBarDock->setShouldBeVisible(group.readEntry(d->configShowThumbbarEntry, false)); setAutoSaveSettings(configGroupName(), true); d->viewContainer->setAutoSaveSettings(QLatin1String("ImageViewer Thumbbar"), true); //------------------------------------------------------------- d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->loadState(); d->rightSideBar->populateTags(); slotSetupChanged(); } ImageWindow::~ImageWindow() { m_instance = 0; delete d->rightSideBar; delete d->thumbBar; delete d; } void ImageWindow::closeEvent(QCloseEvent* e) { if (!queryClose()) { e->ignore(); return; } // put right side bar in a defined state emit signalNoCurrentItem(); m_canvas->resetImage(); // There is one nasty habit with the thumbnail bar if it is floating: it // doesn't close when the parent window does, so it needs to be manually // closed. If the light table is opened again, its original state needs to // be restored. // This only needs to be done when closing a visible window and not when // destroying a closed window, since the latter case will always report that // the thumbnail bar isn't visible. if (isVisible()) { thumbBar()->hide(); } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->rightSideBar->setConfigGroup(KConfigGroup(&group, "Right Sidebar")); d->rightSideBar->saveState(); saveSettings(); DXmlGuiWindow::closeEvent(e); } void ImageWindow::showEvent(QShowEvent*) { // Restore the visibility of the thumbbar and start autosaving again. thumbBar()->restoreVisibility(); } bool ImageWindow::queryClose() { // Note: we re-implement closeEvent above for this window. // Additionally, queryClose is called from DigikamApp. // wait if a save operation is currently running if (!waitForSavingToComplete()) { return false; } return promptUserSave(d->currentUrl()); } void ImageWindow::loadImageInfos(const ImageInfoList& imageInfoList, const ImageInfo& imageInfoCurrent, const QString& caption) { // Very first thing is to check for changes, user may choose to cancel operation if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } d->currentImageInfo = imageInfoCurrent; // Note: Addition is asynchronous, indexes not yet available // We enable thumbbar as soon as indexes are available // If not, we load imageInfoCurrent, then the index 0, then again imageInfoCurrent d->thumbBar->setEnabled(false); d->imageInfoModel->setImageInfos(imageInfoList); d->setThumbBarToCurrent(); if (!caption.isEmpty()) { setCaption(i18n("Image Editor - %1", caption)); } else { setCaption(i18n("Image Editor")); } // it can slightly improve the responsiveness when inserting an event loop run here QTimer::singleShot(0, this, SLOT(slotLoadImageInfosStage2())); } void ImageWindow::slotLoadImageInfosStage2() { // if window is minimized, show it if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } slotLoadCurrent(); } void ImageWindow::slotThumbBarModelReady() { d->thumbBar->setEnabled(true); } void ImageWindow::openImage(const ImageInfo& info) { if (d->currentImageInfo == info) { return; } d->currentImageInfo = info; d->ensureModelContains(d->currentImageInfo); slotLoadCurrent(); } void ImageWindow::slotLoadCurrent() { if (!d->currentIsValid()) { return; } m_canvas->load(d->currentImageInfo.filePath(), m_IOFileSettings); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } d->setThumbBarToCurrent(); // Do this _after_ the canvas->load(), so that the main view histogram does not load // a smaller version if a raw image, and after that the EditorCore loads the full version. // So first let EditorCore create its loading task, only then any external objects. setViewToURL(d->currentUrl()); } void ImageWindow::setViewToURL(const QUrl& url) { emit signalURLChanged(url); } void ImageWindow::slotThumbBarImageSelected(const ImageInfo& info) { if (d->currentImageInfo == info || !d->thumbBar->isEnabled()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded, false)) { return; } d->currentImageInfo = info; slotLoadCurrent(); } void ImageWindow::slotDroppedOnThumbbar(const QList& infos) { // Check whether dropped image list is empty if (infos.isEmpty()) { return; } // Create new list and images that are not present currently in the thumbbar QList toAdd; foreach(const ImageInfo& it, infos) { QModelIndex index(d->imageFilterModel->indexForImageInfo(it)); if( !index.isValid() ) { toAdd.append(it); } } // Loading images if new images are dropped if (!toAdd.isEmpty()) { loadImageInfos(toAdd, toAdd.first(), QString()); } } void ImageWindow::slotFileOriginChanged(const QString& filePath) { // By redo or undo, we have virtually switched to a new image. // So we do _not_ load anything! ImageInfo newCurrent = ImageInfo::fromLocalFile(filePath); if (newCurrent.isNull() || !d->imageInfoModel->hasImage(newCurrent)) { return; } d->currentImageInfo = newCurrent; d->setThumbBarToCurrent(); setViewToURL(d->currentUrl()); } void ImageWindow::loadIndex(const QModelIndex& index) { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (!index.isValid()) { return; } d->currentImageInfo = d->imageFilterModel->imageInfo(index); slotLoadCurrent(); } void ImageWindow::slotForward() { loadIndex(d->nextIndex()); } void ImageWindow::slotBackward() { loadIndex(d->previousIndex()); } void ImageWindow::slotFirst() { loadIndex(d->firstIndex()); } void ImageWindow::slotLast() { loadIndex(d->lastIndex()); } void ImageWindow::slotChanged() { QString mpixels; QSize dims(m_canvas->imageWidth(), m_canvas->imageHeight()); mpixels.setNum(dims.width()*dims.height() / 1000000.0, 'f', 2); QString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)", dims.width(), dims.height(), mpixels); m_resLabel->setAdjustedText(str); if (!d->currentIsValid()) { return; } DImg* const img = m_canvas->interface()->getImg(); DImageHistory history = m_canvas->interface()->getImageHistory(); DImageHistory redoHistory = m_canvas->interface()->getImageHistoryOfFullRedo(); d->rightSideBar->itemChanged(d->currentImageInfo, m_canvas->getSelectedArea(), img, redoHistory); // Filters for redo will be turn in grey out d->rightSideBar->getFiltersHistoryTab()->setEnabledHistorySteps(history.actionCount()); /* if (!d->currentImageInfo.isNull()) { } else { d->rightSideBar->itemChanged(d->currentUrl(), m_canvas->getSelectedArea(), img); } */ } void ImageWindow::slotToggleTag(const QUrl& url, int tagID) { toggleTag(ImageInfo::fromUrl(url), tagID); } void ImageWindow::toggleTag(int tagID) { toggleTag(d->currentImageInfo, tagID); } void ImageWindow::toggleTag(const ImageInfo& info, int tagID) { if (!info.isNull()) { if (info.tagIds().contains(tagID)) { FileActionMngr::instance()->removeTag(info, tagID); } else { FileActionMngr::instance()->assignTag(info, tagID); } } } void ImageWindow::slotAssignTag(int tagID) { if (!d->currentImageInfo.isNull()) { FileActionMngr::instance()->assignTag(d->currentImageInfo, tagID); } } void ImageWindow::slotRemoveTag(int tagID) { if (!d->currentImageInfo.isNull()) { FileActionMngr::instance()->removeTag(d->currentImageInfo, tagID); } } void ImageWindow::slotAssignPickLabel(int pickId) { assignPickLabel(d->currentImageInfo, pickId); } void ImageWindow::slotAssignColorLabel(int colorId) { assignColorLabel(d->currentImageInfo, colorId); } void ImageWindow::assignPickLabel(const ImageInfo& info, int pickId) { if (!info.isNull()) { FileActionMngr::instance()->assignPickLabel(info, pickId); } } void ImageWindow::assignColorLabel(const ImageInfo& info, int colorId) { if (!info.isNull()) { FileActionMngr::instance()->assignColorLabel(info, colorId); } } void ImageWindow::slotAssignRating(int rating) { assignRating(d->currentImageInfo, rating); } void ImageWindow::assignRating(const ImageInfo& info, int rating) { rating = qMin(RatingMax, qMax(RatingMin, rating)); if (!info.isNull()) { FileActionMngr::instance()->assignRating(info, rating); } } void ImageWindow::slotRatingChanged(const QUrl& url, int rating) { assignRating(ImageInfo::fromUrl(url), rating); } void ImageWindow::slotColorLabelChanged(const QUrl& url, int color) { assignColorLabel(ImageInfo::fromUrl(url), color); } void ImageWindow::slotPickLabelChanged(const QUrl& url, int pick) { assignPickLabel(ImageInfo::fromUrl(url), pick); } void ImageWindow::slotUpdateItemInfo() { QString text = i18nc(" ( of )", "%1 (%2 of %3)", d->currentImageInfo.name(), - QString::number(d->currentIndex().row() + 1), - QString::number(d->imageFilterModel->rowCount())); + d->currentIndex().row() + 1, + d->imageFilterModel->rowCount()); m_nameLabel->setText(text); if (!m_actionEnabledState) { return; } if (d->imageInfoModel->rowCount() == 1) { m_backwardAction->setEnabled(false); m_forwardAction->setEnabled(false); m_firstAction->setEnabled(false); m_lastAction->setEnabled(false); } else { m_backwardAction->setEnabled(true); m_forwardAction->setEnabled(true); m_firstAction->setEnabled(true); m_lastAction->setEnabled(true); } if (d->currentIndex() == d->firstIndex()) { m_backwardAction->setEnabled(false); m_firstAction->setEnabled(false); } if (d->currentIndex() == d->lastIndex()) { m_forwardAction->setEnabled(false); m_lastAction->setEnabled(false); } /* // Disable some menu actions if the current root image URL // is not include in the digiKam Albums library database. // This is necessary when ImageEditor is opened from cameraclient. QUrl u(d->currentUrl().directory()); PAlbum* palbum = AlbumManager::instance()->findPAlbum(u); if (!palbum) { m_fileDeleteAction->setEnabled(false); } else { m_fileDeleteAction->setEnabled(true); } */ } void ImageWindow::slotToMainWindow() { close(); } void ImageWindow::saveIsComplete() { // With save(), we do not reload the image but just continue using the data. // This means that a saving operation does not lead to quality loss for // subsequent editing operations. // put image in cache, the LoadingCacheInterface cares for the details LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); ImageInfo info = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); // Save new face tags to the image saveFaceTagsToImage(info); // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentImageInfo.setOrientation(meta.getImageOrientation()); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); resetOrigin(); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } slotUpdateItemInfo(); setViewToURL(d->currentImageInfo.fileUrl()); } void ImageWindow::saveVersionIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "save version done"; saveAsIsComplete(); } void ImageWindow::saveAsIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "Saved" << m_savingContext.srcURL << "to" << m_savingContext.destinationURL; // Nothing to be done if operating without database if (d->currentImageInfo.isNull()) { return; } if (CollectionManager::instance()->albumRootPath(m_savingContext.srcURL).isNull() || CollectionManager::instance()->albumRootPath(m_savingContext.destinationURL).isNull()) { // not in-collection operation - nothing to do return; } // copy the metadata of the original file to the new file qCDebug(DIGIKAM_GENERAL_LOG) << "was versioned" << (m_savingContext.executedOperation == SavingContext::SavingStateVersion) << "current" << d->currentImageInfo.id() << d->currentImageInfo.name() << "destinations" << m_savingContext.versionFileOperation.allFilePaths(); ImageInfo sourceInfo = d->currentImageInfo; // Set new current index. Employ synchronous scanning for this main file. d->currentImageInfo = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); if (m_savingContext.destinationExisted) { // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentImageInfo.setOrientation(meta.getImageOrientation()); } QStringList derivedFilePaths; if (m_savingContext.executedOperation == SavingContext::SavingStateVersion) { derivedFilePaths = m_savingContext.versionFileOperation.allFilePaths(); } else { derivedFilePaths << m_savingContext.destinationURL.toLocalFile(); } // Will ensure files are scanned, and then copy attributes in a thread FileActionMngr::instance()->copyAttributes(sourceInfo, derivedFilePaths); // The model updates asynchronously, so we need to force addition of the main entry d->ensureModelContains(d->currentImageInfo); // Save new face tags to the image saveFaceTagsToImage(d->currentImageInfo); // set origin of EditorCore: "As if" the last saved image was loaded directly resetOriginSwitchFile(); // If the DImg is put in the cache under the new name, this means the new file will not be reloaded. // This may irritate users who want to check for quality loss in lossy formats. // In any case, only do that if the format did not change - too many assumptions otherwise (see bug #138949). if (m_savingContext.originalFormat == m_savingContext.format) { LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); } // all that is done in slotLoadCurrent, except for loading d->thumbBar->setCurrentIndex(d->currentIndex()); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } setViewToURL(d->currentImageInfo.fileUrl()); slotUpdateItemInfo(); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); } void ImageWindow::prepareImageToSave() { if (!d->currentImageInfo.isNull()) { MetadataHub hub; hub.load(d->currentImageInfo); // Get face tags d->newFaceTags.clear(); QMultiMap faceTags; faceTags = hub.loadIntegerFaceTags(d->currentImageInfo); if (!faceTags.isEmpty()) { QSize tempS = d->currentImageInfo.dimensions(); QMap::const_iterator it; for (it = faceTags.constBegin() ; it != faceTags.constEnd() ; ++it) { // Start transform each face rect QRect faceRect = it.value().toRect(); int tempH = tempS.height(); int tempW = tempS.width(); qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect before:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); for (int i = 0 ; i < m_transformQue.size() ; i++) { EditorWindow::TransformType type = m_transformQue[i]; switch (type) { case EditorWindow::TransformType::RotateLeft: faceRect = TagRegion::ajustToRotatedImg(faceRect, QSize(tempW, tempH), 1); std::swap(tempH, tempW); break; case EditorWindow::TransformType::RotateRight: faceRect = TagRegion::ajustToRotatedImg(faceRect, QSize(tempW, tempH), 0); std::swap(tempH, tempW); break; case EditorWindow::TransformType::FlipHorizontal: faceRect = TagRegion::ajustToFlippedImg(faceRect, QSize(tempW, tempH), 0); break; case EditorWindow::TransformType::FlipVertical: faceRect = TagRegion::ajustToFlippedImg(faceRect, QSize(tempW, tempH), 1); break; default: break; } qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect transform:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); } d->newFaceTags.insertMulti(it.key(), QVariant(faceRect)); } } // Ensure there is a UUID for the source image in the database, // even if not in the source image's metadata if (d->currentImageInfo.uuid().isNull()) { QString uuid = m_canvas->interface()->ensureHasCurrentUuid(); d->currentImageInfo.setUuid(uuid); } else { m_canvas->interface()->provideCurrentUuid(d->currentImageInfo.uuid()); } } } void ImageWindow::saveFaceTagsToImage(const ImageInfo& info) { if (!info.isNull() && !d->newFaceTags.isEmpty()) { // Delete all old faces FaceTagsEditor().removeAllFaces(info.id()); QMap::const_iterator it; for (it = d->newFaceTags.constBegin() ; it != d->newFaceTags.constEnd() ; ++it) { int tagId = FaceTags::getOrCreateTagForPerson(it.key()); if (tagId) { TagRegion region(it.value().toRect()); FaceTagsEditor().add(info.id(), tagId, region, false); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to create a person tag for name" << it.key(); } } MetadataHub hub; hub.load(info); QSize tempS = info.dimensions(); hub.setFaceTags(d->newFaceTags, tempS); hub.write(info.filePath(), MetadataHub::WRITE_ALL); } m_transformQue.clear(); d->newFaceTags.clear(); } VersionManager* ImageWindow::versionManager() const { return &d->versionManager; } bool ImageWindow::save() { prepareImageToSave(); startingSave(d->currentUrl()); return true; } bool ImageWindow::saveAs() { prepareImageToSave(); return startingSaveAs(d->currentUrl()); } bool ImageWindow::saveNewVersion() { prepareImageToSave(); return startingSaveNewVersion(d->currentUrl()); } bool ImageWindow::saveCurrentVersion() { prepareImageToSave(); return startingSaveCurrentVersion(d->currentUrl()); } bool ImageWindow::saveNewVersionAs() { prepareImageToSave(); return startingSaveNewVersionAs(d->currentUrl()); } bool ImageWindow::saveNewVersionInFormat(const QString& format) { prepareImageToSave(); return startingSaveNewVersionInFormat(d->currentUrl(), format); } QUrl ImageWindow::saveDestinationUrl() { return d->currentUrl(); } void ImageWindow::slotDeleteCurrentItem() { deleteCurrentItem(true, false); } void ImageWindow::slotDeleteCurrentItemPermanently() { deleteCurrentItem(true, true); } void ImageWindow::slotDeleteCurrentItemPermanentlyDirectly() { deleteCurrentItem(false, true); } void ImageWindow::slotTrashCurrentItemDirectly() { deleteCurrentItem(false, false); } void ImageWindow::deleteCurrentItem(bool ask, bool permanently) { // This function implements all four of the above slots. // The meaning of permanently differs depending on the value of ask if (d->currentImageInfo.isNull()) { return; } if (!promptUserDelete(d->currentUrl())) { return; } bool useTrash; if (ask) { bool preselectDeletePermanently = permanently; DeleteDialog dialog(this); QList urlList; urlList << d->currentUrl(); if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, preselectDeletePermanently ? DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash)) { return; } useTrash = !dialog.shouldDelete(); } else { useTrash = !permanently; } DIO::del(d->currentImageInfo, useTrash); // bring all (sidebar) to a defined state without letting them sit on the deleted file emit signalNoCurrentItem(); // We have database information, which means information will get through // everywhere. Just do it asynchronously. removeCurrent(); } void ImageWindow::removeCurrent() { if (!d->currentIsValid()) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } d->imageInfoModel->removeImageInfo(d->currentImageInfo); if (d->imageInfoModel->isEmpty()) { // No image in the current Album -> Quit ImageEditor... QMessageBox::information(this, i18n("No Image in Current Album"), i18n("There is no image to show in the current album.\n" "The image editor will be closed.")); close(); } } void ImageWindow::slotFileMetadataChanged(const QUrl& url) { if (url == d->currentUrl()) { m_canvas->interface()->readMetadataFromFile(url.toLocalFile()); } } /* * Should all be done by ItemViewCategorized * void ImageWindow::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // ignore when closed if (!isVisible() || !d->currentIsValid()) { return; } QModelIndex currentIndex = d->currentIndex(); if (currentIndex.row() >= start && currentIndex.row() <= end) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); // ensure selection int totalToRemove = end - start + 1; if (d->imageFilterModel->rowCount(parent) > totalToRemove) { if (end == d->imageFilterModel->rowCount(parent) - 1) { loadIndex(d->imageFilterModel->index(start - 1, 0)); // last remaining, no next one left } else { loadIndex(d->imageFilterModel->index(end + 1, 0)); // next remaining } } } }*/ /* void ImageWindow::slotCollectionImageChange(const CollectionImageChangeset& changeset) { bool needLoadCurrent = false; switch (changeset.operation()) { case CollectionImageChangeset::Removed: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsImage(d->imageInfoList[i].id())) { if (d->currentImageInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; case CollectionImageChangeset::RemovedAll: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsAlbum(d->imageInfoList[i].albumId())) { if (d->currentImageInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; default: break; } if (needLoadCurrent) { QTimer::singleShot(0, this, SLOT(slotLoadCurrent())); } } */ void ImageWindow::dragMoveEvent(QDragMoveEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs) || DAlbumDrag::decode(e->mimeData(), urls, albumID) || DTagListDrag::canDecode(e->mimeData())) { e->accept(); return; } e->ignore(); } void ImageWindow::dropEvent(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { ImageInfoList imageInfoList(imageIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; AlbumManager* const man = AlbumManager::instance(); PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); ImageInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); ImageInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; TAlbum* const talbum = man->findTAlbum(tagIDs.first()); if (talbum) { ATitle = talbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else { e->ignore(); } } void ImageWindow::slotRevert() { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } } void ImageWindow::slotOpenOriginal() { if (!hasOriginalToRestore()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } // this time, with mustBeAvailable = true DImageHistory availableResolved = ImageScanner::resolvedImageHistory(m_canvas->interface()->getImageHistory(), true); QList originals = availableResolved.referredImagesOfType(HistoryImageId::Original); HistoryImageId originalId = m_canvas->interface()->getImageHistory().originalReferredImage(); if (originals.isEmpty()) { //TODO: point to remote collection QMessageBox::warning(this, i18nc("@title", "File Not Available"), i18nc("@info", "The original file (%1) is currently not available", originalId.m_fileName)); return; } QList imageInfos; foreach(const HistoryImageId& id, originals) { QUrl url = QUrl::fromLocalFile(id.m_filePath); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + (id.m_fileName)); imageInfos << ImageInfo::fromUrl(url); } ImageScanner::sortByProximity(imageInfos, d->currentImageInfo); if (!imageInfos.isEmpty() && !imageInfos.first().isNull()) { openImage(imageInfos.first()); } } bool ImageWindow::hasOriginalToRestore() { // not implemented for db-less situation, so check for ImageInfo return !d->currentImageInfo.isNull() && EditorWindow::hasOriginalToRestore(); } DImageHistory ImageWindow::resolvedImageHistory(const DImageHistory& history) { return ImageScanner::resolvedImageHistory(history); } ThumbBarDock* ImageWindow::thumbBar() const { return d->thumbBarDock; } Sidebar* ImageWindow::rightSideBar() const { return (dynamic_cast(d->rightSideBar)); } void ImageWindow::slotComponentsInfo() { showDigikamComponentsInfo(); } void ImageWindow::slotDBStat() { showDigikamDatabaseStat(); } void ImageWindow::slotAddedDropedItems(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; ImageInfoList imgList; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { imgList = ImageInfoList(imageIDs); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); imgList = ImageInfoList(itemIDs); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); imgList = ImageInfoList(itemIDs); } e->accept(); if (!imgList.isEmpty()) { loadImageInfos(imgList, imgList.first(), QString()); } } void ImageWindow::slotFileWithDefaultApplication() { DFileOperations::openFilesWithDefaultApplication(QList() << d->currentUrl()); } void ImageWindow::slotOpenWith(QAction* action) { openWith(d->currentUrl(), action); } void ImageWindow::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void ImageWindow::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void ImageWindow::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } } // namespace Digikam diff --git a/core/utilities/import/widgets/albumcustomizer.cpp b/core/utilities/import/widgets/albumcustomizer.cpp index d6ad2a29fe..0ccd8529b1 100644 --- a/core/utilities/import/widgets/albumcustomizer.cpp +++ b/core/utilities/import/widgets/albumcustomizer.cpp @@ -1,267 +1,267 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-08-11 * Description : a widget to customize album name created by * camera interface. * * 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 "albumcustomizer.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "tooltipdialog.h" #include "digikam_debug.h" #include "dexpanderbox.h" namespace Digikam { class Q_DECL_HIDDEN AlbumCustomizer::Private { public: explicit Private() : autoAlbumDateCheck(0), autoAlbumExtCheck(0), folderDateLabel(0), customizer(0), tooltipToggleButton(0), customExample(0), folderDateFormat(0), tooltipDialog(0) { } QCheckBox* autoAlbumDateCheck; QCheckBox* autoAlbumExtCheck; QLabel* folderDateLabel; QLineEdit* customizer; QToolButton* tooltipToggleButton; DAdjustableLabel* customExample; QComboBox* folderDateFormat; TooltipDialog* tooltipDialog; }; AlbumCustomizer::AlbumCustomizer(QWidget* const parent) : QWidget(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->tooltipDialog = new TooltipDialog(this); d->tooltipDialog->setTooltip(i18n("

These expressions may be used to customize date format:

" "

d: The day as a number without a leading zero (1 to 31)

" "

dd: The day as a number with a leading zero (01 to 31)

" "

ddd: The abbreviated localized day name (e.g. 'Mon' to 'Sun')

" "

dddd: The long localized day name (e.g. 'Monday' to 'Sunday').

" "

M: The month as a number without a leading zero (1 to 12)

" "

MM: The month as a number with a leading zero (01 to 12)

" "

MMM: The abbreviated localized month name (e.g. 'Jan' to 'Dec')

" "

MMMM: The long localized month name (e.g. 'January' to 'December')

" "

yy: The year as two digit number (eg. 00 to 99)

" "

yyyy: The year as four digit number (eg. 2012)

" "

All other input characters will be treated as text. Any sequence of characters " "that are enclosed in singlequotes will be treated as text and not be used as an " - "expression. Examples, if date is 20 July 1969:

" + "expression. Examples, if date is 20 July 1969:

" "

dd.MM.yyyy : 20.07.1969

" "

ddd MMMM d yy : Sun July 20 69

" "

'Photo shot on ' dddd : Photo shot on Sunday

" )); d->tooltipDialog->resize(650, 530); QVBoxLayout* const albumVlay = new QVBoxLayout(this); d->autoAlbumExtCheck = new QCheckBox(i18nc("@option:check", "Extension-based sub-albums"), this); d->autoAlbumDateCheck = new QCheckBox(i18nc("@option:check", "Date-based sub-albums"), this); DHBox* const hbox1 = new DHBox(this); d->folderDateLabel = new QLabel(i18nc("@label:listbox", "Date format:"), hbox1); d->folderDateFormat = new QComboBox(hbox1); d->folderDateFormat->insertItem(IsoDateFormat, i18nc("@item:inlistbox", "ISO")); d->folderDateFormat->insertItem(TextDateFormat, i18nc("@item:inlistbox", "Full Text")); d->folderDateFormat->insertItem(LocalDateFormat, i18nc("@item:inlistbox", "Local Settings")); d->folderDateFormat->insertItem(CustomDateFormat, i18nc("@item:inlistbox", "Custom")); DHBox* const hbox2 = new DHBox(this); d->customizer = new QLineEdit(hbox2); d->tooltipToggleButton = new QToolButton(hbox2); d->tooltipToggleButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->tooltipToggleButton->setToolTip(i18n("Show a list of all available options")); d->customExample = new DAdjustableLabel(this); albumVlay->addWidget(d->autoAlbumExtCheck); albumVlay->addWidget(d->autoAlbumDateCheck); albumVlay->addWidget(hbox1); albumVlay->addWidget(hbox2); albumVlay->addWidget(d->customExample); albumVlay->addStretch(); albumVlay->setContentsMargins(spacing, spacing, spacing, spacing); albumVlay->setSpacing(spacing); setWhatsThis(i18n("Set how digiKam creates albums automatically when downloading.")); d->autoAlbumExtCheck->setWhatsThis(i18n("Enable this option if you want to download your " "pictures into automatically created file extension-based sub-albums of the destination " "album. This way, you can separate JPEG and RAW files as they are downloaded from your camera.")); d->autoAlbumDateCheck->setWhatsThis(i18n("Enable this option if you want to " "download your pictures into automatically created file date-based sub-albums " "of the destination album.")); d->folderDateFormat->setWhatsThis(i18n("

Select your preferred date format used to " "create new albums. The options available are:

" "

ISO: the date format is in accordance with ISO 8601 " "(YYYY-MM-DD). E.g.: 2006-08-24

" "

Full Text: the date format is in a user-readable string. " "E.g.: Thu Aug 24 2006

" "

Local Settings: the date format depending on KDE control panel settings.

" "

Custom: use a customized format for date.

")); d->customExample->setWhatsThis(i18n("Show the result of converted date 1968-12-26 using your customized format.")); // -------------------------------------------------------------------------------------- connect(d->autoAlbumDateCheck, SIGNAL(toggled(bool)), d->folderDateFormat, SLOT(setEnabled(bool))); connect(d->autoAlbumDateCheck, SIGNAL(toggled(bool)), d->folderDateLabel, SLOT(setEnabled(bool))); connect(d->tooltipToggleButton, SIGNAL(clicked(bool)), this, SLOT(slotToolTipButtonToggled(bool))); connect(d->folderDateFormat, SIGNAL(activated(int)), this, SLOT(slotFolderDateFormatChanged(int))); connect(d->customizer, SIGNAL(textChanged(QString)), this, SLOT(slotCustomizerChanged())); } AlbumCustomizer::~AlbumCustomizer() { delete d; } void AlbumCustomizer::readSettings(KConfigGroup& group) { d->autoAlbumDateCheck->setChecked(group.readEntry("AutoAlbumDate", false)); d->autoAlbumExtCheck->setChecked(group.readEntry("AutoAlbumExt", false)); d->folderDateFormat->setCurrentIndex(group.readEntry("FolderDateFormat", (int)IsoDateFormat)); d->customizer->setText(group.readEntry("CustomDateFormat", QString())); d->folderDateFormat->setEnabled(d->autoAlbumDateCheck->isChecked()); d->folderDateLabel->setEnabled(d->autoAlbumDateCheck->isChecked()); slotFolderDateFormatChanged(d->folderDateFormat->currentIndex()); } void AlbumCustomizer::saveSettings(KConfigGroup& group) { group.writeEntry("AutoAlbumDate", d->autoAlbumDateCheck->isChecked()); group.writeEntry("AutoAlbumExt", d->autoAlbumExtCheck->isChecked()); group.writeEntry("FolderDateFormat", d->folderDateFormat->currentIndex()); group.writeEntry("CustomDateFormat", d->customizer->text()); } bool AlbumCustomizer::autoAlbumDateEnabled() const { return d->autoAlbumDateCheck->isChecked(); } bool AlbumCustomizer::autoAlbumExtEnabled() const { return d->autoAlbumExtCheck->isChecked(); } int AlbumCustomizer::folderDateFormat() const { return d->folderDateFormat->currentIndex(); } QString AlbumCustomizer::customDateFormat() const { return d->customizer->text(); } bool AlbumCustomizer::customDateFormatIsValid() const { QDate date(1968, 12, 26); return !date.toString(customDateFormat()).isEmpty(); } void AlbumCustomizer::slotToolTipButtonToggled(bool /*checked*/) { if (!d->tooltipDialog->isVisible()) { d->tooltipDialog->show(); } d->tooltipDialog->raise(); } void AlbumCustomizer::slotFolderDateFormatChanged(int index) { bool b = (index == CustomDateFormat); d->customizer->setEnabled(b); d->tooltipToggleButton->setEnabled(b); d->customExample->setEnabled(b); slotCustomizerChanged(); } void AlbumCustomizer::slotCustomizerChanged() { if (folderDateFormat() == CustomDateFormat) { QDate date(1968, 12, 26); if (customDateFormatIsValid()) { d->customExample->setAdjustedText(i18nc("Example of custom date format for album naming", "Ex.: %1", date.toString(customDateFormat()))); } else { d->customExample->setAdjustedText(i18nc("Custom date format", "Format is not valid...")); } } else { d->customExample->setAdjustedText(); } } } // namespace Digikam diff --git a/core/utilities/import/widgets/scriptingsettings.cpp b/core/utilities/import/widgets/scriptingsettings.cpp index 5e0b5e531e..b9a9fa512c 100644 --- a/core/utilities/import/widgets/scriptingsettings.cpp +++ b/core/utilities/import/widgets/scriptingsettings.cpp @@ -1,139 +1,139 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-07-20 * Description : scripting settings for camera interface. * * Copyright (C) 2012 by Petri Damstén * * 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 "scriptingsettings.h" // Qt includes #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "dfileselector.h" #include "digikam_debug.h" #include "tooltipdialog.h" namespace Digikam { class Q_DECL_HIDDEN ScriptingSettings::Private { public: explicit Private() : scriptLabel(0), script(0), tooltipDialog(0), tooltipToggleButton(0) { } QLabel* scriptLabel; DFileSelector* script; TooltipDialog* tooltipDialog; QToolButton* tooltipToggleButton; }; ScriptingSettings::ScriptingSettings(QWidget* const parent) : QWidget(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->tooltipDialog = new TooltipDialog(this); d->tooltipDialog->setTooltip(i18n("

These expressions may be used to customize the command line:

" "

%file: full path of the imported file

" "

%filename: file name of the imported file

" "

%path: path of the imported file

" "

%orgfilename: original file name

" "

%orgpath: original path

" - "

If there are no expressions full path is added to the command.

" + "

If there are no expressions full path is added to the command.

" )); d->tooltipDialog->resize(650, 530); QVBoxLayout* const vlay = new QVBoxLayout(this); d->scriptLabel = new QLabel(i18n("Execute script for image:"), this); DHBox* const hbox = new DHBox(this); d->script = new DFileSelector(hbox); d->script->setFileDlgMode(QFileDialog::ExistingFile); d->script->lineEdit()->setPlaceholderText(i18n("No script selected")); d->tooltipToggleButton = new QToolButton(hbox); d->tooltipToggleButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->tooltipToggleButton->setToolTip(i18n("Show a list of all available options")); vlay->addWidget(d->scriptLabel); vlay->addWidget(hbox); vlay->addStretch(); vlay->setContentsMargins(spacing, spacing, spacing, spacing); vlay->setSpacing(spacing); setWhatsThis(i18n("Set here the script that is executed for every imported image.")); // --------------------------------------------------------------------------------------- connect(d->tooltipToggleButton, SIGNAL(clicked(bool)), this, SLOT(slotToolTipButtonToggled(bool))); } ScriptingSettings::~ScriptingSettings() { delete d; } void ScriptingSettings::readSettings(KConfigGroup& group) { d->script->setFileDlgPath(group.readEntry("Script", QString())); } void ScriptingSettings::saveSettings(KConfigGroup& group) { group.writeEntry("Script", d->script->fileDlgPath()); } void ScriptingSettings::settings(DownloadSettings* const settings) const { settings->script = d->script->fileDlgPath(); } void ScriptingSettings::slotToolTipButtonToggled(bool /*checked*/) { if (!d->tooltipDialog->isVisible()) { d->tooltipDialog->show(); } d->tooltipDialog->raise(); } } // namespace Digikam diff --git a/core/utilities/setup/album/setupmime.cpp b/core/utilities/setup/album/setupmime.cpp index ee20e252ed..8f19dc7c24 100644 --- a/core/utilities/setup/album/setupmime.cpp +++ b/core/utilities/setup/album/setupmime.cpp @@ -1,337 +1,337 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2003-05-03 * Description : mime types setup tab * * Copyright (C) 2004-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 "setupmime.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "applicationsettings.h" #include "coredb.h" #include "coredbaccess.h" #include "dlayoutbox.h" #include "scancontroller.h" #include "setuputils.h" namespace Digikam { class Q_DECL_HIDDEN SetupMime::Private { public: explicit Private() : imageFileFilterLabel(0), movieFileFilterLabel(0), audioFileFilterLabel(0), imageFileFilterEdit(0), movieFileFilterEdit(0), audioFileFilterEdit(0) { } QLabel* imageFileFilterLabel; QLabel* movieFileFilterLabel; QLabel* audioFileFilterLabel; QLineEdit* imageFileFilterEdit; QLineEdit* movieFileFilterEdit; QLineEdit* audioFileFilterEdit; }; SetupMime::SetupMime(QWidget* const parent) : QScrollArea(parent), d(new Private) { QWidget* const panel = new QWidget(viewport()); setWidget(panel); setWidgetResizable(true); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(panel); // -------------------------------------------------------- QLabel* const explanationLabel = new QLabel; - explanationLabel->setText(i18n("

Add new file types to show as album items. " + explanationLabel->setText(i18n("

Add new file types to show as album items.

" "

digiKam attempts to support all of the image formats that digital cameras produce, " "while being able to handle a few other important video and audio formats.

" "

You can add to the already-appreciable list of formats that digiKam handles by " "adding the extension of the type you want to add. " "Multiple extensions need to be separated by a semicolon or space.

" "

Note: changes done in this view will perform " "a database rescan in the background.

")); explanationLabel->setWordWrap(true); // -------------------------------------------------------- QGroupBox* const imageFileFilterBox = new QGroupBox(i18n("Image Files"), panel); QGridLayout* const grid1 = new QGridLayout(imageFileFilterBox); QLabel* const logoLabel1 = new QLabel(imageFileFilterBox); logoLabel1->setPixmap(QIcon::fromTheme(QLatin1String("image-jpeg")).pixmap(48)); d->imageFileFilterLabel = new QLabel(imageFileFilterBox); d->imageFileFilterLabel->setText(i18n("Additional &image file extensions (Currently-supported types):")); DHBox* const hbox1 = new DHBox(imageFileFilterBox); d->imageFileFilterEdit = new QLineEdit(hbox1); d->imageFileFilterEdit->setWhatsThis(i18n("

Here you can add the extensions of image files (including RAW files) " "to be displayed in the Album view. Just put \"xyz abc\" " "to display files with the xyz and abc extensions in your Album view.

" "

You can also remove file formats that are shown by default " "by putting a minus sign in front of the extension: e.g. \"-gif\" would remove all GIF files " "from your Album view and any trace of them in your database. " "They would not be deleted, just not shown in digiKam.

" "

Warning: Removing files from the database means losing " "all of their tags and ratings.

")); d->imageFileFilterEdit->setClearButtonEnabled(true); d->imageFileFilterEdit->setPlaceholderText(i18n("Enter additional image file extensions.")); d->imageFileFilterLabel->setBuddy(d->imageFileFilterEdit); hbox1->setStretchFactor(d->imageFileFilterEdit, 10); grid1->addWidget(logoLabel1, 0, 0, 2, 1); grid1->addWidget(d->imageFileFilterLabel, 0, 1, 1, 1); grid1->addWidget(hbox1, 1, 1, 1, 1); grid1->setColumnStretch(1, 10); grid1->setSpacing(spacing); // -------------------------------------------------------- QGroupBox* const movieFileFilterBox = new QGroupBox(i18n("Video Files"), panel); QGridLayout* const grid2 = new QGridLayout(movieFileFilterBox); QLabel* const logoLabel2 = new QLabel(movieFileFilterBox); logoLabel2->setPixmap(QIcon::fromTheme(QLatin1String("video-x-matroska")).pixmap(48)); d->movieFileFilterLabel = new QLabel(movieFileFilterBox); d->movieFileFilterLabel->setText(i18n("Additional &video file extensions (Currently-supported types):")); DHBox* const hbox2 = new DHBox(movieFileFilterBox); d->movieFileFilterEdit = new QLineEdit(hbox2); d->movieFileFilterEdit->setWhatsThis(i18n("

Here you can add extra extensions of video files " "to be displayed in your Album view. Just write \"xyz abc\" " "to support files with the *.xyz and *.abc extensions. " "Clicking on these files will " "play them in an embedded video player.

" "

You can also remove file formats that are supported by default " "by putting a minus sign in front of the extension: e.g. \"-avi\" would remove " "all AVI files from your Album view and any trace of them in your database. " "They would not be deleted, just not shown in digiKam.

" "

Warning: Removing files from the database means losing " "all of their tags and ratings.

")); d->movieFileFilterEdit->setClearButtonEnabled(true); d->movieFileFilterEdit->setPlaceholderText(i18n("Enter additional video file extensions.")); d->movieFileFilterLabel->setBuddy(d->movieFileFilterEdit); hbox2->setStretchFactor(d->movieFileFilterEdit, 10); grid2->addWidget(logoLabel2, 0, 0, 2, 1); grid2->addWidget(d->movieFileFilterLabel, 0, 1, 1, 1); grid2->addWidget(hbox2, 1, 1, 1, 1); grid2->setColumnStretch(1, 10); grid2->setSpacing(spacing); // -------------------------------------------------------- QGroupBox* const audioFileFilterBox = new QGroupBox(i18n("Audio Files"), panel); QGridLayout* const grid3 = new QGridLayout(audioFileFilterBox); QLabel* const logoLabel3 = new QLabel(audioFileFilterBox); logoLabel3->setPixmap(QIcon::fromTheme(QLatin1String("audio-x-mpeg")).pixmap(48)); d->audioFileFilterLabel = new QLabel(audioFileFilterBox); d->audioFileFilterLabel->setText(i18n("Additional &audio file extensions (Currently-supported types):")); DHBox* const hbox3 = new DHBox(audioFileFilterBox); d->audioFileFilterEdit = new QLineEdit(hbox3); d->audioFileFilterEdit->setWhatsThis(i18n("

Here you can add extra extensions of audio files " "to be displayed in your Album view. Just write \"xyz abc\" " "to support files with the *.xyz and *.abc extensions. " "Clicking on these files will " "play them in an embedded audio player.

" "

You can also remove file formats that are supported by default " "by putting a minus sign in front of the extension: e.g. \"-ogg\" would " "remove all OGG files from your Album view and any trace of them in your database. " "They would not be deleted, just not shown in digiKam.

" "

Warning: Removing files from the database means losing " "all of their tags and ratings.

")); d->audioFileFilterEdit->setClearButtonEnabled(true); d->audioFileFilterEdit->setPlaceholderText(i18n("Enter additional audio file extensions.")); d->audioFileFilterLabel->setBuddy(d->audioFileFilterEdit); hbox3->setStretchFactor(d->audioFileFilterEdit, 10); grid3->addWidget(logoLabel3, 0, 0, 2, 1); grid3->addWidget(d->audioFileFilterLabel, 0, 1, 1, 1); grid3->addWidget(hbox3, 1, 1, 1, 1); grid3->setColumnStretch(1, 10); grid3->setSpacing(spacing); // -------------------------------------------------------- layout->setContentsMargins(spacing, spacing, spacing, spacing); layout->setSpacing(spacing); layout->addWidget(explanationLabel); layout->addWidget(imageFileFilterBox); layout->addWidget(movieFileFilterBox); layout->addWidget(audioFileFilterBox); layout->addStretch(); // -------------------------------------------------------- connect(d->imageFileFilterLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowCurrentImageSettings())); connect(d->movieFileFilterLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowCurrentMovieSettings())); connect(d->audioFileFilterLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowCurrentAudioSettings())); // -------------------------------------------------------- readSettings(); } SetupMime::~SetupMime() { delete d; } void SetupMime::applySettings() { // Display warning if user removes a core format QStringList coreImageFormats, removedImageFormats; coreImageFormats << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png"); // PNG QString imageFilter = d->imageFileFilterEdit->text(); foreach(const QString& format, coreImageFormats) { if (imageFilter.contains(QLatin1Char('-') + format) || imageFilter.contains(QLatin1String("-*.") + format)) { removedImageFormats << format; } } if (!removedImageFormats.isEmpty()) { int result = QMessageBox::warning(this, qApp->applicationName(), i18n("

You have chosen to remove the following image formats " "from the list of supported formats: %1.

" "

These are very common formats. If you have images in your collection " "with these formats, they will be removed from the database and you will " "lose all information about them, including rating and tags.

" "

Are you sure you want to apply your changes and lose the support for these formats?

", removedImageFormats.join(QLatin1Char(' '))), QMessageBox::Yes | QMessageBox::No); if (result != QMessageBox::Yes) { return; } } QString imageFilterString; QString movieFilterString; QString audioFilterString; CoreDbAccess().db()->getUserFilterSettings(&imageFilterString, &movieFilterString, &audioFilterString); imageFilterString.replace(QLatin1Char(';'), QLatin1Char(' ')); movieFilterString.replace(QLatin1Char(';'), QLatin1Char(' ')); audioFilterString.replace(QLatin1Char(';'), QLatin1Char(' ')); if (d->imageFileFilterEdit->text() != imageFilterString || d->movieFileFilterEdit->text() != movieFilterString || d->audioFileFilterEdit->text() != audioFilterString) { CoreDbAccess().db()->setUserFilterSettings(cleanUserFilterString(d->imageFileFilterEdit->text()), cleanUserFilterString(d->movieFileFilterEdit->text()), cleanUserFilterString(d->audioFileFilterEdit->text())); ScanController::instance()->completeCollectionScanInBackground(false); } } void SetupMime::readSettings() { QString image; QString audio; QString video; CoreDbAccess().db()->getUserFilterSettings(&image, &video, &audio); d->imageFileFilterEdit->setText(image.replace(QLatin1Char(';'), QLatin1Char(' '))); d->movieFileFilterEdit->setText(video.replace(QLatin1Char(';'), QLatin1Char(' '))); d->audioFileFilterEdit->setText(audio.replace(QLatin1Char(';'), QLatin1Char(' '))); } void SetupMime::slotShowCurrentImageSettings() { QStringList imageList; CoreDbAccess().db()->getFilterSettings(&imageList, 0, 0); QString text = i18n("

Files with these extensions will be recognized as images " "and included into the database:
%1

", imageList.join(QLatin1Char(' '))); QWhatsThis::showText(d->imageFileFilterLabel->mapToGlobal(QPoint(0, 0)), text, d->imageFileFilterLabel); } void SetupMime::slotShowCurrentMovieSettings() { QStringList movieList; CoreDbAccess().db()->getFilterSettings(0, &movieList, 0); QString text = i18n("

Files with these extensions will be recognized as video files " "and included into the database:
%1

", movieList.join(QLatin1Char(' '))); QWhatsThis::showText(d->movieFileFilterLabel->mapToGlobal(QPoint(0, 0)), text, d->movieFileFilterLabel); } void SetupMime::slotShowCurrentAudioSettings() { QStringList audioList; CoreDbAccess().db()->getFilterSettings(0, 0, &audioList); QString text = i18n("

Files with these extensions will be recognized as audio files " "and included into the database:
%1

", audioList.join(QLatin1Char(' '))); QWhatsThis::showText(d->audioFileFilterLabel->mapToGlobal(QPoint(0, 0)), text, d->audioFileFilterLabel); } } // namespace Digikam diff --git a/core/utilities/setup/setup.cpp b/core/utilities/setup/setup.cpp index d4b16ffe01..d2636e1315 100644 --- a/core/utilities/setup/setup.cpp +++ b/core/utilities/setup/setup.cpp @@ -1,574 +1,574 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2003-02-03 * Description : digiKam setup dialog. * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2003-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU Album * 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 Album Public License for more details. * * ============================================================ */ #include "setup.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "applicationsettings.h" #include "thumbsgenerator.h" #include "setupalbumview.h" #include "setupcamera.h" #include "setupcollections.h" #include "setupeditor.h" #include "setupicc.h" #include "setuplighttable.h" #include "setupmetadata.h" #include "setupmisc.h" #include "setupslideshow.h" #include "setupimagequalitysorter.h" #include "setuptooltip.h" #include "setupdatabase.h" #include "importsettings.h" #include "dxmlguiwindow.h" namespace Digikam { class Q_DECL_HIDDEN Setup::Private { public: explicit Private() : page_database(0), page_collections(0), page_albumView(0), page_tooltip(0), page_metadata(0), page_template(0), page_lighttable(0), page_editor(0), page_slideshow(0), page_imagequalitysorter(0), page_icc(0), page_camera(0), page_misc(0), databasePage(0), collectionsPage(0), albumViewPage(0), tooltipPage(0), metadataPage(0), templatePage(0), lighttablePage(0), editorPage(0), slideshowPage(0), imageQualitySorterPage(0), iccPage(0), cameraPage(0), miscPage(0) { } DConfigDlgWdgItem* page_database; DConfigDlgWdgItem* page_collections; DConfigDlgWdgItem* page_albumView; DConfigDlgWdgItem* page_tooltip; DConfigDlgWdgItem* page_metadata; DConfigDlgWdgItem* page_template; DConfigDlgWdgItem* page_lighttable; DConfigDlgWdgItem* page_editor; DConfigDlgWdgItem* page_slideshow; DConfigDlgWdgItem* page_imagequalitysorter; DConfigDlgWdgItem* page_icc; DConfigDlgWdgItem* page_camera; DConfigDlgWdgItem* page_misc; SetupDatabase* databasePage; SetupCollections* collectionsPage; SetupAlbumView* albumViewPage; SetupToolTip* tooltipPage; SetupMetadata* metadataPage; SetupTemplate* templatePage; SetupLightTable* lighttablePage; SetupEditor* editorPage; SetupSlideShow* slideshowPage; SetupImageQualitySorter* imageQualitySorterPage; SetupICC* iccPage; SetupCamera* cameraPage; SetupMisc* miscPage; public: DConfigDlgWdgItem* pageItem(Setup::Page page) const; }; Setup::Setup(QWidget* const parent) : DConfigDlg(parent), d(new Private) { setWindowFlags((windowFlags() & ~Qt::Dialog) | Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowMinMaxButtonsHint); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(List); setModal(true); d->databasePage = new SetupDatabase(); d->page_database = addPage(d->databasePage, i18n("Database")); d->page_database->setHeader(i18n("Database Settings
" "Customize database settings
")); d->page_database->setIcon(QIcon::fromTheme(QLatin1String("network-server-database"))); d->collectionsPage = new SetupCollections(); d->page_collections = addPage(d->collectionsPage, i18n("Collections")); d->page_collections->setHeader(i18n("Collections Settings
" "Set root albums locations
")); d->page_collections->setIcon(QIcon::fromTheme(QLatin1String("folder-pictures"))); d->albumViewPage = new SetupAlbumView(); d->page_albumView = addPage(d->albumViewPage, i18n("Views")); d->page_albumView->setHeader(i18n("Application Views Settings
" "Customize the look of the views
")); d->page_albumView->setIcon(QIcon::fromTheme(QLatin1String("view-list-icons"))); d->tooltipPage = new SetupToolTip(); d->page_tooltip = addPage(d->tooltipPage, i18n("Tool-Tip")); d->page_tooltip->setHeader(i18n("Items Tool-Tip Settings
" "Customize information in item tool-tips
")); d->page_tooltip->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->metadataPage = new SetupMetadata(); d->page_metadata = addPage(d->metadataPage, i18n("Metadata")); d->page_metadata->setHeader(i18n("Embedded Image Information Management
" "Setup relations between images and metadata
")); d->page_metadata->setIcon(QIcon::fromTheme(QLatin1String("format-text-code"))); // krazy:exclude=iconnames d->templatePage = new SetupTemplate(); d->page_template = addPage(d->templatePage, i18n("Templates")); d->page_template->setHeader(i18n("Metadata templates
" "Manage your collection of metadata templates
")); d->page_template->setIcon(QIcon::fromTheme(QLatin1String("im-user"))); d->editorPage = new SetupEditor(); d->page_editor = addPage(d->editorPage, i18n("Image Editor")); d->page_editor->setHeader(i18n("Image Editor Settings
" "Customize the image editor settings
")); d->page_editor->setIcon(QIcon::fromTheme(QLatin1String("document-edit"))); d->iccPage = new SetupICC(buttonBox()); d->page_icc = addPage(d->iccPage, i18n("Color Management")); d->page_icc->setHeader(i18n("Settings for Color Management
" "Customize the color management settings
")); d->page_icc->setIcon(QIcon::fromTheme(QLatin1String("preferences-desktop-display-color"))); d->lighttablePage = new SetupLightTable(); d->page_lighttable = addPage(d->lighttablePage, i18n("Light Table")); d->page_lighttable->setHeader(i18n("Light Table Settings
" "Customize tool used to compare images
")); d->page_lighttable->setIcon(QIcon::fromTheme(QLatin1String("lighttable"))); d->slideshowPage = new SetupSlideShow(); d->page_slideshow = addPage(d->slideshowPage, i18n("Slide Show")); d->page_slideshow->setHeader(i18n("Slide Show Settings
" "Customize slideshow settings
")); d->page_slideshow->setIcon(QIcon::fromTheme(QLatin1String("view-presentation"))); d->imageQualitySorterPage = new SetupImageQualitySorter(); d->page_imagequalitysorter = addPage(d->imageQualitySorterPage, i18n("Image Quality Sorter")); - d->page_imagequalitysorter->setHeader(i18n("Image Quality Sorter Settings
")); + d->page_imagequalitysorter->setHeader(i18n("Image Quality Sorter Settings")); d->page_imagequalitysorter->setIcon(QIcon::fromTheme(QLatin1String("flag-green"))); d->cameraPage = new SetupCamera(); d->page_camera = addPage(d->cameraPage, i18n("Cameras")); d->page_camera->setHeader(i18n("Camera Settings
" "Manage your camera devices
")); d->page_camera->setIcon(QIcon::fromTheme(QLatin1String("camera-photo"))); connect(d->cameraPage, SIGNAL(signalUseFileMetadataChanged(bool)), d->tooltipPage, SLOT(slotUseFileMetadataChanged(bool))); connect(buttonBox(), SIGNAL(helpRequested()), this, SLOT(slotHelp())); connect(buttonBox()->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &Setup::slotOkClicked); d->miscPage = new SetupMisc(); d->page_misc = addPage(d->miscPage, i18n("Miscellaneous")); d->page_misc->setHeader(i18n("Miscellaneous Settings
" "Customize behavior of the other parts of digiKam
")); d->page_misc->setIcon(QIcon::fromTheme(QLatin1String("preferences-other"))); for (int i = 0 ; i != SetupPageEnumLast ; ++i) { DConfigDlgWdgItem* const item = d->pageItem((Page)i); if (!item) { continue; } QWidget* const wgt = item->widget(); QScrollArea* const scrollArea = qobject_cast(wgt); if (scrollArea) { scrollArea->setFrameShape(QFrame::NoFrame); } } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Setup Dialog")); winId(); windowHandle()->resize(800, 600); DXmlGuiWindow::restoreWindowSize(windowHandle(), group); resize(windowHandle()->size()); } Setup::~Setup() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Setup Dialog")); group.writeEntry(QLatin1String("Setup Page"), (int)activePageIndex()); DXmlGuiWindow::saveWindowSize(windowHandle(), group); config->sync(); delete d; } void Setup::slotHelp() { DXmlGuiWindow::openHandbook(); } void Setup::setTemplate(const Template& t) { if (d->templatePage) { d->templatePage->setTemplate(t); } } QSize Setup::sizeHint() const { // The minimum size is very small. But the default initial size is such // that some important tabs get a scroll bar, although the dialog could be larger // on a normal display (QScrollArea size hint does not take widget into account) // Adjust size hint here so that certain selected tabs are display full per default. QSize hint = DConfigDlg::sizeHint(); int maxHintHeight = 0; int maxWidgetHeight = 0; for (int page = 0 ; page != SetupPageEnumLast ; ++page) { // only take tabs into account here that should better be displayed without scrolling if (page == CollectionsPage || page == AlbumViewPage || page == TemplatePage || page == LightTablePage || page == EditorPage || page == MiscellaneousPage) { DConfigDlgWdgItem* const item = d->pageItem((Page)page); if (!item) { continue; } QWidget* const page = item->widget(); maxHintHeight = qMax(maxHintHeight, page->sizeHint().height()); QScrollArea* const scrollArea = qobject_cast(page); if (scrollArea) { maxWidgetHeight = qMax(maxWidgetHeight, scrollArea->widget()->sizeHint().height()); } } } // The additional 20 is a hack to make it work. // Don't know why, the largest page would have scroll bars without this if (maxWidgetHeight > maxHintHeight) { hint.setHeight(hint.height() + (maxWidgetHeight - maxHintHeight) + 20); } return hint; } bool Setup::execDialog(Page page) { return execDialog(0, page); } bool Setup::execDialog(QWidget* const parent, Page page) { QPointer setup = new Setup(parent); setup->showPage(page); bool success = (setup->DConfigDlg::exec() == QDialog::Accepted); delete setup; return success; } bool Setup::execSinglePage(Page page) { return execSinglePage(0, page); } bool Setup::execSinglePage(QWidget* const parent, Page page) { QPointer setup = new Setup(parent); setup->showPage(page); setup->setFaceType(Plain); bool success = (setup->DConfigDlg::exec() == QDialog::Accepted); delete setup; return success; } bool Setup::execTemplateEditor(QWidget* const parent, const Template& t) { QPointer setup = new Setup(parent); setup->showPage(TemplatePage); setup->setFaceType(Plain); setup->setTemplate(t); bool success = (setup->DConfigDlg::exec() == QDialog::Accepted); delete setup; return success; } bool Setup::execMetadataFilters(QWidget* const parent, int tab) { QPointer setup = new Setup(parent); setup->showPage(MetadataPage); setup->setFaceType(Plain); DConfigDlgWdgItem* const cur = setup->currentPage(); if (!cur) return false; SetupMetadata* const widget = dynamic_cast(cur->widget()); if (!widget) return false; widget->setActiveMainTab(SetupMetadata::Display); widget->setActiveSubTab(tab); bool success = (setup->DConfigDlg::exec() == QDialog::Accepted); delete setup; return success; } void Setup::slotOkClicked() { if (!d->cameraPage->checkSettings()) { showPage(CameraPage); return; } qApp->setOverrideCursor(Qt::WaitCursor); d->cameraPage->applySettings(); d->databasePage->applySettings(); d->collectionsPage->applySettings(); d->albumViewPage->applySettings(); d->tooltipPage->applySettings(); d->metadataPage->applySettings(); d->templatePage->applySettings(); d->lighttablePage->applySettings(); d->editorPage->applySettings(); d->slideshowPage->applySettings(); d->imageQualitySorterPage->applySettings(); d->iccPage->applySettings(); d->miscPage->applySettings(); ApplicationSettings::instance()->emitSetupChanged(); ImportSettings::instance()->emitSetupChanged(); qApp->restoreOverrideCursor(); if (d->metadataPage->exifAutoRotateHasChanged()) { QString msg = i18n("The Exif auto-rotate thumbnails option has been changed.\n" "Do you want to rebuild all albums' items' thumbnails now?\n\n" "Note: thumbnail processing can take a while. You can start " "this job later from the \"Tools-Maintenance\" menu."); int result = QMessageBox::warning(this, qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::No); if (result != QMessageBox::Yes) { return; } new ThumbsGenerator(true, -1); } accept(); } void Setup::showPage(Setup::Page page) { DConfigDlgWdgItem* item = 0; if (page == LastPageUsed) { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Setup Dialog")); item = d->pageItem((Page)group.readEntry(QLatin1String("Setup Page"), (int)CollectionsPage)); } else { item = d->pageItem(page); } if (!item) { item = d->pageItem(CollectionsPage); } setCurrentPage(item); } Setup::Page Setup::activePageIndex() const { DConfigDlgWdgItem* const cur = currentPage(); if (cur == d->page_collections) { return CollectionsPage; } if (cur == d->page_albumView) { return AlbumViewPage; } if (cur == d->page_tooltip) { return ToolTipPage; } if (cur == d->page_metadata) { return MetadataPage; } if (cur == d->page_template) { return TemplatePage; } if (cur == d->page_lighttable) { return LightTablePage; } if (cur == d->page_editor) { return EditorPage; } if (cur == d->page_slideshow) { return SlideshowPage; } if (cur == d->page_imagequalitysorter) { return ImageQualityPage; } if (cur == d->page_icc) { return ICCPage; } if (cur == d->page_camera) { return CameraPage; } if (cur == d->page_misc) { return MiscellaneousPage; } return DatabasePage; } DConfigDlgWdgItem* Setup::Private::pageItem(Setup::Page page) const { switch (page) { case Setup::DatabasePage: return page_database; case Setup::CollectionsPage: return page_collections; case Setup::AlbumViewPage: return page_albumView; case Setup::ToolTipPage: return page_tooltip; case Setup::MetadataPage: return page_metadata; case Setup::TemplatePage: return page_template; case Setup::LightTablePage: return page_lighttable; case Setup::EditorPage: return page_editor; case Setup::SlideshowPage: return page_slideshow; case Setup::ImageQualityPage: return page_imagequalitysorter; case Setup::ICCPage: return page_icc; case Setup::CameraPage: return page_camera; case Setup::MiscellaneousPage: return page_misc; default: return 0; } } } // namespace Digikam diff --git a/core/utilities/setup/setupdatabase.cpp b/core/utilities/setup/setupdatabase.cpp index b518b1374a..f657d33b7b 100644 --- a/core/utilities/setup/setupdatabase.cpp +++ b/core/utilities/setup/setupdatabase.cpp @@ -1,323 +1,323 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-11-14 * Description : database setup tab * * Copyright (C) 2009-2010 by Holger Foerster * 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 "setupdatabase.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 "albummanager.h" #include "applicationsettings.h" #include "coredbschemaupdater.h" #include "databaseserverstarter.h" #include "dbengineparameters.h" #include "dbsettingswidget.h" #include "digikam_debug.h" #include "scancontroller.h" #include "setuputils.h" namespace Digikam { class Q_DECL_HIDDEN SetupDatabase::Private { public: explicit Private() : databaseWidget(0), updateBox(0), hashesButton(0), ignoreEdit(0), ignoreLabel(0) { } DatabaseSettingsWidget* databaseWidget; QGroupBox* updateBox; QPushButton* hashesButton; QLineEdit* ignoreEdit; QLabel* ignoreLabel; }; SetupDatabase::SetupDatabase(QWidget* const parent) : QScrollArea(parent), d(new Private) { QTabWidget* tab = new QTabWidget(viewport()); setWidget(tab); setWidgetResizable(true); // -------------------------------------------------------- QWidget* const settingsPanel = new QWidget(tab); QVBoxLayout* settingsLayout = new QVBoxLayout(settingsPanel); d->databaseWidget = new DatabaseSettingsWidget; settingsLayout->addWidget(d->databaseWidget); if (!CoreDbSchemaUpdater::isUniqueHashUpToDate()) { createUpdateBox(); settingsLayout->addStretch(10); settingsLayout->addWidget(d->updateBox); } tab->insertTab(DbSettings, settingsPanel, i18nc("@title:tab", "Database Settings")); // -------------------------------------------------------- QWidget* const ignorePanel = new QWidget(tab); QGridLayout* ignoreLayout = new QGridLayout(ignorePanel); QLabel* const ignoreInfoLabel = new QLabel( i18n("

Set the names of directories that you want to ignore " "from your photo collections. The names are case sensitive " "and should be separated by a semicolon.

" "

This is for example useful when you store your photos " "on a Synology NAS (Network Attached Storage). In every " "directory the system creates a subdirectory @eaDir to " "store thumbnails. To avoid digiKam inserting the original " "photo and its corresponding thumbnail twice, @eaDir is " "ignored by default.

" "

To re-include directories that are ignored by default " "prefix it with a minus, e.g. -@eaDir.

"), ignorePanel); ignoreInfoLabel->setWordWrap(true); QLabel* const logoLabel1 = new QLabel(ignorePanel); logoLabel1->setPixmap(QIcon::fromTheme(QLatin1String("folder")).pixmap(48)); d->ignoreLabel = new QLabel(ignorePanel); d->ignoreLabel->setText(i18n("Additional directories to ignore " "(Currently ignored directories):")); d->ignoreEdit = new QLineEdit(ignorePanel); d->ignoreEdit->setClearButtonEnabled(true); d->ignoreEdit->setPlaceholderText(i18n("Enter directories that you want to " "ignore from adding to your collections.")); ignoreInfoLabel->setBuddy(d->ignoreEdit); int row = 0; ignoreLayout->addWidget(ignoreInfoLabel, row, 0, 1, 2); row++; ignoreLayout->addWidget(logoLabel1, row, 0, 2, 1); ignoreLayout->addWidget(d->ignoreLabel, row, 1, 1, 1); row++; ignoreLayout->addWidget(d->ignoreEdit, row, 1, 1, 1); row++; ignoreLayout->setColumnStretch(1, 10); ignoreLayout->setRowStretch(row, 10); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); ignoreLayout->setContentsMargins(spacing, spacing, spacing, spacing); ignoreLayout->setSpacing(spacing); connect(d->ignoreLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowCurrentIgnoredDirectoriesSettings())); tab->insertTab(IgnoreDirs, ignorePanel, i18nc("@title:tab", "Ignored Directories")); // -------------------------------------------------------- readSettings(); adjustSize(); } SetupDatabase::~SetupDatabase() { delete d; } void SetupDatabase::applySettings() { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } QString ignoreDirectory; CoreDbAccess().db()->getUserIgnoreDirectoryFilterSettings(&ignoreDirectory); if (d->ignoreEdit->text() != ignoreDirectory) { CoreDbAccess().db()->setUserIgnoreDirectoryFilterSettings( cleanUserFilterString(d->ignoreEdit->text(), true, true)); ScanController::instance()->completeCollectionScanInBackground(false); } if (d->databaseWidget->getDbEngineParameters() == d->databaseWidget->orgDatabasePrm()) { qCDebug(DIGIKAM_GENERAL_LOG) << "No DB settings changes. Do nothing..."; return; } if (!d->databaseWidget->checkDatabaseSettings()) { qCDebug(DIGIKAM_GENERAL_LOG) << "DB settings check invalid. Do nothing..."; return; } switch (d->databaseWidget->databaseType()) { case DatabaseSettingsWidget::SQlite: { qCDebug(DIGIKAM_GENERAL_LOG) << "Switch to SQlite DB config..."; DbEngineParameters params = d->databaseWidget->getDbEngineParameters(); settings->setDbEngineParameters(params); settings->saveSettings(); AlbumManager::instance()->changeDatabase(params); break; } case DatabaseSettingsWidget::MysqlInternal: { qCDebug(DIGIKAM_GENERAL_LOG) << "Switch to Mysql Internal DB config..."; DbEngineParameters params = d->databaseWidget->getDbEngineParameters(); settings->setDbEngineParameters(params); settings->saveSettings(); AlbumManager::instance()->changeDatabase(params); break; } default: // DatabaseSettingsWidget::MysqlServer { qCDebug(DIGIKAM_GENERAL_LOG) << "Switch to Mysql server DB config..."; DbEngineParameters params = d->databaseWidget->getDbEngineParameters(); settings->setDbEngineParameters(params); settings->saveSettings(); AlbumManager::instance()->changeDatabase(params); break; } } } void SetupDatabase::readSettings() { ApplicationSettings* const settings = ApplicationSettings::instance(); if (!settings) { return; } QString ignoreDirectory; CoreDbAccess().db()->getUserIgnoreDirectoryFilterSettings(&ignoreDirectory); d->ignoreEdit->setText(ignoreDirectory); d->databaseWidget->setParametersFromSettings(settings); } void SetupDatabase::upgradeUniqueHashes() { int result = QMessageBox::warning(this, qApp->applicationName(), i18nc("@info", "

The process of updating the file hashes takes a few minutes.

" "

Please ensure that any important collections on removable media are connected. " "After the upgrade you cannot use your database with a digiKam version " "prior to 2.0.

" "

Do you want to begin the update?

"), QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) { ScanController::instance()->updateUniqueHash(); } } void SetupDatabase::createUpdateBox() { d->updateBox = new QGroupBox(i18nc("@title:group", "Updates")); QGridLayout* const updateLayout = new QGridLayout; d->hashesButton = new QPushButton(i18nc("@action:button", "Update File Hashes")); d->hashesButton->setWhatsThis(i18nc("@info:tooltip", "File hashes are used to identify identical files and to display thumbnails. " "A new, improved algorithm to create the hash is now used. " "The old algorithm, though, still works quite well, so it is recommended to " - "carry out this upgrade, but not required.
" + "carry out this upgrade, but not required.
" "After the upgrade you cannot use your database with a digiKam version " "prior to 2.0.
")); QPushButton* const infoHash = new QPushButton; infoHash->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); infoHash->setToolTip(i18nc("@info:tooltip", "Get information about Update File Hashes")); updateLayout->addWidget(d->hashesButton, 0, 0); updateLayout->addWidget(infoHash, 0, 1); updateLayout->setColumnStretch(2, 1); d->updateBox->setLayout(updateLayout); connect(d->hashesButton, SIGNAL(clicked()), this, SLOT(upgradeUniqueHashes())); connect(infoHash, SIGNAL(clicked()), this, SLOT(showHashInformation())); } void SetupDatabase::showHashInformation() { qApp->postEvent(d->hashesButton, new QHelpEvent(QEvent::WhatsThis, QPoint(0, 0), QCursor::pos())); } void SetupDatabase::slotShowCurrentIgnoredDirectoriesSettings() const { QStringList ignoreDirectoryList; CoreDbAccess().db()->getIgnoreDirectoryFilterSettings(&ignoreDirectoryList); QString text = i18n("

Directories starting with a dot are ignored by " "default.
%1

", ignoreDirectoryList.join(QLatin1Char(';'))); QWhatsThis::showText(d->ignoreLabel->mapToGlobal(QPoint(0, 0)), text, d->ignoreLabel); } } // namespace Digikam diff --git a/core/utilities/setup/setupicc.cpp b/core/utilities/setup/setupicc.cpp index d3d888a68f..c4016a0cac 100644 --- a/core/utilities/setup/setupicc.cpp +++ b/core/utilities/setup/setupicc.cpp @@ -1,966 +1,966 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-11-24 * Description : Color management setup tab. * * Copyright (C) 2005-2007 by F.J. Cruz * Copyright (C) 2005-2018 by Gilles Caulier * Copyright (C) 2009-2012 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "setupicc.h" // 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 "dlayoutbox.h" #include "squeezedcombobox.h" #include "digikam_debug.h" #include "applicationsettings.h" #include "iccprofileinfodlg.h" #include "iccprofilescombobox.h" #include "iccsettings.h" #include "iccsettingscontainer.h" #include "dactivelabel.h" #include "dfileselector.h" namespace Digikam { class Q_DECL_HIDDEN SetupICC::Private { public: explicit Private() : iccFolderLabel(0), enableColorManagement(0), defaultSRGBConvert(0), bpcAlgorithm(0), managedView(0), managedPreviews(0), defaultAskMismatch(0), defaultConvertMismatch(0), defaultAskMissing(0), defaultSRGBMissing(0), defaultWSMissing(0), defaultInputMissing(0), defaultAskRaw(0), defaultInputRaw(0), defaultGuessRaw(0), infoWorkProfiles(0), infoMonitorProfiles(0), infoInProfiles(0), infoProofProfiles(0), workspaceGB(0), mismatchGB(0), missingGB(0), rawGB(0), inputGB(0), viewGB(0), proofGB(0), iccFolderGB(0), advancedSettingsGB(0), defaultPathKU(0), renderingIntentKC(0), behaviorPanel(0), profilesPanel(0), advancedPanel(0), tab(0), dlgBtnBox(0), inProfilesKC(0), workProfilesKC(0), proofProfilesKC(0), monitorProfilesKC(0) { } QLabel* iccFolderLabel; QCheckBox* enableColorManagement; QCheckBox* defaultSRGBConvert; QCheckBox* bpcAlgorithm; QCheckBox* managedView; QCheckBox* managedPreviews; QRadioButton* defaultAskMismatch; QRadioButton* defaultConvertMismatch; QRadioButton* defaultAskMissing; QRadioButton* defaultSRGBMissing; QRadioButton* defaultWSMissing; QRadioButton* defaultInputMissing; QRadioButton* defaultAskRaw; QRadioButton* defaultInputRaw; QRadioButton* defaultGuessRaw; QPushButton* infoWorkProfiles; QPushButton* infoMonitorProfiles; QPushButton* infoInProfiles; QPushButton* infoProofProfiles; QGroupBox* workspaceGB; QGroupBox* mismatchGB; QGroupBox* missingGB; QGroupBox* rawGB; QGroupBox* inputGB; QGroupBox* viewGB; QGroupBox* proofGB; QGroupBox* iccFolderGB; QGroupBox* advancedSettingsGB; DFileSelector* defaultPathKU; IccRenderingIntentComboBox* renderingIntentKC; QWidget* behaviorPanel; QWidget* profilesPanel; QWidget* advancedPanel; QTabWidget* tab; QDialogButtonBox* dlgBtnBox; IccProfilesComboBox* inProfilesKC; IccProfilesComboBox* workProfilesKC; IccProfilesComboBox* proofProfilesKC; IccProfilesComboBox* monitorProfilesKC; }; SetupICC::SetupICC(QDialogButtonBox* const dlgBtnBox, QWidget* const parent) : QScrollArea(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->dlgBtnBox = dlgBtnBox; d->tab = new QTabWidget(viewport()); setWidget(d->tab); setWidgetResizable(true); d->behaviorPanel = new QWidget; QVBoxLayout* const mainLayout = new QVBoxLayout(d->behaviorPanel); // -------------------------------------------------------- QWidget* const colorPolicy = new QWidget; QGridLayout* const gridHeader = new QGridLayout(colorPolicy); d->enableColorManagement = new QCheckBox(colorPolicy); d->enableColorManagement->setText(i18n("Enable Color Management")); d->enableColorManagement->setWhatsThis(i18n("
  • Checked: Color Management is enabled
  • " "
  • Unchecked: Color Management is " "disabled
")); DActiveLabel* const lcmsLogoLabel = new DActiveLabel(QUrl(QLatin1String("http://www.littlecms.com")), QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-lcms.png")), colorPolicy); lcmsLogoLabel->setToolTip(i18n("Visit Little CMS project website")); gridHeader->addWidget(d->enableColorManagement, 0, 0, 1, 1); gridHeader->addWidget(lcmsLogoLabel, 0, 2, 1, 1); gridHeader->setColumnStretch(1, 10); gridHeader->setContentsMargins(spacing, spacing, spacing, spacing); gridHeader->setSpacing(0); // -------------------------------------------------------- d->workspaceGB = new QGroupBox(i18n("Working Color Space")); QHBoxLayout* const hboxWS = new QHBoxLayout(d->workspaceGB); QLabel* const workIcon = new QLabel; workIcon->setPixmap(QIcon::fromTheme(QLatin1String("input-tablet")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); d->workProfilesKC = new IccProfilesComboBox; d->workProfilesKC->setWhatsThis(i18n("

This is the color space all the images will be converted to when opened " "(if you choose to convert) and the profile that will be embedded when saving. " - "Good and safe choices are Adobe RGB (1998) and sRGB IEC61966-2.1")); + "Good and safe choices are Adobe RGB (1998) and sRGB IEC61966-2.1

")); d->infoWorkProfiles = new QPushButton; d->infoWorkProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoWorkProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected workspace profile.

")); hboxWS->addWidget(workIcon); hboxWS->addWidget(d->workProfilesKC, 10); hboxWS->addWidget(d->infoWorkProfiles); // -------------------------------------------------------- d->mismatchGB = new QGroupBox;//(i18n("Behavior on Profile Mismatch"); QVBoxLayout* const vlayMismatch = new QVBoxLayout(d->mismatchGB); QLabel* const behaviorIcon = new QLabel; behaviorIcon->setPixmap(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(32)); QLabel* const behaviorLabel = new QLabel(i18n("When the profile of an image does not match the working color space")); behaviorLabel->setWordWrap(true); QHBoxLayout* const hboxBL = new QHBoxLayout; hboxBL->addWidget(behaviorIcon); hboxBL->addWidget(behaviorLabel, 10); d->defaultAskMismatch = new QRadioButton(d->mismatchGB); d->defaultAskMismatch->setText(i18n("Ask when opening the image")); d->defaultAskMismatch->setWhatsThis(i18n("

If an image has an embedded color profile not matching the working " "space profile, digiKam will ask if you want to convert to the working space, " "keep the embedded profile or discard the embedded profile and assign " "a different one.

")); d->defaultConvertMismatch = new QRadioButton(d->mismatchGB); d->defaultConvertMismatch->setText(i18n("Convert the image to the working color space")); d->defaultConvertMismatch->setWhatsThis(i18n("

If an image has an embedded color profile not matching the working " "space profile, digiKam will convert the image's color information to " "the working color space. This changes the pixel data, but not the " "appearance of the image.

")); vlayMismatch->addLayout(hboxBL); vlayMismatch->addWidget(d->defaultAskMismatch); vlayMismatch->addWidget(d->defaultConvertMismatch); // -------------------------------------------------------- d->missingGB = new QGroupBox;//(i18n("Missing Profile Behavior")); QVBoxLayout* const vlayMissing = new QVBoxLayout(d->missingGB); QLabel* const missingIcon = new QLabel; missingIcon->setPixmap(QIcon::fromTheme(QLatin1String("paint-unknown")).pixmap(32)); QLabel* const missingLabel = new QLabel(i18n("When an image has no color profile information")); missingLabel->setWordWrap(true); QHBoxLayout* const hboxMP = new QHBoxLayout; hboxMP->addWidget(missingIcon); hboxMP->addWidget(missingLabel, 10); d->defaultAskMissing = new QRadioButton(i18n("Ask when opening the image")); d->defaultAskMissing->setWhatsThis(i18n("

If an image has no embedded color profile, " "digiKam will ask which color space shall be used to interpret the image " "and to which color space it shall be transformed for editing.

")); d->defaultSRGBMissing = new QRadioButton(i18n("Assume it is using the sRGB color space (Internet standard)")); /** * @todo d->defaultSRGBMissing->setWhatsThis( i18n("

")); */ d->defaultSRGBConvert = new QCheckBox(i18n("and convert it to the working color space")); /** * @todo d->defaultSRGBConvert->setWhatsThis( i18n("

")); */ d->defaultSRGBConvert->setChecked(true); QGridLayout* const gridRgb = new QGridLayout; gridRgb->addWidget(d->defaultSRGBMissing, 0, 0, 1, 2); gridRgb->addWidget(d->defaultSRGBConvert, 1, 1); gridRgb->setColumnMinimumWidth(0, 10); d->defaultWSMissing = new QRadioButton(i18n("Assume it is using the working color space")); /** * @todo d->defaultWSMissing->setWhatsThis( i18n("

")); */ d->defaultInputMissing = new QRadioButton(i18n("Convert it from default input color space to working space")); /** * @todo d->defaultWSMissing->setWhatsThis( i18n("

")); */ vlayMissing->addLayout(hboxMP); vlayMissing->addWidget(d->defaultAskMissing); vlayMissing->addLayout(gridRgb); vlayMissing->addWidget(d->defaultWSMissing); vlayMissing->addWidget(d->defaultInputMissing); // -------------------------------------------------------- d->rawGB = new QGroupBox;//(i18n("Raw File Behavior")); QVBoxLayout* const vlayRaw = new QVBoxLayout(d->rawGB); QLabel* const rawBehaviorIcon = new QLabel; rawBehaviorIcon->setPixmap(QIcon::fromTheme(QLatin1String("image-x-adobe-dng")).pixmap(32)); QLabel* const rawBehaviorLabel = new QLabel(i18n("When loading a RAW file with uncalibrated colors")); rawBehaviorLabel->setWordWrap(true); QHBoxLayout* const hboxRF = new QHBoxLayout; hboxRF->addWidget(rawBehaviorIcon); hboxRF->addWidget(rawBehaviorLabel, 10); d->defaultAskRaw = new QRadioButton(i18n("Ask for the input profile")); /** * @todo d->defaultAskRaw->setWhatsThis( i18n("

")); */ d->defaultGuessRaw = new QRadioButton(i18n("Automatic color correction")); /** * @todo d->defaultGuessRaw->setWhatsThis( i18n("

")); */ d->defaultInputRaw = new QRadioButton(i18n("Convert it from the default input profile")); /** * @todo d->defaultSRGBMissing->setWhatsThis( i18n("

")); */ d->defaultGuessRaw->setChecked(true); vlayRaw->addLayout(hboxRF); vlayRaw->addWidget(d->defaultAskRaw); vlayRaw->addWidget(d->defaultGuessRaw); vlayRaw->addWidget(d->defaultInputRaw); mainLayout->addWidget(colorPolicy); mainLayout->addWidget(d->workspaceGB); mainLayout->addWidget(d->mismatchGB); mainLayout->addWidget(d->missingGB); mainLayout->addWidget(d->rawGB); mainLayout->addStretch(); // -------------------------------------------------------- d->profilesPanel = new QWidget; QVBoxLayout* const vboxDisplay = new QVBoxLayout(d->profilesPanel); d->viewGB = new QGroupBox(i18n("Color Managed View")); QGridLayout* const gridView = new QGridLayout(d->viewGB); QLabel* const monitorIcon = new QLabel; monitorIcon->setPixmap(QIcon::fromTheme(QLatin1String("video-display")).pixmap(32)); QLabel* const monitorProfiles = new QLabel(i18n("Monitor profile:")); d->monitorProfilesKC = new IccProfilesComboBox; monitorProfiles->setBuddy(d->monitorProfilesKC); d->monitorProfilesKC->setWhatsThis(i18n("

Select the color profile for your monitor here.

")); d->infoMonitorProfiles = new QPushButton; d->infoMonitorProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoMonitorProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected monitor profile.

")); d->managedView = new QCheckBox; d->managedView->setText(i18n("Use color managed view in editor")); d->managedView->setWhatsThis(i18n("

Turn on this option if " "you want to use your Monitor Color Profile to show your pictures in " "the Image Editor window with a color correction adapted to your monitor. " "You can at any time toggle this option from the Editor window. " "Warning: This can slow down rendering of the image, depending on the speed of your computer.

")); d->managedPreviews = new QCheckBox; d->managedPreviews->setText(i18n("Use color managed view for previews and thumbnails")); /** * @todo d->managedPreview->setWhatsThis( i18n("") ); */ gridView->addWidget(monitorIcon, 0, 0); gridView->addWidget(monitorProfiles, 0, 1, 1, 2); gridView->addWidget(d->monitorProfilesKC, 1, 0, 1, 2); gridView->addWidget(d->infoMonitorProfiles, 1, 2); gridView->addWidget(d->managedView, 2, 0, 1, 3); gridView->addWidget(d->managedPreviews, 3, 0, 1, 3); gridView->setColumnStretch(1, 10); // -------------------------------------------------------- d->inputGB = new QGroupBox(i18n("Camera and Scanner")); QGridLayout* const gridIP = new QGridLayout(d->inputGB); QLabel* const inputIcon = new QLabel; inputIcon->setPixmap(QIcon::fromTheme(QLatin1String("input-tablet")).pixmap(32)); QLabel* const inputLabel = new QLabel(i18n("Default input color profile:")); d->inProfilesKC = new IccProfilesComboBox; d->inProfilesKC->setWhatsThis(i18n("

This is the default color profile for your input device " "(that is your camera - or your scanner). A camera input profile " "is recommended for correct conversion of RAW images in 16bit. " "Some of the options about loading behavior above refer to this profile.

")); d->infoInProfiles = new QPushButton; d->infoInProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoInProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected input profile.

")); gridIP->addWidget(inputIcon, 0, 0); gridIP->addWidget(inputLabel, 0, 1, 1, 2); gridIP->addWidget(d->inProfilesKC, 1, 0, 1, 2); gridIP->addWidget(d->infoInProfiles, 1, 2); gridIP->setColumnStretch(1, 10); // -------------------------------------------------------- d->proofGB = new QGroupBox(i18n("Printing and Proofing")); QGridLayout* const gridProof = new QGridLayout(d->proofGB); QLabel* const proofIcon = new QLabel; proofIcon->setPixmap(QIcon::fromTheme(QLatin1String("printer")).pixmap(32)); QLabel* const proofLabel = new QLabel(i18n("Output device profile:")); d->proofProfilesKC = new IccProfilesComboBox; proofLabel->setBuddy(d->proofProfilesKC); d->proofProfilesKC->setWhatsThis(i18n("

Select the profile for your output device " "(usually, your printer). This profile will be used to do a soft proof, so you will " "be able to preview how an image will be rendered via an output device.

")); d->infoProofProfiles = new QPushButton; d->infoProofProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoProofProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected proofing profile.

")); gridProof->addWidget(proofIcon, 0, 0); gridProof->addWidget(proofLabel, 0, 1, 1, 2); gridProof->addWidget(d->proofProfilesKC, 1, 0, 1, 2); gridProof->addWidget(d->infoProofProfiles, 1, 2); gridProof->setColumnStretch(1, 10); // -------------------------------------------------------- d->iccFolderGB = new QGroupBox(i18n("Color Profiles Repository")); QGridLayout* const gridIccFolder = new QGridLayout(d->iccFolderGB); QLabel* const iccFolderIcon = new QLabel; iccFolderIcon->setPixmap(QIcon::fromTheme(QLatin1String("folder-downloads")).pixmap(32)); d->iccFolderLabel = new QLabel(i18n("digiKam looks for ICC profiles in a number of default locations. " "You can specify an additional folder:")); d->iccFolderLabel->setWordWrap(true); d->defaultPathKU = new DFileSelector; d->iccFolderLabel->setBuddy(d->defaultPathKU); d->defaultPathKU->lineEdit()->setReadOnly(true); d->defaultPathKU->setFileDlgMode(QFileDialog::Directory); d->defaultPathKU->setWhatsThis(i18n("

digiKam searches ICC profiles in default system folders " "and ships itself a few selected profiles. " "Store all your additional color profiles in the directory set here.

")); gridIccFolder->addWidget(iccFolderIcon, 0, 0); gridIccFolder->addWidget(d->iccFolderLabel, 0, 1); gridIccFolder->addWidget(d->defaultPathKU, 1, 0, 1, 2); gridIccFolder->setColumnStretch(1, 10); vboxDisplay->addWidget(d->viewGB); vboxDisplay->addWidget(d->inputGB); vboxDisplay->addWidget(d->proofGB); vboxDisplay->addWidget(d->iccFolderGB); vboxDisplay->addStretch(1); // -------------------------------------------------------- d->advancedPanel = new QWidget; QVBoxLayout* const vboxAdvanced = new QVBoxLayout(d->advancedPanel); d->advancedSettingsGB = new QGroupBox(i18n("Advanced Settings")); QGridLayout* const gridAdvanced = new QGridLayout(d->advancedSettingsGB); d->bpcAlgorithm = new QCheckBox(d->advancedSettingsGB); d->bpcAlgorithm->setText(i18n("Use black point compensation")); d->bpcAlgorithm->setWhatsThis(i18n("

Black Point Compensation is a way to make " "adjustments between the maximum " "black levels of digital files and the black capabilities of various " "digital devices.

")); QLabel* const lablel = new QLabel(d->advancedSettingsGB); lablel->setText(i18n("Rendering Intents:")); d->renderingIntentKC = new IccRenderingIntentComboBox(d->advancedSettingsGB); gridAdvanced->addWidget(d->bpcAlgorithm, 0, 0, 1, 2); gridAdvanced->addWidget(lablel, 1, 0, 1, 1); gridAdvanced->addWidget(d->renderingIntentKC, 1, 1, 1, 1); gridAdvanced->setContentsMargins(spacing, spacing, spacing, spacing); gridAdvanced->setSpacing(0); vboxAdvanced->addWidget(d->advancedSettingsGB); vboxAdvanced->addStretch(1); // -------------------------------------------------------- d->tab->addTab(d->behaviorPanel, i18n("Behavior")); d->tab->addTab(d->profilesPanel, i18n("Profiles")); d->tab->addTab(d->advancedPanel, i18n("Advanced")); // -------------------------------------------------------- connect(d->enableColorManagement, SIGNAL(toggled(bool)), this, SLOT(slotToggledEnabled())); connect(d->infoProofProfiles, SIGNAL(clicked()), this, SLOT(slotClickedProof())); connect(d->infoInProfiles, SIGNAL(clicked()), this, SLOT(slotClickedIn())); connect(d->infoMonitorProfiles, SIGNAL(clicked()), this, SLOT(slotClickedMonitor())); connect(d->infoWorkProfiles, SIGNAL(clicked()), this, SLOT(slotClickedWork())); connect(d->defaultPathKU, SIGNAL(signalUrlSelected(QUrl)), this, SLOT(slotUrlChanged())); connect(d->defaultPathKU->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotUrlTextChanged())); connect(d->iccFolderLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowDefaultSearchPaths())); connect(d->defaultAskMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultSRGBMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultWSMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultInputMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); // -------------------------------------------------------- adjustSize(); readSettings(); slotToggledEnabled(); // -------------------------------------------------------- } SetupICC::~SetupICC() { delete d; } void SetupICC::applySettings() { ICCSettingsContainer settings; settings.enableCM = d->enableColorManagement->isChecked(); if (d->defaultAskMismatch->isChecked()) { settings.defaultMismatchBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultConvertMismatch->isChecked()) { settings.defaultMismatchBehavior = ICCSettingsContainer::EmbeddedToWorkspace; } if (d->defaultAskMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultSRGBMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::UseSRGB; if (d->defaultSRGBConvert->isChecked()) { settings.defaultMissingProfileBehavior |= ICCSettingsContainer::ConvertToWorkspace; } else { settings.defaultMissingProfileBehavior |= ICCSettingsContainer::KeepProfile; } } else if (d->defaultWSMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::UseWorkspace | ICCSettingsContainer::KeepProfile; } else if (d->defaultInputMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::InputToWorkspace; } if (d->defaultAskRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultInputRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::InputToWorkspace; } else if (d->defaultGuessRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::AutomaticColors | ICCSettingsContainer::ConvertToWorkspace; } settings.iccFolder = d->defaultPathKU->fileDlgPath(); settings.useBPC = d->bpcAlgorithm->isChecked(); settings.renderingIntent = d->renderingIntentKC->intent(); settings.useManagedView = d->managedView->isChecked(); settings.useManagedPreviews = d->managedPreviews->isChecked(); settings.defaultInputProfile = d->inProfilesKC->currentProfile().filePath(); settings.workspaceProfile = d->workProfilesKC->currentProfile().filePath(); settings.defaultProofProfile = d->proofProfilesKC->currentProfile().filePath(); if (!IccSettings::instance()->monitorProfileFromSystem()) { settings.monitorProfile = d->monitorProfilesKC->currentProfile().filePath(); } IccSettings::instance()->setSettings(settings); } void SetupICC::readSettings(bool restore) { ICCSettingsContainer settings = IccSettings::instance()->settings(); if (!restore) { d->enableColorManagement->setChecked(settings.enableCM); } d->bpcAlgorithm->setChecked(settings.useBPC); d->renderingIntentKC->setIntent(settings.renderingIntent); d->managedView->setChecked(settings.useManagedView); d->managedPreviews->setChecked(settings.useManagedPreviews); if (settings.defaultMismatchBehavior & ICCSettingsContainer::AskUser) { d->defaultAskMismatch->setChecked(true); } else if (settings.defaultMismatchBehavior & ICCSettingsContainer::ConvertToWorkspace) { d->defaultConvertMismatch->setChecked(true); } if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::AskUser) { d->defaultAskMissing->setChecked(true); } else { if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseSRGB) { d->defaultSRGBMissing->setChecked(true); d->defaultSRGBConvert->setChecked(settings.defaultMissingProfileBehavior & ICCSettingsContainer::ConvertToWorkspace); } else if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseWorkspace) { d->defaultWSMissing->setChecked(true); } else if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseDefaultInputProfile) { d->defaultInputMissing->setChecked(true); } } if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AskUser) { d->defaultAskRaw->setChecked(true); } else if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::UseDefaultInputProfile) { d->defaultInputRaw->setChecked(true); } else if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors) { d->defaultGuessRaw->setChecked(true); } d->defaultPathKU->setFileDlgPath(settings.iccFolder); fillCombos(false); d->workProfilesKC->setCurrentProfile(IccProfile(settings.workspaceProfile)); d->inProfilesKC->setCurrentProfile(IccProfile(settings.defaultInputProfile)); d->proofProfilesKC->setCurrentProfile(IccProfile(settings.defaultProofProfile)); if (IccSettings::instance()->monitorProfileFromSystem()) { d->monitorProfilesKC->clear(); d->monitorProfilesKC->setNoProfileIfEmpty(i18n("Monitor Profile From System Settings")); } else { d->monitorProfilesKC->setCurrentProfile(IccProfile(settings.monitorProfile)); } } void SetupICC::slotUrlChanged() { IccSettings::instance()->setIccPath(d->defaultPathKU->fileDlgPath()); fillCombos(true); } void SetupICC::slotUrlTextChanged() { d->defaultPathKU->setFileDlgPath(d->defaultPathKU->fileDlgPath()); } void SetupICC::fillCombos(bool report) { if (!d->enableColorManagement->isChecked()) { return; } QList profiles = IccSettings::instance()->allProfiles(); if (profiles.isEmpty()) { if (report) { QString message = i18n("No ICC profiles files found."); QMessageBox::information(this, qApp->applicationName(), message); } qCDebug(DIGIKAM_GENERAL_LOG) << "No ICC profile files found!!!"; d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } d->workProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->workspaceProfiles()); d->monitorProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->displayProfiles()); d->inProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->inputProfiles()); d->proofProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->outputProfiles()); d->workProfilesKC->setNoProfileIfEmpty(i18n("No Profile Available")); d->monitorProfilesKC->setNoProfileIfEmpty(i18n("No Display Profile Available")); d->inProfilesKC->setNoProfileIfEmpty(i18n("No Input Profile Available")); d->proofProfilesKC->setNoProfileIfEmpty(i18n("No Output Profile Available")); if (d->monitorProfilesKC->count() == 0) { d->managedView->setEnabled(false); d->managedPreviews->setEnabled(false); } else { d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); d->managedPreviews->setEnabled(true); } if (d->workProfilesKC->count() == 0) { // If there is no workspace ICC profiles available, // the CM is broken and cannot be used. d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void SetupICC::setWidgetsEnabled(bool enabled) { d->workspaceGB->setEnabled(enabled); d->mismatchGB->setEnabled(enabled); d->missingGB->setEnabled(enabled); d->rawGB->setEnabled(enabled); d->tab->setTabEnabled(1, enabled); d->tab->setTabEnabled(2, enabled); //d->profilesPanel->setEnabled(enabled); //d->advancedPanel->setEnabled(enabled); } void SetupICC::slotToggledEnabled() { bool enabled = d->enableColorManagement->isChecked(); setWidgetsEnabled(enabled); if (enabled) { readSettings(true); } else { d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); } } void SetupICC::slotClickedWork() { IccProfile profile = d->workProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedIn() { IccProfile profile = d->inProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedMonitor() { IccProfile profile; if (IccSettings::instance()->monitorProfileFromSystem()) { profile = IccSettings::instance()->monitorProfile(); } else { profile = d->monitorProfilesKC->currentProfile(); } if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedProof() { IccProfile profile = d->proofProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::profileInfo(const IccProfile& profile) { if (profile.isNull()) { QMessageBox::critical(this, i18n("Profile Error"), i18n("No profile is selected.")); return; } ICCProfileInfoDlg infoDlg(this, profile.filePath(), profile); infoDlg.exec(); } void SetupICC::slotMissingToggled(bool on) { if (!on) { return; } d->defaultSRGBConvert->setEnabled(d->defaultSRGBMissing->isChecked()); } void SetupICC::slotShowDefaultSearchPaths() { QStringList defaultSearchPaths = IccProfile::defaultSearchPaths(); QString existingPaths; if (defaultSearchPaths.isEmpty()) { existingPaths = i18nc("none of the paths", "none"); } else { existingPaths = defaultSearchPaths.join(QLatin1String("
  • ")); } #ifdef Q_OS_WIN QString text = i18n("On Windows, the default search paths include " "
      " "
    • %1/Windows/Spool/Drivers/Color/
    • " // For Win2K and WinXP "
    • %2/Windows/Color/
    • " // For Win98 and WinMe "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %3
    • " "
    ", QDir::rootPath(), QDir::rootPath(), existingPaths); #elif defined (Q_OS_OSX) QString text = i18n("On Mac OS X, the default search paths include " "
      " "
    • /System/Library/ColorSync/Profiles
    • " "
    • /Library/ColorSync/Profiles
    • " "
    • ~/Library/ColorSync/Profiles
    • " "
    • /opt/local/share/color/icc
    • " "
    • /opt/digikam/share/color/icc
    • " "
    • ~/.local/share/color/icc/
    • " "
    • ~/.local/share/icc/
    • " "
    • ~/.color/icc/
    • " "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %1
    • " "
    ", existingPaths); #else // Linux QString text = i18n("On Linux, the default search paths include " "
      " "
    • /usr/share/color/icc
    • " "
    • /usr/local/share/color/icc
    • " "
    • ~/.local/share/color/icc/
    • " "
    • ~/.local/share/icc/
    • " "
    • ~/.color/icc/
    • " "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %1
    • " "
    ", existingPaths); #endif QWhatsThis::showText(d->iccFolderLabel->mapToGlobal(QPoint(0, 0)), text, d->iccFolderLabel); } bool SetupICC::iccRepositoryIsValid() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Color Management")); // If color management is disable, no need to check anymore. if (!group.readEntry(QLatin1String("EnableCM"), false)) { return true; } // Can at least RawEngine profiles be opened? if (IccProfile::sRGB().open()) { return true; } // To be valid, the ICC profiles repository must exist and be readable. QString extraPath = group.readEntry(QLatin1String("DefaultPath"), QString()); QFileInfo info(extraPath); if (info.isDir() && info.exists() && info.isReadable()) { return true; } QStringList paths = IccProfile::defaultSearchPaths(); return !paths.isEmpty(); } } // namespace Digikam diff --git a/core/utilities/slideshow/slidehelp.cpp b/core/utilities/slideshow/slidehelp.cpp index b9fc8dbc3e..9a0d7f5174 100644 --- a/core/utilities/slideshow/slidehelp.cpp +++ b/core/utilities/slideshow/slidehelp.cpp @@ -1,104 +1,104 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2014-10-05 * Description : slideshow help dialog * * Copyright (C) 2014-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 "slidehelp.h" // Qt includes #include #include #include #include // KDE includes #include namespace Digikam { SlideHelp::SlideHelp() : QDialog(0) { setWindowTitle(i18n("Slideshow Usage")); QDialogButtonBox* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this); buttons->button(QDialogButtonBox::Ok)->setDefault(true); // ------------------------------------------------------------------------------------------------------------------- QLabel* const label = new QLabel(this); label->setText(i18n("" "" "" "" "" - "" "" - "" "" + "" "" + "" "" "" "" - "" "" + "" "" "" "" - "" "" - "" "" - "" "" - "" "" + "" "" + "" "" + "" "" + "" "" "" "" - "" "" + "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
    " "

    Item Access

    " "
    Previous Item:Up key
    PgUp key
    Left key
    PgUp key
    Left key
    Mouse wheel up
    Left mouse button
    Left mouse button
    Next Item:Down key
    PgDown key
    Right key
    Mouse wheel down
    Right mouse button
    PgDown key
    Right key
    Mouse wheel down
    Right mouse button
    Pause/Start:Space key
    Quit:Esc key
    Quit:Esc key
    " "

    Item Properties

    " "
    Change Tags:Use Tags keyboard shortcuts
    Change Rating:Use Rating keyboard shortcuts
    Change Color Label:Use Color label keyboard shortcuts
    Change Pick Label:Use Pick label keyboard shortcuts
    " "

    Others

    " "
    Show this help:F1 key
    " "
    ")); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(label); vbx->addWidget(buttons); setLayout(vbx); // --------------------------------------------------------------------------------------------------------------------- connect(buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept())); adjustSize(); } SlideHelp::~SlideHelp() { } } // namespace Digikam