diff --git a/AndroidRemoteControl/RemoteInterface.cpp b/AndroidRemoteControl/RemoteInterface.cpp
index 8a9952a5..997d1fcb 100644
--- a/AndroidRemoteControl/RemoteInterface.cpp
+++ b/AndroidRemoteControl/RemoteInterface.cpp
@@ -1,292 +1,292 @@
/* Copyright (C) 2014 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "RemoteInterface.h"
#include "Action.h"
#include "Client.h"
#include "ImageDetails.h"
#include "ImageStore.h"
#include "RemoteCommand.h"
#include "ScreenInfo.h"
#include "Settings.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace RemoteControl;
RemoteInterface::RemoteInterface()
: m_categories(new CategoryModel(this)), m_categoryItems(new ThumbnailModel(this)), m_thumbnailModel(new ThumbnailModel(this)),
m_discoveryModel(new DiscoveryModel(this))
{
m_connection = new Client;
connect(m_connection, SIGNAL(gotCommand(RemoteCommand)), this, SLOT(handleCommand(RemoteCommand)));
connect(m_connection, &Client::gotConnected,this, &RemoteInterface::connectionChanged);
connect(m_connection, &Client::gotConnected, this, &RemoteInterface::requestInitialData);
connect(m_connection, &Client::disconnected, this, &RemoteInterface::gotDisconnected);
connect(m_connection, &Client::disconnected, this, &RemoteInterface::connectionChanged);
qRegisterMetaType("RemoteControl::CategoryModel*");
qRegisterMetaType("ThumbnailModel*");
qRegisterMetaType("DiscoveryModel*");
QTimer::singleShot(1000, this, SLOT(pushAwayFromStartupState()));
}
void RemoteInterface::setCurrentPage(Page page)
{
if (m_currentPage != page) {
m_currentPage = page;
emit currentPageChanged();
}
}
void RemoteInterface::setListCategoryValues(const QStringList& values)
{
if (m_listCategoryValues != values) {
m_listCategoryValues = values;
emit listCategoryValuesChanged();
}
}
void RemoteInterface::requestHomePageImages()
{
m_connection->sendCommand(StaticImageRequest(ScreenInfo::instance().overviewIconSize()));
}
void RemoteInterface::gotDisconnected()
{
setCurrentPage(Page::UnconnectedPage);
}
void RemoteInterface::setHomePageImages(const StaticImageResult& command)
{
m_homeImage = command.homeIcon;
emit homeImageChanged();
m_kphotoalbumImage = command.kphotoalbumIcon;
emit kphotoalbumImageChange();
m_discoveryImage = command.discoverIcon;
emit discoveryImageChanged();
}
RemoteInterface& RemoteInterface::instance()
{
static RemoteInterface interface;
return interface;
}
bool RemoteInterface::isConnected() const
{
return m_connection->isConnected();
}
void RemoteInterface::sendCommand(const RemoteCommand& command)
{
m_connection->sendCommand(command);
}
QString RemoteInterface::currentCategory() const
{
return m_search.currentCategory();
}
QImage RemoteInterface::discoveryImage() const
{
return m_discoveryImage;
}
void RemoteInterface::setActiveThumbnailModel(RemoteInterface::ModelType type)
{
ThumbnailModel* newModel = (type == ModelType::Thumbnail ? m_thumbnailModel : m_discoveryModel);
if (newModel != m_activeThumbnailModel) {
m_activeThumbnailModel = newModel;
activeThumbnailModelChanged();
}
m_activeThumbnailModel->setImages({});
}
void RemoteInterface::goHome()
{
requestInitialData();
}
void RemoteInterface::goBack()
{
if(m_history.canGoBack())
m_history.goBackward();
else
qApp->quit();
}
void RemoteInterface::goForward()
{
if (m_history.canGoForward())
m_history.goForward();
}
void RemoteInterface::selectCategory(const QString& category, int type)
{
m_search.addCategory(category);
m_history.push(std::unique_ptr(new ShowCategoryValueAction(m_search, static_cast(type))));
}
void RemoteInterface::selectCategoryValue(const QString& value)
{
m_search.addValue(value);
m_history.push(std::unique_ptr(new ShowThumbnailsAction(m_search)));
}
void RemoteInterface::showThumbnails()
{
m_history.push(std::unique_ptr(new ShowThumbnailsAction(m_search)));
}
void RemoteInterface::showImage(int imageId)
{
m_history.push(std::unique_ptr(new ShowImagesAction(imageId, m_search)));
}
void RemoteInterface::requestDetails(int imageId)
{
m_connection->sendCommand(ImageDetailsRequest(imageId));
}
void RemoteInterface::activateSearch(const QString& search)
{
QStringList list = search.split(";;;");
QString category = list[0];
QString item = list[1];
SearchInfo result;
result.addCategory(category);
result.addValue(item);
m_history.push(std::unique_ptr(new ShowThumbnailsAction(result)));
}
void RemoteInterface::doDiscovery()
{
m_history.push(std::unique_ptr(new DiscoverAction(m_search, m_discoveryModel)));
}
void RemoteInterface::showOverviewPage()
{
m_history.push(std::unique_ptr(new ShowOverviewAction(m_search)));
}
void RemoteInterface::setToken(int imageId, const QString &token)
{
sendCommand(ToggleTokenRequest(imageId, token, ToggleTokenRequest::On));
}
void RemoteInterface::removeToken(int imageId, const QString &token)
{
sendCommand(ToggleTokenRequest(imageId, token, ToggleTokenRequest::Off));
}
void RemoteInterface::rerequestOverviewPageData()
{
requestHomePageImages();
m_history.rerunTopItem();
}
void RemoteInterface::pushAwayFromStartupState()
{
// Avoid that the "not connected page" show for a few milliseconds while the connection is being set up.
if (!isConnected() && m_currentPage == Types::Startup)
setCurrentPage(Types::UnconnectedPage);
}
void RemoteInterface::setCurrentView(int imageId)
{
emit jumpToImage(m_activeThumbnailModel->indexOf(imageId));
}
QString RemoteInterface::networkAddress() const
{
QStringList result;
for (const QHostAddress& address : QNetworkInterface::allAddresses()) {
if (address.isLoopback() || address.toIPv4Address() == 0)
continue;
result.append(address.toString());
}
return result.join(QStringLiteral(", "));
}
QStringList RemoteInterface::tokens() const
{
- // FIXME: in KPA the tokens category is now retreived using categoryForSpecial
+ // FIXME: in KPA the tokens category is now retrieved using categoryForSpecial
return ImageDetails::instance().itemsOfCategory(QStringLiteral("Tokens"));
}
void RemoteInterface::requestInitialData()
{
requestHomePageImages();
m_history.push(std::unique_ptr(new ShowOverviewAction({})));
}
void RemoteInterface::handleCommand(const RemoteCommand& command)
{
if (command.commandType() == CommandType::ThumbnailResult)
updateImage(static_cast(command));
else if (command.commandType() == CommandType::CategoryListResult)
updateCategoryList(static_cast(command));
else if (command.commandType() == CommandType::SearchResult)
gotSearchResult(static_cast(command));
else if (command.commandType() == CommandType::TimeCommand)
; // Used for debugging, it will print time stamp when decoded
else if (command.commandType() == CommandType::ImageDetailsResult) {
ImageDetails::instance().setData(static_cast(command));
emit tokensChanged();
}
else if (command.commandType() == CommandType::CategoryItemsResult)
setListCategoryValues(static_cast(command).items);
else if (command.commandType() == CommandType::StaticImageResult)
setHomePageImages(static_cast(command));
else
qFatal("Unhandled command");
}
void RemoteInterface::updateImage(const ThumbnailResult& command)
{
ImageStore::instance().updateImage(command.imageId, command.image, command.label, command.type);
}
void RemoteInterface::updateCategoryList(const CategoryListResult& command)
{
ScreenInfo::instance().setCategoryCount(command.categories.count());
m_categories->setCategories(command.categories);
}
void RemoteInterface::gotSearchResult(const SearchResult& result)
{
if (result.type == SearchType::Images) {
m_activeThumbnailModel->setImages(result.result);
}
else if (result.type == SearchType::CategoryItems) {
m_categoryItems->setImages(result.result);
}
}
diff --git a/AnnotationDialog/Dialog.cpp b/AnnotationDialog/Dialog.cpp
index e6210447..6a837606 100644
--- a/AnnotationDialog/Dialog.cpp
+++ b/AnnotationDialog/Dialog.cpp
@@ -1,1749 +1,1749 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "Dialog.h"
#include "DescriptionEdit.h"
#include "enums.h"
#include "ImagePreviewWidget.h"
#include "DateEdit.h"
#include "ListSelect.h"
#include "Logging.h"
#include "ResizableFrame.h"
#include "ShortCutManager.h"
#include "ShowSelectionOnlyManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_KGEOMAP
#include
" );
KMessageBox::information( nullptr, txt, i18n("How to Use the Export File"), QString::fromLatin1("export_how_to_use_the_export_file") );
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/ImportExport/Import.cpp b/ImportExport/Import.cpp
index 2d56433c..8403dae4 100644
--- a/ImportExport/Import.cpp
+++ b/ImportExport/Import.cpp
@@ -1,125 +1,124 @@
/* Copyright (C) 2003-2010 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "Import.h"
#include
#include
-#include
#include
#include
#include
#include
#include
#include "ImportHandler.h"
#include "KimFileReader.h"
#include "ImportDialog.h"
using namespace ImportExport;
void Import::imageImport()
{
QUrl url = QFileDialog::getOpenFileUrl(
nullptr, /*parent*/
i18n("KPhotoAlbum Export Files" ), /*caption*/
QUrl(), /* directory */
i18n("KPhotoAlbum import files") +
QString::fromLatin1( "(*.kim)" ) /*filter*/
);
if ( url.isEmpty() )
return;
imageImport( url );
// This instance will delete itself when done.
}
void Import::imageImport( const QUrl &url )
{
Import* import = new Import;
import->m_kimFileUrl = url;
if ( !url.isLocalFile() )
import->downloadUrl(url);
else
import->exec(url.path());
// This instance will delete itself when done.
}
ImportExport::Import::Import()
:m_tmp(nullptr)
{
}
void ImportExport::Import::downloadUrl( const QUrl &url )
{
m_tmp = new QTemporaryFile;
m_tmp->setFileTemplate(QString::fromLatin1("XXXXXX.kim"));
if ( !m_tmp->open() ) {
KMessageBox::error( MainWindow::Window::theMainWindow(), i18n("Unable to create temporary file") );
delete this;
return;
}
KIO::TransferJob* job = KIO::get( url );
connect(job, &KIO::TransferJob::result, this, &Import::downloadKimJobCompleted);
connect(job, &KIO::TransferJob::data, this, &Import::data);
}
void ImportExport::Import::downloadKimJobCompleted( KJob* job )
{
if ( job->error() ) {
job->uiDelegate()->showErrorMessage();
delete this;
}
else {
QString path = m_tmp->fileName();
m_tmp->close();
exec( path );
}
}
void ImportExport::Import::exec(const QString& fileName )
{
ImportDialog dialog(MainWindow::Window::theMainWindow());
KimFileReader kimFileReader;
if ( !kimFileReader.open( fileName ) ) {
delete this;
return;
}
bool ok = dialog.exec( &kimFileReader, m_kimFileUrl );
if ( ok ) {
ImportHandler handler;
handler.exec(dialog.settings(), &kimFileReader);
}
delete this;
}
void ImportExport::Import::data( KIO::Job*, const QByteArray& data )
{
m_tmp->write( data );
}
ImportExport::Import::~Import()
{
delete m_tmp;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/ImportExport/ImportHandler.cpp b/ImportExport/ImportHandler.cpp
index 34c6ee65..dc90cc2f 100644
--- a/ImportExport/ImportHandler.cpp
+++ b/ImportExport/ImportHandler.cpp
@@ -1,358 +1,357 @@
/* Copyright (C) 2003-2010 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ImportHandler.h"
#include "Logging.h"
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include "Utilities/Util.h"
#include "KimFileReader.h"
#include "ImportSettings.h"
#include "MainWindow/Window.h"
#include "DB/ImageDB.h"
#include "Browser/BrowserWidget.h"
#include "DB/MD5Map.h"
#include "DB/Category.h"
#include "DB/CategoryCollection.h"
#include "Utilities/UniqFilenameMapper.h"
#include "kio/job.h"
#include
using namespace ImportExport;
ImportExport::ImportHandler::ImportHandler()
: m_fileMapper(nullptr), m_finishedPressed(false), m_progress(0), m_reportUnreadableFiles( true )
, m_eventLoop( new QEventLoop )
{
}
ImportHandler::~ImportHandler() {
delete m_fileMapper;
delete m_eventLoop;
}
bool ImportExport::ImportHandler::exec( const ImportSettings& settings, KimFileReader* kimFileReader )
{
m_settings = settings;
m_kimFileReader = kimFileReader;
m_finishedPressed = true;
delete m_fileMapper;
m_fileMapper = new Utilities::UniqFilenameMapper(m_settings.destination());
bool ok;
// copy images
if ( m_settings.externalSource() ) {
copyFromExternal();
// If none of the images were to be copied, then we flushed the loop before we got started, in that case, don't start the loop.
qCDebug(ImportExportLog) << "Copying" << m_pendingCopies.count() << "files from external source...";
if ( m_pendingCopies.count() > 0 )
ok = m_eventLoop->exec();
else
ok = false;
}
else {
ok = copyFilesFromZipFile();
if ( ok )
updateDB();
}
if ( m_progress )
delete m_progress;
return ok;
}
void ImportExport::ImportHandler::copyFromExternal()
{
m_pendingCopies = m_settings.selectedImages();
m_totalCopied = 0;
m_progress = new QProgressDialog( MainWindow::Window::theMainWindow());
m_progress->setWindowTitle(i18n("Copying Images") );
m_progress->setMinimum( 0 );
m_progress->setMaximum( 2 * m_pendingCopies.count() );
m_progress->show();
connect(m_progress, &QProgressDialog::canceled, this, &ImportHandler::stopCopyingImages);
copyNextFromExternal();
}
void ImportExport::ImportHandler::copyNextFromExternal()
{
DB::ImageInfoPtr info = m_pendingCopies[0];
if ( isImageAlreadyInDB( info ) ) {
qCDebug(ImportExportLog) << info->fileName().relative() << "is already in database.";
aCopyJobCompleted(0);
return;
}
const DB::FileName fileName = info->fileName();
bool succeeded = false;
QStringList tried;
// First search for images next to the .kim file
// Second search for images base on the image root as specified in the .kim file
QList searchUrls {
m_settings.kimFile().adjusted(QUrl::RemoveFilename)
, m_settings.baseURL().adjusted(QUrl::RemoveFilename)
};
Q_FOREACH(const QUrl& url, searchUrls)
{
QUrl src (url);
src.setPath(src.path() + fileName.relative() );
- std::unique_ptr statJob { KIO::stat(src, KIO::StatJob::SourceSide, 0 /* just query for existance */ ) };
+ std::unique_ptr statJob { KIO::stat(src, KIO::StatJob::SourceSide, 0 /* just query for existence */ ) };
KJobWidgets::setWindow(statJob.get(), MainWindow::Window::theMainWindow());
if ( statJob->exec() )
{
QUrl dest = QUrl::fromLocalFile( m_fileMapper->uniqNameFor(fileName) );
m_job = KIO::file_copy( src, dest, -1, KIO::HideProgressInfo );
connect(m_job, &KIO::FileCopyJob::result, this, &ImportHandler::aCopyJobCompleted);
succeeded = true;
qCDebug(ImportExportLog) << "Copying" << src << "to" << dest;
break;
} else
tried << src.toDisplayString();
}
if (!succeeded)
aCopyFailed( tried );
}
bool ImportExport::ImportHandler::copyFilesFromZipFile()
{
DB::ImageInfoList images = m_settings.selectedImages();
m_totalCopied = 0;
m_progress = new QProgressDialog( MainWindow::Window::theMainWindow());
m_progress->setWindowTitle(i18n("Copying Images") );
m_progress->setMinimum( 0 );
m_progress->setMaximum( 2 * m_pendingCopies.count() );
m_progress->show();
for( DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it ) {
if ( !isImageAlreadyInDB( *it ) ) {
const DB::FileName fileName = (*it)->fileName();
QByteArray data = m_kimFileReader->loadImage( fileName.relative() );
if ( data.isNull() )
return false;
QString newName = m_fileMapper->uniqNameFor(fileName);
QFile out( newName );
if ( !out.open( QIODevice::WriteOnly ) ) {
KMessageBox::error( MainWindow::Window::theMainWindow(), i18n("Error when writing image %1", newName ) );
return false;
}
out.write( data.constData(), data.size() );
out.close();
}
qApp->processEvents();
m_progress->setValue( ++m_totalCopied );
if ( m_progress->wasCanceled() ) {
return false;
}
}
return true;
}
void ImportExport::ImportHandler::updateDB()
{
disconnect(m_progress, &QProgressDialog::canceled, this, &ImportHandler::stopCopyingImages);
m_progress->setLabelText( i18n("Updating Database") );
int len = Settings::SettingsData::instance()->imageDirectory().length();
// image directory is always a prefix of destination
if ( len == m_settings.destination().length() )
len = 0;
else
qCDebug(ImportExportLog)
<< "Re-rooting of ImageInfos from " << Settings::SettingsData::instance()->imageDirectory()
<< " to " << m_settings.destination();
// Run though all images
DB::ImageInfoList images = m_settings.selectedImages();
for( DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it ) {
DB::ImageInfoPtr info = *it;
if ( len != 0) {
// exchange prefix:
QString name = m_settings.destination() + info->fileName().absolute().mid(len);
qCDebug(ImportExportLog) << info->fileName().absolute() << " -> " << name;
info->setFileName( DB::FileName::fromAbsolutePath(name) );
}
if ( isImageAlreadyInDB( info ) ) {
qCDebug(ImportExportLog) << "Updating ImageInfo for " << info->fileName().absolute();
updateInfo( matchingInfoFromDB( info ), info );
} else {
qCDebug(ImportExportLog) << "Adding ImageInfo for " << info->fileName().absolute();
addNewRecord( info );
}
m_progress->setValue( ++m_totalCopied );
if ( m_progress->wasCanceled() )
break;
}
Browser::BrowserWidget::instance()->home();
}
void ImportExport::ImportHandler::stopCopyingImages()
{
m_job->kill();
}
void ImportExport::ImportHandler::aCopyFailed( QStringList files )
{
int result = m_reportUnreadableFiles ?
KMessageBox::warningYesNoCancelList( m_progress,
i18n("Cannot copy from any of the following locations:"),
files, QString(), KStandardGuiItem::cont(), KGuiItem( i18n("Continue without Asking") )) : KMessageBox::Yes;
switch (result) {
case KMessageBox::Cancel:
// This might be late -- if we managed to copy some files, we will
// just throw away any changes to the DB, but some new image files
// might be in the image directory...
m_eventLoop->exit(false);
m_pendingCopies.pop_front();
break;
case KMessageBox::No:
m_reportUnreadableFiles = false;
// fall through
default:
aCopyJobCompleted( 0 );
}
}
void ImportExport::ImportHandler::aCopyJobCompleted( KJob* job )
{
qCDebug(ImportExportLog) << "CopyJob" << job << "completed.";
m_pendingCopies.pop_front();
if ( job && job->error() ) {
job->uiDelegate()->showErrorMessage();
m_eventLoop->exit(false);
}
else if ( m_pendingCopies.count() == 0 ) {
updateDB();
m_eventLoop->exit(true);
}
else if ( m_progress->wasCanceled() ) {
m_eventLoop->exit(false);
}
else {
m_progress->setValue( ++m_totalCopied );
copyNextFromExternal();
}
}
bool ImportExport::ImportHandler::isImageAlreadyInDB( const DB::ImageInfoPtr& info )
{
return DB::ImageDB::instance()->md5Map()->contains(info->MD5Sum());
}
DB::ImageInfoPtr ImportExport::ImportHandler::matchingInfoFromDB( const DB::ImageInfoPtr& info )
{
const DB::FileName name = DB::ImageDB::instance()->md5Map()->lookup(info->MD5Sum());
return DB::ImageDB::instance()->info(name);
}
/**
* Merge the ImageInfo data from the kim file into the existing ImageInfo.
*/
void ImportExport::ImportHandler::updateInfo( DB::ImageInfoPtr dbInfo, DB::ImageInfoPtr newInfo )
{
if ( dbInfo->label() != newInfo->label() && m_settings.importAction(QString::fromLatin1("*Label*")) == ImportSettings::Replace )
dbInfo->setLabel( newInfo->label() );
if ( dbInfo->description().simplified() != newInfo->description().simplified() ) {
if ( m_settings.importAction(QString::fromLatin1("*Description*")) == ImportSettings::Replace )
dbInfo->setDescription( newInfo->description() );
else if ( m_settings.importAction(QString::fromLatin1("*Description*")) == ImportSettings::Merge )
dbInfo->setDescription( dbInfo->description() + QString::fromLatin1("
") + newInfo->description() );
}
if (dbInfo->angle() != newInfo->angle() && m_settings.importAction(QString::fromLatin1("*Orientation*")) == ImportSettings::Replace )
dbInfo->setAngle( newInfo->angle() );
if (dbInfo->date() != newInfo->date() && m_settings.importAction(QString::fromLatin1("*Date*")) == ImportSettings::Replace )
dbInfo->setDate( newInfo->date() );
updateCategories( newInfo, dbInfo, false );
}
void ImportExport::ImportHandler::addNewRecord( DB::ImageInfoPtr info )
{
const DB::FileName importName = info->fileName();
DB::ImageInfoPtr updateInfo(new DB::ImageInfo(importName, info->mediaType(), false /*don't read exif */));
updateInfo->setLabel( info->label() );
updateInfo->setDescription( info->description() );
updateInfo->setDate( info->date() );
updateInfo->setAngle( info->angle() );
updateInfo->setMD5Sum( Utilities::MD5Sum( updateInfo->fileName() ) );
DB::ImageInfoList list;
list.append(updateInfo);
DB::ImageDB::instance()->addImages( list );
updateCategories( info, updateInfo, true );
}
void ImportExport::ImportHandler::updateCategories( DB::ImageInfoPtr XMLInfo, DB::ImageInfoPtr DBInfo, bool forceReplace )
{
// Run though the categories
const QList matches = m_settings.categoryMatchSetting();
for ( const CategoryMatchSetting& match : matches ) {
QString XMLCategoryName = match.XMLCategoryName();
QString DBCategoryName = match.DBCategoryName();
ImportSettings::ImportAction action = m_settings.importAction(DBCategoryName);
const Utilities::StringSet items = XMLInfo->itemsOfCategory(XMLCategoryName);
DB::CategoryPtr DBCategoryPtr = DB::ImageDB::instance()->categoryCollection()->categoryForName( DBCategoryName );
if ( !forceReplace && action == ImportSettings::Replace )
DBInfo->setCategoryInfo( DBCategoryName, Utilities::StringSet() );
if ( action == ImportSettings::Merge || action == ImportSettings::Replace || forceReplace ) {
for ( const QString& item : items ) {
if (match.XMLtoDB().contains( item ) ) {
DBInfo->addCategoryInfo( DBCategoryName, match.XMLtoDB()[item] );
DBCategoryPtr->addItem( match.XMLtoDB()[item] );
}
}
}
}
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/TokenEditor.cpp b/MainWindow/TokenEditor.cpp
index 525fd633..2508a58c 100644
--- a/MainWindow/TokenEditor.cpp
+++ b/MainWindow/TokenEditor.cpp
@@ -1,139 +1,138 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "TokenEditor.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include "DB/ImageDB.h"
#include "DB/CategoryCollection.h"
#include "DB/Category.h"
#include "DB/ImageSearchInfo.h"
#include "Settings/SettingsData.h"
using namespace MainWindow;
TokenEditor::TokenEditor( QWidget* parent )
:QDialog( parent )
{
setWindowTitle( i18n( "Remove Tokens" ) );
QVBoxLayout *dialogLayout = new QVBoxLayout(this);
QWidget* mainContents = new QWidget;
QVBoxLayout* vlay = new QVBoxLayout( mainContents );
QLabel* label = new QLabel( i18n("Select tokens to remove from all images and videos:") );
vlay->addWidget( label );
QGridLayout* grid = new QGridLayout;
vlay->addLayout( grid );
int index = 0;
for ( int ch = 'A'; ch <= 'Z'; ch++, index++ ) {
QChar token = QChar::fromLatin1( (char) ch );
QCheckBox* box = new QCheckBox( token );
grid->addWidget( box, index/5, index % 5 );
m_checkBoxes.append( box );
}
QHBoxLayout* hlay = new QHBoxLayout;
vlay->addLayout( hlay );
hlay->addStretch( 1 );
QPushButton* selectAll = new QPushButton( i18n("Select All") );
QPushButton* selectNone = new QPushButton( i18n("Select None") );
hlay->addWidget( selectAll );
hlay->addWidget( selectNone );
connect(selectAll, &QPushButton::clicked, this, &TokenEditor::selectAll);
connect(selectNone, &QPushButton::clicked, this, &TokenEditor::selectNone);
dialogLayout->addWidget(mainContents);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
buttonBox->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::accepted, this, &TokenEditor::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &TokenEditor::reject);
dialogLayout->addWidget(buttonBox);
}
void TokenEditor::show()
{
QStringList tokens = tokensInUse();
Q_FOREACH( QCheckBox *box, m_checkBoxes ) {
box->setChecked( false );
QString txt = box->text().remove( QString::fromLatin1("&") );
box->setEnabled( tokens.contains( txt ) );
}
QDialog::show();
}
void TokenEditor::selectAll()
{
Q_FOREACH( QCheckBox *box, m_checkBoxes ) {
box->setChecked( true );
}
}
void TokenEditor::selectNone()
{
Q_FOREACH( QCheckBox *box, m_checkBoxes ) {
box->setChecked( false );
}
}
/**
I would love to use Settings::optionValue, but that method does not
forget about an item once it has seen it, which is really what it should
do anyway, otherwise it would be way to expensive in use.
*/
QStringList TokenEditor::tokensInUse()
{
QStringList res;
DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
QMap map =
DB::ImageDB::instance()->classify( DB::ImageSearchInfo(), tokensCategory->name(), DB::anyMediaType );
for( QMap::Iterator it = map.begin(); it != map.end(); ++it ) {
if ( it.value() > 0 )
res.append( it.key() );
}
return res;
}
void TokenEditor::accept()
{
DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
Q_FOREACH( const QCheckBox *box, m_checkBoxes ) {
if ( box->isChecked() && box->isEnabled() ) {
QString txt = box->text().remove( QString::fromLatin1("&") );
tokensCategory->removeItem( txt );
}
}
QDialog::accept();
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/WelcomeDialog.cpp b/MainWindow/WelcomeDialog.cpp
index 8aded087..1acaad93 100644
--- a/MainWindow/WelcomeDialog.cpp
+++ b/MainWindow/WelcomeDialog.cpp
@@ -1,211 +1,210 @@
/* Copyright (C) 2003-2018 Jesper K Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "WelcomeDialog.h"
#include "FeatureDialog.h"
#include "Window.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
using namespace MainWindow;
WelcomeDialog::WelcomeDialog( QWidget* parent )
: QDialog( parent )
{
QVBoxLayout* lay1 = new QVBoxLayout( this );
QHBoxLayout* lay2 = new QHBoxLayout;
lay1->addLayout( lay2 );
QLabel* image = new QLabel( this );
image->setMinimumSize( QSize( 273, 204 ) );
image->setMaximumSize( QSize( 273, 204 ) );
image->setPixmap(Utilities::locateDataFile(QString::fromLatin1("pics/splash.png")));
lay2->addWidget( image );
QLabel* textLabel2 = new QLabel( this );
lay2->addWidget( textLabel2 );
textLabel2->setText( i18n( "Welcome to KPhotoAlbum
"
"KPhotoAlbum is a powerful free tool to archive, tag and manage your photos and "
"videos. It will not modify or change your precious files, it only indexes them "
"and lets you easily find and manage your photos and videos.
"
"Start by showing KPhotoAlbum where your photos are by pressing on Create My Own "
"Database. Select this button also if you have an existing KPhotoAlbum database "
"that you want to start using again.
"
"If you feel safer first trying out KPhotoAlbum with prebuilt set of images, "
"press the Load Demo button.
" ) );
textLabel2->setWordWrap( true );
QHBoxLayout* lay3 = new QHBoxLayout;
lay1->addLayout( lay3 );
lay3->addStretch( 1 );
QPushButton* createSetup = new QPushButton( i18n("Create My Own Database..."), this );
lay3->addWidget( createSetup );
QPushButton* loadDemo = new QPushButton( i18n("Load Demo") );
lay3->addWidget( loadDemo );
QPushButton* checkFeatures = new QPushButton( i18n("Check My Feature Set") );
lay3->addWidget( checkFeatures );
connect(loadDemo, &QPushButton::clicked, this, &WelcomeDialog::slotLoadDemo);
connect(createSetup, &QPushButton::clicked, this, &WelcomeDialog::createSetup);
connect(checkFeatures, &QPushButton::clicked, this, &WelcomeDialog::checkFeatures);
}
void WelcomeDialog::slotLoadDemo()
{
// rerun KPA with "--demo"
MainWindow::Window::theMainWindow()->runDemo();
// cancel the dialog (and exit this instance of KPA)
reject();
}
void WelcomeDialog::createSetup()
{
FileDialog dialog( this );
m_configFile = dialog.getFileName();
if ( !m_configFile.isNull() )
accept();
}
QString WelcomeDialog::configFileName() const
{
return m_configFile;
}
FileDialog::FileDialog( QWidget* parent ) :QDialog( parent )
{
QVBoxLayout *mainLayout = new QVBoxLayout (this);
QLabel* label = new QLabel( i18n("KPhotoAlbum database creation
"
"You need to show where the photos and videos are for KPhotoAlbum to "
"find them. They all need to be under one root directory, for example "
"/home/user/Images. In this directory you can have as many subdirectories as you "
"want, KPhotoAlbum will find them all for you.
"
"Feel safe, KPhotoAlbum will not modify or edit any of your images, so you can "
"simply point KPhotoAlbum to the directory where you already have all your "
"images.
"
"If you have an existing KPhotoAlbum database and root directory somewhere, "
"point KPhotoAlbum to that directory to start using it again.
" ), this );
label->setWordWrap( true );
mainLayout->addWidget( label );
QHBoxLayout* lay2 = new QHBoxLayout;
label = new QLabel( i18n("Image/Video root directory: "), this );
lay2->addWidget( label );
m_lineEdit = new QLineEdit( this );
m_lineEdit->setText( QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) );
lay2->addWidget( m_lineEdit );
QPushButton* button = new QPushButton( QString::fromLatin1("..."), this );
button->setMaximumWidth( 30 );
lay2->addWidget( button );
connect(button, &QPushButton::clicked, this, &FileDialog::slotBrowseForDirecory);
mainLayout->addLayout( lay2 );
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::accepted, this, &FileDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &FileDialog::reject);
mainLayout->addWidget(buttonBox);
}
void FileDialog::slotBrowseForDirecory()
{
QString dir = QFileDialog::getExistingDirectory(this , QString(), m_lineEdit->text());
if ( ! dir.isNull() )
m_lineEdit->setText( dir );
}
QString FileDialog::getFileName()
{
bool ok = false;
QString dir;
while ( !ok ) {
if ( exec() == Rejected )
return QString();
dir = KShell::tildeExpand( m_lineEdit->text() );
if ( !QFileInfo( dir ).exists() ) {
int create = KMessageBox::questionYesNo( this, i18n("Directory does not exist, create it?") );
if ( create == KMessageBox::Yes ) {
bool ok2 = QDir().mkdir( dir );
if ( !ok2 ) {
KMessageBox::sorry( this, i18n("Could not create directory %1",dir) );
}
else
ok = true;
}
}
else if ( !QFileInfo( dir ).isDir() ) {
KMessageBox::sorry( this, i18n("%1 exists, but is not a directory",dir) );
}
else
ok = true;
}
QString file = dir + QString::fromLatin1("/index.xml");
KConfigGroup group = KSharedConfig::openConfig()->group(QString::fromUtf8("General"));
group.writeEntry( QString::fromLatin1("imageDBFile"), file );
group.sync();
return file;
}
void MainWindow::WelcomeDialog::checkFeatures()
{
if ( !FeatureDialog::hasAllFeaturesAvailable() ) {
const QString msg =
i18n("KPhotoAlbum does not seem to be build with support for all its features. The following is a list "
"indicating to you what you may miss:
"
"For details on how to solve this problem, please choose Help|KPhotoAlbum Feature Status "
"from the menus.
", FeatureDialog::featureString() );
KMessageBox::information( this, msg, i18n("Feature Check") );
}
else {
KMessageBox::information( this, i18n("Congratulations: all dynamic features have been enabled."),
i18n("Feature Check" ) );
}
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/Window.cpp b/MainWindow/Window.cpp
index 9b3f00c4..78c791da 100644
--- a/MainWindow/Window.cpp
+++ b/MainWindow/Window.cpp
@@ -1,1990 +1,1987 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include
#include "Window.h"
#include
#ifdef HAVE_STDLIB_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for #if KIO_VERSION...
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HASKIPI
# include
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HASKIPI
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "AutoStackImages.h"
#include "BreadcrumbViewer.h"
#include "CopyPopup.h"
#include "DeleteDialog.h"
#include "DirtyIndicator.h"
#include "DuplicateMerger/DuplicateMerger.h"
#include "ExternalPopup.h"
#include "FeatureDialog.h"
#include "ImageCounter.h"
#include "InvalidDateFinder.h"
#include "Logging.h"
#include "Options.h"
#include "SearchBar.h"
#include "SplashScreen.h"
#include "StatisticsDialog.h"
#include "StatusBar.h"
#include "TokenEditor.h"
#include "UpdateVideoThumbnail.h"
#include "WelcomeDialog.h"
using namespace DB;
MainWindow::Window* MainWindow::Window::s_instance = nullptr;
MainWindow::Window::Window( QWidget* parent )
:KXmlGuiWindow( parent ),
m_annotationDialog(nullptr),
m_deleteDialog( nullptr ), m_htmlDialog(nullptr), m_tokenEditor( nullptr )
{
#ifdef HAVE_KGEOMAP
m_positionBrowser = 0;
#endif
qCDebug(MainWindowLog) << "Using icon theme: " << QIcon::themeName();
qCDebug(MainWindowLog) << "Icon search paths: " << QIcon::themeSearchPaths();
QElapsedTimer timer;
timer.start();
SplashScreen::instance()->message( i18n("Loading Database") );
s_instance = this;
bool gotConfigFile = load();
if ( !gotConfigFile )
throw 0;
qCInfo(TimingLog) << "MainWindow: Loading Database: " << timer.restart() << "ms.";
SplashScreen::instance()->message( i18n("Loading Main Window") );
QWidget* top = new QWidget( this );
QVBoxLayout* lay = new QVBoxLayout( top );
lay->setSpacing(2);
lay->setContentsMargins(2,2,2,2);
setCentralWidget( top );
m_stack = new QStackedWidget( top );
lay->addWidget( m_stack, 1 );
m_dateBar = new DateBar::DateBarWidget( top );
lay->addWidget( m_dateBar );
m_dateBarLine = new QFrame( top );
m_dateBarLine->setFrameStyle( QFrame::HLine | QFrame::Plain );
m_dateBarLine->setLineWidth(0); m_dateBarLine->setMidLineWidth(0);
QPalette pal = m_dateBarLine->palette();
pal.setColor( QPalette::WindowText, QColor("#c4c1bd") );
m_dateBarLine->setPalette( pal );
lay->addWidget( m_dateBarLine );
setHistogramVisibilty(Settings::SettingsData::instance()->showHistogram());
m_browser = new Browser::BrowserWidget( m_stack );
m_thumbnailView = new ThumbnailView::ThumbnailFacade();
m_stack->addWidget( m_browser );
m_stack->addWidget( m_thumbnailView->gui() );
m_stack->setCurrentWidget( m_browser );
m_settingsDialog = nullptr;
qCInfo(TimingLog) << "MainWindow: Loading MainWindow: " << timer.restart() << "ms.";
setupMenuBar();
qCInfo(TimingLog) << "MainWindow: setupMenuBar: " << timer.restart() << "ms.";
createSarchBar();
qCInfo(TimingLog) << "MainWindow: createSearchBar: " << timer.restart() << "ms.";
setupStatusBar();
qCInfo(TimingLog) << "MainWindow: setupStatusBar: " << timer.restart() << "ms.";
// Misc
m_autoSaveTimer = new QTimer( this );
connect(m_autoSaveTimer, &QTimer::timeout, this, &Window::slotAutoSave);
startAutoSaveTimer();
connect(m_browser, &Browser::BrowserWidget::showingOverview, this, &Window::showBrowser);
connect( m_browser, SIGNAL(pathChanged(Browser::BreadcrumbList)), m_statusBar->mp_pathIndicator, SLOT(setBreadcrumbs(Browser::BreadcrumbList)) );
connect( m_statusBar->mp_pathIndicator, SIGNAL(widenToBreadcrumb(Browser::Breadcrumb)), m_browser, SLOT(widenToBreadcrumb(Browser::Breadcrumb)) );
connect( m_browser, SIGNAL(pathChanged(Browser::BreadcrumbList)), this, SLOT(updateDateBar(Browser::BreadcrumbList)) );
connect(m_dateBar, &DateBar::DateBarWidget::dateSelected, m_thumbnailView, &ThumbnailView::ThumbnailFacade::gotoDate);
connect(m_dateBar, &DateBar::DateBarWidget::toolTipInfo, this, &Window::showDateBarTip);
connect( Settings::SettingsData::instance(), SIGNAL(histogramSizeChanged(QSize)), m_dateBar, SLOT(setHistogramBarSize(QSize)) );
connect( Settings::SettingsData::instance(), SIGNAL(actualThumbnailSizeChanged(int)), this, SLOT(slotThumbnailSizeChanged()) );
connect(m_dateBar, &DateBar::DateBarWidget::dateRangeChange, this, &Window::setDateRange);
connect(m_dateBar, &DateBar::DateBarWidget::dateRangeCleared, this, &Window::clearDateRange);
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::currentDateChanged, m_dateBar, &DateBar::DateBarWidget::setDate);
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::showImage, this, &Window::showImage);
connect( m_thumbnailView, SIGNAL(showSelection()), this, SLOT(slotView()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::fileIdUnderCursorChanged, this, &Window::slotSetFileName);
connect( DB::ImageDB::instance(), SIGNAL(totalChanged(uint)), this, SLOT(updateDateBar()) );
connect( DB::ImageDB::instance()->categoryCollection(), SIGNAL(categoryCollectionChanged()), this, SLOT(slotOptionGroupChanged()) );
connect( m_browser, SIGNAL(imageCount(uint)), m_statusBar->mp_partial, SLOT(showBrowserMatches(uint)) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::selectionChanged, this, &Window::updateContextMenuFromSelectionSize);
+ checkIfMplayerIsInstalled();
+ executeStartupActions();
+
+ qCInfo(TimingLog) << "MainWindow: executeStartupActions " << timer.restart() << "ms.";
QTimer::singleShot( 0, this, SLOT(delayedInit()) );
updateContextMenuFromSelectionSize(0);
// Automatically save toolbar settings
setAutoSaveSettings();
- checkIfMplayerIsInstalled();
-
qCInfo(TimingLog) << "MainWindow: misc setup time: " << timer.restart() << "ms.";
- executeStartupActions();
- qCInfo(TimingLog) << "MainWindow: executeStartupActions " << timer.restart() << "ms.";
}
MainWindow::Window::~Window()
{
DB::ImageDB::deleteInstance();
ImageManager::ThumbnailCache::deleteInstance();
Exif::Database::deleteInstance();
}
void MainWindow::Window::delayedInit()
{
QElapsedTimer timer;
timer.start();
SplashScreen* splash = SplashScreen::instance();
setupPluginMenu();
qCInfo(TimingLog) << "MainWindow: setupPluginMenu: " << timer.restart() << "ms.";
if ( Settings::SettingsData::instance()->searchForImagesOnStart() ||
Options::the()->searchForImagesOnStart() ) {
splash->message( i18n("Searching for New Files") );
qApp->processEvents();
DB::ImageDB::instance()->slotRescan();
qCInfo(TimingLog) << "MainWindow: Search for New Files: " << timer.restart() << "ms.";
}
if ( !Settings::SettingsData::instance()->delayLoadingPlugins() ) {
splash->message( i18n( "Loading Plug-ins" ) );
loadPlugins();
qCInfo(TimingLog) << "MainWindow: Loading Plug-ins: " << timer.restart() << "ms.";
}
splash->done();
show();
updateDateBar();
qCInfo(TimingLog) << "MainWindow: MainWindow.show():" << timer.restart() << "ms.";
QUrl importUrl = Options::the()->importFile();
if ( importUrl.isValid() )
{
// I need to do this in delayed init to get the import window on top of the normal window
ImportExport::Import::imageImport( importUrl );
qCInfo(TimingLog) << "MainWindow: imageImport:" << timer.restart() << "ms.";
} else {
// I need to postpone this otherwise the tip dialog will not get focus on start up
KTipDialog::showTip( this );
}
- Exif::Database* exifDB = Exif::Database::instance(); // Load the database
- if ( exifDB->isAvailable() && !exifDB->isOpen() ) {
- KMessageBox::sorry( this, i18n("EXIF database cannot be opened. Check that the image root directory is writable.") );
- }
+ Exif::Database::instance(); // Load the database
qCInfo(TimingLog) << "MainWindow: Loading EXIF DB:" << timer.restart() << "ms.";
if (!Options::the()->listen().isNull())
RemoteControl::RemoteInterface::instance().listen(Options::the()->listen());
else if ( Settings::SettingsData::instance()->listenForAndroidDevicesOnStartup())
RemoteControl::RemoteInterface::instance().listen();
announceAndroidVersion();
}
bool MainWindow::Window::slotExit()
{
if ( Options::the()->demoMode() ) {
QString txt = i18n("Delete Your Temporary Demo Database
"
"I hope you enjoyed the KPhotoAlbum demo. The demo database was copied to "
"/tmp, should it be deleted now? If you do not delete it, it will waste disk space; "
"on the other hand, if you want to come back and try the demo again, you "
"might want to keep it around with the changes you made through this session.
" );
int ret = KMessageBox::questionYesNoCancel( this, txt, i18n("Delete Demo Database"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(),
QString::fromLatin1("deleteDemoDatabase") );
if ( ret == KMessageBox::Cancel )
return false;
else if ( ret == KMessageBox::Yes ) {
Utilities::deleteDemo();
goto doQuit;
}
else {
// pass through to the check for dirtyness.
}
}
if ( m_statusBar->mp_dirtyIndicator->isSaveDirty() ) {
int ret = KMessageBox::warningYesNoCancel( this, i18n("Do you want to save the changes?"),
i18n("Save Changes?") );
if (ret == KMessageBox::Cancel) {
return false;
}
if ( ret == KMessageBox::Yes ) {
slotSave();
}
if ( ret == KMessageBox::No ) {
QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
}
}
// Flush any remaining thumbnails
ImageManager::ThumbnailCache::instance()->save();
doQuit:
ImageManager::AsyncLoader::instance()->requestExit();
qApp->quit();
return true;
}
void MainWindow::Window::slotOptions()
{
if ( ! m_settingsDialog ) {
m_settingsDialog = new Settings::SettingsDialog( this );
connect( m_settingsDialog, SIGNAL(changed()), this, SLOT(reloadThumbnails()) );
connect(m_settingsDialog, &Settings::SettingsDialog::changed, this, &Window::startAutoSaveTimer);
connect(m_settingsDialog, &Settings::SettingsDialog::changed, m_browser, &Browser::BrowserWidget::reload);
}
m_settingsDialog->show();
}
void MainWindow::Window::slotCreateImageStack()
{
const DB::FileNameList list = selected();
if (list.size() < 2) {
// it doesn't make sense to make a stack from one image, does it?
return;
}
bool ok = DB::ImageDB::instance()->stack( list );
if ( !ok ) {
if ( KMessageBox::questionYesNo( this,
i18n("Some of the selected images already belong to a stack. "
"Do you want to remove them from their stacks and create a "
"completely new one?"), i18n("Stacking Error")) == KMessageBox::Yes ) {
DB::ImageDB::instance()->unstack(list);
if ( ! DB::ImageDB::instance()->stack(list)) {
KMessageBox::sorry( this,
i18n("Unknown error, stack creation failed."),
i18n("Stacking Error"));
return;
}
} else {
return;
}
}
DirtyIndicator::markDirty();
// The current item might have just became invisible
m_thumbnailView->setCurrentItem(list.at(0));
m_thumbnailView->updateDisplayModel();
}
/** @short Make the selected image the head of a stack
*
* The whole point of image stacking is to group images together and then select
* one of them as the "most important". This function is (maybe just a
* temporary) way of promoting a selected image to the "head" of a stack it
* belongs to. In future, it might get replaced by a Ligtroom-like interface.
* */
void MainWindow::Window::slotSetStackHead()
{
const DB::FileNameList list = selected();
if ( list.size() != 1 ) {
// this should be checked by enabling/disabling of QActions
return;
}
setStackHead( *list.begin() );
}
void MainWindow::Window::setStackHead( const DB::FileName& image )
{
if ( ! image.info()->isStacked() )
return;
unsigned int oldOrder = image.info()->stackOrder();
DB::FileNameList others = DB::ImageDB::instance()->getStackFor(image);
Q_FOREACH( const DB::FileName& current, others ) {
if (current == image) {
current.info()->setStackOrder( 1 );
} else if ( current.info()->stackOrder() < oldOrder ) {
current.info()->setStackOrder( current.info()->stackOrder() + 1 );
}
}
DirtyIndicator::markDirty();
m_thumbnailView->updateDisplayModel();
}
void MainWindow::Window::slotUnStackImages()
{
const DB::FileNameList& list = selected();
if (list.isEmpty())
return;
DB::ImageDB::instance()->unstack(list);
DirtyIndicator::markDirty();
m_thumbnailView->updateDisplayModel();
}
void MainWindow::Window::slotConfigureAllImages()
{
configureImages( false );
}
void MainWindow::Window::slotConfigureImagesOneAtATime()
{
configureImages( true );
}
void MainWindow::Window::configureImages( bool oneAtATime )
{
const DB::FileNameList& list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."), i18n("No Selection") );
}
else {
DB::ImageInfoList images;
Q_FOREACH( const DB::FileName& fileName, list) {
images.append(fileName.info());
}
configureImages( images, oneAtATime );
}
}
void MainWindow::Window::configureImages( const DB::ImageInfoList& list, bool oneAtATime )
{
s_instance->configImages( list, oneAtATime );
}
void MainWindow::Window::configImages( const DB::ImageInfoList& list, bool oneAtATime )
{
createAnnotationDialog();
if ( m_annotationDialog->configure( list, oneAtATime ) == QDialog::Rejected )
return;
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::slotSearch()
{
createAnnotationDialog();
DB::ImageSearchInfo searchInfo = m_annotationDialog->search();
if ( !searchInfo.isNull() )
m_browser->addSearch( searchInfo );
}
void MainWindow::Window::createAnnotationDialog()
{
Utilities::ShowBusyCursor dummy;
if ( !m_annotationDialog.isNull() )
return;
m_annotationDialog = new AnnotationDialog::Dialog( nullptr );
connect(m_annotationDialog.data(), &AnnotationDialog::Dialog::imageRotated, this, &Window::slotImageRotated);
}
void MainWindow::Window::slotSave()
{
Utilities::ShowBusyCursor dummy;
m_statusBar->showMessage(i18n("Saving..."), 5000 );
DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("index.xml"), false );
ImageManager::ThumbnailCache::instance()->save();
m_statusBar->mp_dirtyIndicator->saved();
QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
m_statusBar->showMessage(i18n("Saving... Done"), 5000 );
}
void MainWindow::Window::slotDeleteSelected()
{
if ( ! m_deleteDialog )
m_deleteDialog = new DeleteDialog( this );
if ( m_deleteDialog->exec( selected() ) != QDialog::Accepted )
return;
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotCopySelectedURLs()
{
QList urls; int urlcount = 0;
Q_FOREACH(const DB::FileName &fileName, selected()) {
urls.append( QUrl::fromLocalFile(fileName.absolute()) );
urlcount++;
}
if (urlcount == 1) m_paste->setEnabled (true); else m_paste->setEnabled(false);
QMimeData* mimeData = new QMimeData;
mimeData->setUrls(urls);
QApplication::clipboard()->setMimeData( mimeData );
}
void MainWindow::Window::slotPasteInformation()
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
// Idealy this would look like
// QList urls;
// urls.fromMimeData(mimeData);
// if ( urls.count() != 1 ) return;
// const QString string = urls.first().path();
QString string = mimeData->text();
// fail silent if more than one image is in clipboard.
if (string.count(QString::fromLatin1("\n")) != 0) return;
const QString urlHead = QLatin1String("file://");
if (string.startsWith(urlHead)) {
string = string.right(string.size()-urlHead.size());
}
const DB::FileName fileName = DB::FileName::fromAbsolutePath(string);
// fail silent if there is no file.
if (fileName.isNull()) return;
MD5 originalSum = Utilities::MD5Sum( fileName );
ImageInfoPtr originalInfo;
if ( DB::ImageDB::instance()->md5Map()->contains( originalSum ) ) {
originalInfo = DB::ImageDB::instance()->info( fileName );
} else {
originalInfo = fileName.info();
}
// fail silent if there is no info for the file.
if (!originalInfo) return;
Q_FOREACH(const DB::FileName& newFile, selected()) {
newFile.info()->copyExtraData(*originalInfo, false);
}
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotReReadExifInfo()
{
DB::FileNameList files = selectedOnDisk();
static Exif::ReReadDialog* dialog = nullptr;
if ( ! dialog )
dialog = new Exif::ReReadDialog( this );
if ( dialog->exec( files ) == QDialog::Accepted )
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotAutoStackImages()
{
const DB::FileNameList list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."), i18n("No Selection") );
return;
}
QPointer stacker = new AutoStackImages( this, list );
if ( stacker->exec() == QDialog::Accepted )
showThumbNails();
delete stacker;
}
/**
* In thumbnail mode, return a list of files that are selected.
* Otherwise, return all images in the current scope/context.
*/
DB::FileNameList MainWindow::Window::selected( ThumbnailView::SelectionMode mode) const
{
if ( m_thumbnailView->gui() == m_stack->currentWidget() )
return m_thumbnailView->selection(mode);
else
// return all images in the current scope (parameter false: include images not on disk)
return DB::ImageDB::instance()->currentScope(false);
}
void MainWindow::Window::slotViewNewWindow()
{
slotView( false, false );
}
/*
* Returns a list of files that are both selected and on disk. If there are no
* selected files, returns all files form current context that are on disk.
* Note: On some setups (NFS), this can be a very time-consuming method!
* */
DB::FileNameList MainWindow::Window::selectedOnDisk()
{
const DB::FileNameList list = selected(ThumbnailView::NoExpandCollapsedStacks);
DB::FileNameList listOnDisk;
Q_FOREACH(const DB::FileName& fileName, list) {
if (DB::ImageInfo::imageOnDisk(fileName))
listOnDisk.append(fileName);
}
return listOnDisk;
}
void MainWindow::Window::slotView( bool reuse, bool slideShow, bool random )
{
launchViewer(selected(ThumbnailView::NoExpandCollapsedStacks), reuse, slideShow, random );
}
void MainWindow::Window::launchViewer(const DB::FileNameList& inputMediaList, bool reuse, bool slideShow, bool random)
{
DB::FileNameList mediaList = inputMediaList;
int seek = -1;
if (mediaList.isEmpty()) {
mediaList = m_thumbnailView->imageList( ThumbnailView::ViewOrder );
} else if (mediaList.size() == 1) {
// we fake it so it appears the user has selected all images
// and magically scrolls to the originally selected one
const DB::FileName first = mediaList.at(0);
mediaList = m_thumbnailView->imageList( ThumbnailView::ViewOrder );
seek = mediaList.indexOf(first);
}
if (mediaList.isEmpty())
mediaList = DB::ImageDB::instance()->currentScope( false );
if (mediaList.isEmpty()) {
KMessageBox::sorry( this, i18n("There are no images to be shown.") );
return;
}
if (random) {
mediaList = DB::FileNameList(Utilities::shuffleList(mediaList));
}
Viewer::ViewerWidget* viewer;
if ( reuse && Viewer::ViewerWidget::latest() ) {
viewer = Viewer::ViewerWidget::latest();
viewer->raise();
viewer->activateWindow();
}
else
viewer = new Viewer::ViewerWidget(Viewer::ViewerWidget::ViewerWindow,
&m_viewerInputMacros);
connect(viewer, &Viewer::ViewerWidget::soughtTo, m_thumbnailView, &ThumbnailView::ThumbnailFacade::changeSingleSelection);
connect(viewer, &Viewer::ViewerWidget::imageRotated, this, &Window::slotImageRotated);
viewer->show( slideShow );
viewer->load( mediaList, seek < 0 ? 0 : seek );
viewer->raise();
}
void MainWindow::Window::slotSortByDateAndTime()
{
DB::ImageDB::instance()->sortAndMergeBackIn(selected());
showThumbNails( DB::ImageDB::instance()->search( Browser::BrowserWidget::instance()->currentContext()));
DirtyIndicator::markDirty();
}
void MainWindow::Window::slotSortAllByDateAndTime()
{
DB::ImageDB::instance()->sortAndMergeBackIn(DB::ImageDB::instance()->images());
if ( m_thumbnailView->gui() == m_stack->currentWidget() )
showThumbNails( DB::ImageDB::instance()->search( Browser::BrowserWidget::instance()->currentContext()));
DirtyIndicator::markDirty();
}
QString MainWindow::Window::welcome()
{
QString configFileName;
QPointer dialog = new WelcomeDialog( this );
// exit if the user dismissed the welcome dialog
if (!dialog->exec())
{
qApp->quit();
}
configFileName = dialog->configFileName();
delete dialog;
return configFileName;
}
void MainWindow::Window::closeEvent( QCloseEvent* e )
{
bool quit = true;
quit = slotExit();
// If I made it here, then the user canceled
if ( !quit )
e->ignore();
else
e->setAccepted(true);
}
void MainWindow::Window::slotLimitToSelected()
{
Utilities::ShowBusyCursor dummy;
showThumbNails( selected() );
}
void MainWindow::Window::setupMenuBar()
{
// File menu
KStandardAction::save( this, SLOT(slotSave()), actionCollection() );
KStandardAction::quit( this, SLOT(slotExit()), actionCollection() );
m_generateHtml = actionCollection()->addAction( QString::fromLatin1("exportHTML") );
m_generateHtml->setText( i18n("Generate HTML...") );
connect(m_generateHtml, &QAction::triggered, this, &Window::slotExportToHTML);
QAction* a = actionCollection()->addAction( QString::fromLatin1("import"), this, SLOT(slotImport()) );
a->setText( i18n( "Import...") );
a = actionCollection()->addAction( QString::fromLatin1("export"), this, SLOT(slotExport()) );
a->setText( i18n( "Export/Copy Images...") );
// Go menu
a = KStandardAction::back( m_browser, SLOT(back()), actionCollection() );
connect(m_browser, &Browser::BrowserWidget::canGoBack, a, &QAction::setEnabled);
a->setEnabled( false );
a = KStandardAction::forward( m_browser, SLOT(forward()), actionCollection() );
connect(m_browser, &Browser::BrowserWidget::canGoForward, a, &QAction::setEnabled);
a->setEnabled( false );
a = KStandardAction::home( m_browser, SLOT(home()), actionCollection() );
actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Home);
connect(a, &QAction::triggered, m_dateBar, &DateBar::DateBarWidget::clearSelection);
KStandardAction::redisplay( m_browser, SLOT(go()), actionCollection() );
// The Edit menu
m_copy = KStandardAction::copy( this, SLOT(slotCopySelectedURLs()), actionCollection() );
m_paste = KStandardAction::paste( this, SLOT(slotPasteInformation()), actionCollection() );
m_paste->setEnabled(false);
m_selectAll = KStandardAction::selectAll( m_thumbnailView, SLOT(selectAll()), actionCollection() );
KStandardAction::find( this, SLOT(slotSearch()), actionCollection() );
m_deleteSelected = actionCollection()->addAction(QString::fromLatin1("deleteSelected"));
m_deleteSelected->setText( i18nc("Delete selected images", "Delete Selected" ) );
m_deleteSelected->setIcon( QIcon::fromTheme( QString::fromLatin1("edit-delete") ) );
actionCollection()->setDefaultShortcut(m_deleteSelected, Qt::Key_Delete);
connect(m_deleteSelected, &QAction::triggered, this, &Window::slotDeleteSelected);
a = actionCollection()->addAction(QString::fromLatin1("removeTokens"), this, SLOT(slotRemoveTokens()));
a->setText( i18n("Remove Tokens...") );
a = actionCollection()->addAction(QString::fromLatin1("showListOfFiles"), this, SLOT(slotShowListOfFiles()));
a->setText( i18n("Open List of Files...")) ;
m_configOneAtATime = actionCollection()->addAction( QString::fromLatin1("oneProp"), this, SLOT(slotConfigureImagesOneAtATime()) );
m_configOneAtATime->setText( i18n( "Annotate Individual Items" ) );
actionCollection()->setDefaultShortcut(m_configOneAtATime, Qt::CTRL + Qt::Key_1);
m_configAllSimultaniously = actionCollection()->addAction( QString::fromLatin1("allProp"), this, SLOT(slotConfigureAllImages()) );
m_configAllSimultaniously->setText( i18n( "Annotate Multiple Items at a Time" ) );
actionCollection()->setDefaultShortcut(m_configAllSimultaniously, Qt::CTRL + Qt::Key_2);
m_createImageStack = actionCollection()->addAction( QString::fromLatin1("createImageStack"), this, SLOT(slotCreateImageStack()) );
m_createImageStack->setText( i18n("Merge Images into a Stack") );
actionCollection()->setDefaultShortcut(m_createImageStack, Qt::CTRL + Qt::Key_3);
m_unStackImages = actionCollection()->addAction( QString::fromLatin1("unStackImages"), this, SLOT(slotUnStackImages()) );
m_unStackImages->setText( i18n("Remove Images from Stack") );
m_setStackHead = actionCollection()->addAction( QString::fromLatin1("setStackHead"), this, SLOT(slotSetStackHead()) );
m_setStackHead->setText( i18n("Set as First Image in Stack") );
actionCollection()->setDefaultShortcut(m_setStackHead, Qt::CTRL + Qt::Key_4);
m_rotLeft = actionCollection()->addAction( QString::fromLatin1("rotateLeft"), this, SLOT(slotRotateSelectedLeft()) );
m_rotLeft->setText( i18n( "Rotate counterclockwise" ) );
actionCollection()->setDefaultShortcut(m_rotLeft, Qt::Key_7);
m_rotRight = actionCollection()->addAction( QString::fromLatin1("rotateRight"), this, SLOT(slotRotateSelectedRight()) );
m_rotRight->setText( i18n( "Rotate clockwise" ) );
actionCollection()->setDefaultShortcut(m_rotRight, Qt::Key_9);
// The Images menu
m_view = actionCollection()->addAction( QString::fromLatin1("viewImages"), this, SLOT(slotView()) );
m_view->setText( i18n("View") );
actionCollection()->setDefaultShortcut(m_view, Qt::CTRL + Qt::Key_I);
m_viewInNewWindow = actionCollection()->addAction( QString::fromLatin1("viewImagesNewWindow"), this, SLOT(slotViewNewWindow()) );
m_viewInNewWindow->setText( i18n("View (In New Window)") );
m_runSlideShow = actionCollection()->addAction( QString::fromLatin1("runSlideShow"), this, SLOT(slotRunSlideShow()) );
m_runSlideShow->setText( i18n("Run Slide Show") );
m_runSlideShow->setIcon( QIcon::fromTheme( QString::fromLatin1("view-presentation") ) );
actionCollection()->setDefaultShortcut(m_runSlideShow, Qt::CTRL + Qt::Key_R);
m_runRandomSlideShow = actionCollection()->addAction( QString::fromLatin1("runRandomizedSlideShow"), this, SLOT(slotRunRandomizedSlideShow()) );
m_runRandomSlideShow->setText( i18n( "Run Randomized Slide Show" ) );
a = actionCollection()->addAction( QString::fromLatin1("collapseAllStacks"),
m_thumbnailView, SLOT(collapseAllStacks()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::collapseAllStacksEnabled, a, &QAction::setEnabled);
a->setEnabled(false);
a->setText( i18n("Collapse all stacks" ));
a = actionCollection()->addAction( QString::fromLatin1("expandAllStacks"),
m_thumbnailView, SLOT(expandAllStacks()) );
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::expandAllStacksEnabled, a, &QAction::setEnabled);
a->setEnabled(false);
a->setText( i18n("Expand all stacks" ));
QActionGroup* grp = new QActionGroup( this );
a = actionCollection()->add( QString::fromLatin1("orderIncr"), this, SLOT(slotOrderIncr()) );
a->setText( i18n("Show &Oldest First") ) ;
a->setActionGroup(grp);
a->setChecked( !Settings::SettingsData::instance()->showNewestThumbnailFirst() );
a = actionCollection()->add( QString::fromLatin1("orderDecr"), this, SLOT(slotOrderDecr()) );
a->setText( i18n("Show &Newest First") );
a->setActionGroup(grp);
a->setChecked( Settings::SettingsData::instance()->showNewestThumbnailFirst() );
m_sortByDateAndTime = actionCollection()->addAction( QString::fromLatin1("sortImages"), this, SLOT(slotSortByDateAndTime()) );
m_sortByDateAndTime->setText( i18n("Sort Selected by Date && Time") );
m_limitToMarked = actionCollection()->addAction( QString::fromLatin1("limitToMarked"), this, SLOT(slotLimitToSelected()) );
m_limitToMarked->setText( i18n("Limit View to Selection") );
m_jumpToContext = actionCollection()->addAction( QString::fromLatin1("jumpToContext"), this, SLOT(slotJumpToContext()) );
m_jumpToContext->setText( i18n("Jump to Context") );
actionCollection()->setDefaultShortcut(m_jumpToContext, Qt::CTRL + Qt::Key_J);
m_jumpToContext->setIcon( QIcon::fromTheme( QString::fromLatin1( "kphotoalbum" ) ) ); // icon suggestion: go-jump (don't know the exact meaning though, so I didn't replace it right away
m_lock = actionCollection()->addAction( QString::fromLatin1("lockToDefaultScope"), this, SLOT(lockToDefaultScope()) );
m_lock->setText( i18n("Lock Images") );
m_unlock = actionCollection()->addAction( QString::fromLatin1("unlockFromDefaultScope"), this, SLOT(unlockFromDefaultScope()) );
m_unlock->setText( i18n("Unlock") );
a = actionCollection()->addAction( QString::fromLatin1("changeScopePasswd"), this, SLOT(changePassword()) );
a->setText( i18n("Change Password...") );
actionCollection()->setDefaultShortcut(a, 0);
m_setDefaultPos = actionCollection()->addAction( QString::fromLatin1("setDefaultScopePositive"), this, SLOT(setDefaultScopePositive()) );
m_setDefaultPos->setText( i18n("Lock Away All Other Items") );
m_setDefaultNeg = actionCollection()->addAction( QString::fromLatin1("setDefaultScopeNegative"), this, SLOT(setDefaultScopeNegative()) );
m_setDefaultNeg->setText( i18n("Lock Away Current Set of Items") );
// Maintenance
a = actionCollection()->addAction( QString::fromLatin1("findUnavailableImages"), this, SLOT(slotShowNotOnDisk()) );
a->setText( i18n("Display Images and Videos Not on Disk") );
a = actionCollection()->addAction( QString::fromLatin1("findImagesWithInvalidDate"), this, SLOT(slotShowImagesWithInvalidDate()) );
a->setText( i18n("Display Images and Videos with Incomplete Dates...") );
#ifdef DOES_STILL_NOT_WORK_IN_KPA4
a = actionCollection()->addAction( QString::fromLatin1("findImagesWithChangedMD5Sum"), this, SLOT(slotShowImagesWithChangedMD5Sum()) );
a->setText( i18n("Display Images and Videos with Changed MD5 Sum") );
#endif //DOES_STILL_NOT_WORK_IN_KPA4
a = actionCollection()->addAction( QLatin1String("mergeDuplicates"), this, SLOT(mergeDuplicates()));
a->setText(i18n("Merge duplicates"));
a = actionCollection()->addAction( QString::fromLatin1("rebuildMD5s"), this, SLOT(slotRecalcCheckSums()) );
a->setText( i18n("Recalculate Checksum") );
a = actionCollection()->addAction( QString::fromLatin1("rescan"), DB::ImageDB::instance(), SLOT(slotRescan()) );
a->setText( i18n("Rescan for Images and Videos") );
QAction* recreateExif = actionCollection()->addAction( QString::fromLatin1( "recreateExifDB" ), this, SLOT(slotRecreateExifDB()) );
recreateExif->setText( i18n("Recreate Exif Search Database") );
QAction* rereadExif = actionCollection()->addAction( QString::fromLatin1("reReadExifInfo"), this, SLOT(slotReReadExifInfo()) );
rereadExif->setText( i18n("Read EXIF Info From Files...") );
m_sortAllByDateAndTime = actionCollection()->addAction( QString::fromLatin1("sortAllImages"), this, SLOT(slotSortAllByDateAndTime()) );
m_sortAllByDateAndTime->setText( i18n("Sort All by Date && Time") );
m_sortAllByDateAndTime->setEnabled(true);
m_AutoStackImages = actionCollection()->addAction( QString::fromLatin1( "autoStack" ), this, SLOT (slotAutoStackImages()) );
m_AutoStackImages->setText( i18n("Automatically Stack Selected Images...") );
a = actionCollection()->addAction( QString::fromLatin1("buildThumbs"), this, SLOT(slotBuildThumbnails()) );
a->setText( i18n("Build Thumbnails") );
a->setText( i18n("Statistics...") );
m_markUntagged = actionCollection()->addAction(QString::fromUtf8("markUntagged"),
this, SLOT(slotMarkUntagged()));
m_markUntagged->setText(i18n("Mark As Untagged"));
// Settings
KStandardAction::preferences( this, SLOT(slotOptions()), actionCollection() );
KStandardAction::keyBindings( this, SLOT(slotConfigureKeyBindings()), actionCollection() );
KStandardAction::configureToolbars( this, SLOT(slotConfigureToolbars()), actionCollection() );
a = actionCollection()->addAction( QString::fromLatin1("readdAllMessages"), this, SLOT(slotReenableMessages()) );
a->setText( i18n("Enable All Messages") );
m_viewMenu = actionCollection()->add( QString::fromLatin1("configureView") );
m_viewMenu->setText( i18n("Configure Current View") );
m_viewMenu->setIcon( QIcon::fromTheme( QString::fromLatin1( "view-list-details" ) ) );
m_viewMenu->setDelayed( false );
QActionGroup* viewGrp = new QActionGroup( this );
viewGrp->setExclusive( true );
m_smallListView = actionCollection()->add( QString::fromLatin1("smallListView"), m_browser, SLOT(slotSmallListView()) );
m_smallListView->setText( i18n("Tree") );
m_viewMenu->addAction( m_smallListView );
m_smallListView->setActionGroup( viewGrp );
m_largeListView = actionCollection()->add( QString::fromLatin1("largelistview"), m_browser, SLOT(slotLargeListView()) );
m_largeListView->setText( i18n("Tree with User Icons") );
m_viewMenu->addAction( m_largeListView );
m_largeListView->setActionGroup( viewGrp );
m_largeIconView = actionCollection()->add( QString::fromLatin1("largeiconview"), m_browser, SLOT(slotLargeIconView()) );
m_largeIconView->setText( i18n("Icons") );
m_viewMenu->addAction( m_largeIconView );
m_largeIconView->setActionGroup( viewGrp );
connect(m_browser, &Browser::BrowserWidget::isViewChangeable, viewGrp, &QActionGroup::setEnabled);
connect(m_browser, &Browser::BrowserWidget::currentViewTypeChanged, this, &Window::slotUpdateViewMenu);
// The help menu
KStandardAction::tipOfDay( this, SLOT(showTipOfDay()), actionCollection() );
a = actionCollection()->add( QString::fromLatin1("showToolTipOnImages") );
a->setText( i18n("Show Tooltips in Thumbnails Window") );
actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_T);
connect(a, &QAction::toggled, m_thumbnailView, &ThumbnailView::ThumbnailFacade::showToolTipsOnImages);
a = actionCollection()->addAction( QString::fromLatin1("runDemo"), this, SLOT(runDemo()) );
a->setText( i18n("Run KPhotoAlbum Demo") );
a = actionCollection()->addAction( QString::fromLatin1("features"), this, SLOT(showFeatures()) );
a->setText( i18n("KPhotoAlbum Feature Status") );
a = actionCollection()->addAction( QString::fromLatin1("showVideo"), this, SLOT(showVideos()) );
a->setText( i18n( "Show Demo Videos") );
// Context menu actions
m_showExifDialog = actionCollection()->addAction( QString::fromLatin1("showExifInfo"), this, SLOT(slotShowExifInfo()) );
m_showExifDialog->setText( i18n("Show Exif Info") );
m_recreateThumbnails = actionCollection()->addAction( QString::fromLatin1("recreateThumbnails"), m_thumbnailView, SLOT(slotRecreateThumbnail()) );
m_recreateThumbnails->setText( i18n("Recreate Selected Thumbnails") );
m_useNextVideoThumbnail = actionCollection()->addAction( QString::fromLatin1("useNextVideoThumbnail"), this, SLOT(useNextVideoThumbnail()));
m_useNextVideoThumbnail->setText(i18n("Use next video thumbnail"));
actionCollection()->setDefaultShortcut(m_useNextVideoThumbnail, Qt::CTRL + Qt::Key_Plus);
m_usePreviousVideoThumbnail = actionCollection()->addAction( QString::fromLatin1("usePreviousVideoThumbnail"), this, SLOT(usePreviousVideoThumbnail()));
m_usePreviousVideoThumbnail->setText(i18n("Use previous video thumbnail"));
actionCollection()->setDefaultShortcut(m_usePreviousVideoThumbnail, Qt::CTRL + Qt::Key_Minus);
createGUI( QString::fromLatin1( "kphotoalbumui.rc" ) );
}
void MainWindow::Window::slotExportToHTML()
{
if ( ! m_htmlDialog )
m_htmlDialog = new HTMLGenerator::HTMLDialog( this );
m_htmlDialog->exec(selectedOnDisk());
}
void MainWindow::Window::startAutoSaveTimer()
{
int i = Settings::SettingsData::instance()->autoSave();
m_autoSaveTimer->stop();
if ( i != 0 ) {
m_autoSaveTimer->start( i * 1000 * 60 );
}
}
void MainWindow::Window::slotAutoSave()
{
if ( m_statusBar->mp_dirtyIndicator->isAutoSaveDirty() ) {
Utilities::ShowBusyCursor dummy;
m_statusBar->showMessage(i18n("Auto saving...."));
DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml"), true );
ImageManager::ThumbnailCache::instance()->save();
m_statusBar->showMessage(i18n("Auto saving.... Done"), 5000);
m_statusBar->mp_dirtyIndicator->autoSaved();
}
}
void MainWindow::Window::showThumbNails()
{
m_statusBar->showThumbnailSlider();
reloadThumbnails( ThumbnailView::ClearSelection );
m_stack->setCurrentWidget( m_thumbnailView->gui() );
m_thumbnailView->gui()->setFocus();
updateStates( true );
}
void MainWindow::Window::showBrowser()
{
m_statusBar->clearMessage();
m_statusBar->hideThumbnailSlider();
m_stack->setCurrentWidget( m_browser );
m_browser->setFocus();
updateContextMenuFromSelectionSize( 0 );
updateStates( false );
}
void MainWindow::Window::slotOptionGroupChanged()
{
// FIXME: What if annotation dialog is open? (if that's possible)
delete m_annotationDialog;
m_annotationDialog = nullptr;
DirtyIndicator::markDirty();
}
void MainWindow::Window::showTipOfDay()
{
KTipDialog::showTip( this, QString(), true );
}
void MainWindow::Window::runDemo()
{
KProcess* process = new KProcess;
*process << QLatin1String("kphotoalbum") << QLatin1String("--demo");
process->startDetached();
}
bool MainWindow::Window::load()
{
// Let first try to find a config file.
QString configFile;
QUrl dbFileUrl = Options::the()->dbFile();
if ( !dbFileUrl.isEmpty() && dbFileUrl.isLocalFile() )
{
configFile = dbFileUrl.toLocalFile();
}
else if ( Options::the()->demoMode() )
{
configFile = Utilities::setupDemo();
}
else {
bool showWelcome = false;
KConfigGroup config = KSharedConfig::openConfig()->group(QString::fromUtf8("General"));
if ( config.hasKey( QString::fromLatin1("imageDBFile") ) ) {
configFile = config.readEntry( QString::fromLatin1("imageDBFile"), QString() );
if ( !QFileInfo( configFile ).exists() )
showWelcome = true;
}
else
showWelcome = true;
if ( showWelcome ) {
SplashScreen::instance()->hide();
configFile = welcome();
}
}
if ( configFile.isNull() )
return false;
if (configFile.startsWith( QString::fromLatin1( "~" ) ) )
configFile = QDir::home().path() + QString::fromLatin1( "/" ) + configFile.mid(1);
// To avoid a race conditions where both the image loader thread creates an instance of
// Settings, and where the main thread crates an instance, we better get it created now.
Settings::SettingsData::setup( QFileInfo( configFile ).absolutePath() );
if ( Settings::SettingsData::instance()->showSplashScreen() ) {
SplashScreen::instance()->show();
qApp->processEvents();
}
// Doing some validation on user provided index file
if ( Options::the()->dbFile().isValid() ) {
QFileInfo fi( configFile );
if ( !fi.dir().exists() ) {
KMessageBox::error( this, i18n("Could not open given index.xml as provided directory does not exist.
%1
",
fi.absolutePath()) );
return false;
}
// We use index.xml as the XML backend, thus we want to test for exactly it
fi.setFile( QString::fromLatin1( "%1/index.xml" ).arg( fi.dir().absolutePath() ) );
if ( !fi.exists() ) {
int answer = KMessageBox::questionYesNo(this,i18n("Given index file does not exist, do you want to create following?"
"
%1/index.xml
", fi.absolutePath() ) );
if (answer != KMessageBox::Yes)
return false;
}
configFile = fi.absoluteFilePath();
}
DB::ImageDB::setupXMLDB( configFile );
// some sanity checks:
if ( ! Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()
&& ! (Settings::SettingsData::instance()->untaggedCategory().isEmpty()
&& Settings::SettingsData::instance()->untaggedTag().isEmpty() )
&& ! Options::the()->demoMode() )
{
KMessageBox::error( this, i18n(
"You have configured a tag for untagged images, but either the tag itself "
"or its category does not exist in the database.
"
"Please review your untagged tag setting under "
"Settings|Configure KPhotoAlbum...|Categories
"));
}
return true;
}
void MainWindow::Window::contextMenuEvent( QContextMenuEvent* e )
{
if ( m_stack->currentWidget() == m_thumbnailView->gui() ) {
QMenu menu( this );
menu.addAction( m_configOneAtATime );
menu.addAction( m_configAllSimultaniously );
menu.addSeparator();
menu.addAction( m_createImageStack );
menu.addAction( m_unStackImages );
menu.addAction( m_setStackHead );
menu.addSeparator();
menu.addAction( m_runSlideShow );
menu.addAction(m_runRandomSlideShow );
menu.addAction( m_showExifDialog);
menu.addSeparator();
menu.addAction(m_rotLeft);
menu.addAction(m_rotRight);
menu.addAction(m_recreateThumbnails);
menu.addAction(m_useNextVideoThumbnail);
menu.addAction(m_usePreviousVideoThumbnail);
m_useNextVideoThumbnail->setEnabled(anyVideosSelected());
m_usePreviousVideoThumbnail->setEnabled(anyVideosSelected());
menu.addSeparator();
menu.addAction(m_view);
menu.addAction(m_viewInNewWindow);
// "Invoke external program"
ExternalPopup externalCommands { &menu };
DB::ImageInfoPtr info = m_thumbnailView->mediaIdUnderCursor().info();
externalCommands.populate( info, selected());
QAction* action = menu.addMenu( &externalCommands );
if (!info && selected().isEmpty())
action->setEnabled( false );
QUrl selectedFile = QUrl::fromLocalFile(info->fileName().absolute());
QList allSelectedFiles;
for (const QString &selectedFile : selected().toStringList(DB::AbsolutePath)) {
allSelectedFiles << QUrl::fromLocalFile(selectedFile);
}
// "Copy image(s) to ..."
CopyPopup copyMenu (&menu, selectedFile, allSelectedFiles, m_lastTarget, CopyPopup::Copy);
QAction *copyAction = menu.addMenu(©Menu);
if (!info && selected().isEmpty()) {
copyAction->setEnabled(false);
}
// "Link image(s) to ..."
CopyPopup linkMenu (&menu, selectedFile, allSelectedFiles, m_lastTarget, CopyPopup::Link);
QAction *linkAction = menu.addMenu(&linkMenu);
if (!info && selected().isEmpty()) {
linkAction->setEnabled(false);
}
menu.exec( QCursor::pos() );
}
e->setAccepted(true);
}
void MainWindow::Window::setDefaultScopePositive()
{
Settings::SettingsData::instance()->setCurrentLock( m_browser->currentContext(), false );
}
void MainWindow::Window::setDefaultScopeNegative()
{
Settings::SettingsData::instance()->setCurrentLock( m_browser->currentContext(), true );
}
void MainWindow::Window::lockToDefaultScope()
{
int i = KMessageBox::warningContinueCancel( this,
i18n( "The password protection is only a means of allowing your little sister "
"to look in your images, without getting to those embarrassing images from "
"your last party.
"
"In other words, anyone with access to the index.xml file can easily "
"circumvent this password.
"),
i18n("Password Protection"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QString::fromLatin1( "lockPassWordIsNotEncruption" ) );
if ( i == KMessageBox::Cancel )
return;
setLocked( true, false );
}
void MainWindow::Window::unlockFromDefaultScope()
{
bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
QPointer dialog = new KPasswordDialog( this );
while ( !OK ) {
dialog->setPrompt( i18n("Type in Password to Unlock") );
const int code = dialog->exec();
if ( code == QDialog::Rejected )
return;
const QString passwd = dialog->password();
OK = (Settings::SettingsData::instance()->password() == passwd);
if ( !OK )
KMessageBox::sorry( this, i18n("Invalid password.") );
}
setLocked( false, false );
delete dialog;
}
void MainWindow::Window::setLocked( bool locked, bool force, bool recount )
{
m_statusBar->setLocked( locked );
Settings::SettingsData::instance()->setLocked( locked, force );
m_lock->setEnabled( !locked );
m_unlock->setEnabled( locked );
m_setDefaultPos->setEnabled( !locked );
m_setDefaultNeg->setEnabled( !locked );
if (recount)
m_browser->reload();
}
void MainWindow::Window::changePassword()
{
bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
QPointer dialog = new KPasswordDialog;
while ( !OK ) {
dialog->setPrompt( i18n("Type in Old Password") );
const int code = dialog->exec();
if ( code == QDialog::Rejected )
return;
const QString passwd = dialog->password();
OK = (Settings::SettingsData::instance()->password() == QString(passwd));
if ( !OK )
KMessageBox::sorry( this, i18n("Invalid password.") );
}
dialog->setPrompt( i18n("Type in New Password") );
const int code = dialog->exec();
if ( code == QDialog::Accepted )
Settings::SettingsData::instance()->setPassword( dialog->password() );
delete dialog;
}
void MainWindow::Window::slotConfigureKeyBindings()
{
Viewer::ViewerWidget* viewer = new Viewer::ViewerWidget; // Do not show, this is only used to get a key configuration
KShortcutsDialog* dialog = new KShortcutsDialog();
dialog->addCollection( actionCollection(), i18n( "General" ) );
dialog->addCollection( viewer->actions(), i18n("Viewer") );
#ifdef HASKIPI
loadPlugins();
Q_FOREACH( const KIPI::PluginLoader::Info *pluginInfo, m_pluginLoader->pluginList() ) {
KIPI::Plugin* plugin = pluginInfo->plugin();
if ( plugin )
dialog->addCollection( plugin->actionCollection(),
i18nc("Add 'Plugin' prefix so that KIPI plugins are obvious in KShortcutsDialog…","Plugin: %1", pluginInfo->name()) );
}
#endif
createAnnotationDialog();
dialog->addCollection( m_annotationDialog->actions(), i18n("Annotation Dialog" ) );
dialog->configure();
delete dialog;
delete viewer;
}
void MainWindow::Window::slotSetFileName( const DB::FileName& fileName )
{
ImageInfoPtr info;
if ( fileName.isNull() )
m_statusBar->clearMessage();
else {
info = fileName.info();
if (info != ImageInfoPtr(nullptr) )
m_statusBar->showMessage( fileName.absolute(), 4000 );
}
}
void MainWindow::Window::updateContextMenuFromSelectionSize(int selectionSize)
{
m_configAllSimultaniously->setEnabled(selectionSize > 1);
m_configOneAtATime->setEnabled(selectionSize >= 1);
m_createImageStack->setEnabled(selectionSize > 1);
m_unStackImages->setEnabled(selectionSize >= 1);
m_setStackHead->setEnabled(selectionSize == 1); // FIXME: do we want to check if it's stacked here?
m_sortByDateAndTime->setEnabled(selectionSize > 1);
m_recreateThumbnails->setEnabled(selectionSize >= 1);
m_rotLeft->setEnabled(selectionSize >= 1);
m_rotRight->setEnabled(selectionSize >= 1);
m_AutoStackImages->setEnabled(selectionSize > 1);
m_markUntagged->setEnabled(selectionSize >= 1);
m_statusBar->mp_selected->setSelectionCount( selectionSize );
}
void MainWindow::Window::rotateSelected( int angle )
{
const DB::FileNameList list = selected();
if (list.isEmpty()) {
KMessageBox::sorry( this, i18n("No item is selected."),
i18n("No Selection") );
} else {
Q_FOREACH(const DB::FileName& fileName, list) {
fileName.info()->rotate(angle);
ImageManager::ThumbnailCache::instance()->removeThumbnail(fileName);
}
m_statusBar->mp_dirtyIndicator->markDirty();
}
}
void MainWindow::Window::slotRotateSelectedLeft()
{
rotateSelected( -90 );
reloadThumbnails();
}
void MainWindow::Window::slotRotateSelectedRight()
{
rotateSelected( 90 );
reloadThumbnails();
}
void MainWindow::Window::reloadThumbnails( ThumbnailView::SelectionUpdateMethod method )
{
m_thumbnailView->reload( method );
updateContextMenuFromSelectionSize( m_thumbnailView->selection().size() );
}
void MainWindow::Window::slotUpdateViewMenu( DB::Category::ViewType type )
{
if ( type == DB::Category::TreeView )
m_smallListView->setChecked( true );
else if ( type == DB::Category::ThumbedTreeView )
m_largeListView->setChecked( true );
else if ( type == DB::Category::ThumbedIconView )
m_largeIconView->setChecked( true );
}
void MainWindow::Window::slotShowNotOnDisk()
{
DB::FileNameList notOnDisk;
Q_FOREACH(const DB::FileName& fileName, DB::ImageDB::instance()->images()) {
if ( !fileName.exists() )
notOnDisk.append(fileName);
}
showThumbNails(notOnDisk);
}
void MainWindow::Window::slotShowImagesWithChangedMD5Sum()
{
#ifdef DOES_STILL_NOT_WORK_IN_KPA4
Utilities::ShowBusyCursor dummy;
StringSet changed = DB::ImageDB::instance()->imagesWithMD5Changed();
showThumbNails( changed.toList() );
#else // DOES_STILL_NOT_WORK_IN_KPA4
qFatal("Code commented out in MainWindow::Window::slotShowImagesWithChangedMD5Sum");
#endif // DOES_STILL_NOT_WORK_IN_KPA4
}
void MainWindow::Window::updateStates( bool thumbNailView )
{
m_selectAll->setEnabled( thumbNailView );
m_deleteSelected->setEnabled( thumbNailView );
m_limitToMarked->setEnabled( thumbNailView );
m_jumpToContext->setEnabled( thumbNailView );
}
void MainWindow::Window::slotRunSlideShow()
{
slotView( true, true );
}
void MainWindow::Window::slotRunRandomizedSlideShow()
{
slotView( true, true, true );
}
MainWindow::Window* MainWindow::Window::theMainWindow()
{
Q_ASSERT( s_instance );
return s_instance;
}
void MainWindow::Window::slotConfigureToolbars()
{
QPointer dlg = new KEditToolBar(guiFactory());
connect(dlg, SIGNAL(newToolbarConfig()),
SLOT(slotNewToolbarConfig()));
dlg->exec();
delete dlg;
}
void MainWindow::Window::slotNewToolbarConfig()
{
createGUI();
createSarchBar();
}
void MainWindow::Window::slotImport()
{
ImportExport::Import::imageImport();
}
void MainWindow::Window::slotExport()
{
ImportExport::Export::imageExport(selectedOnDisk());
}
void MainWindow::Window::slotReenableMessages()
{
int ret = KMessageBox::questionYesNo( this, i18n("Really enable all message boxes where you previously "
"checked the do-not-show-again check box?
" ) );
if ( ret == KMessageBox::Yes )
KMessageBox::enableAllMessages();
}
void MainWindow::Window::setupPluginMenu()
{
QMenu* menu = findChild( QString::fromLatin1("plugins") );
if ( !menu ) {
KMessageBox::error( this, i18n("KPhotoAlbum hit an internal error (missing plug-in menu in MainWindow::Window::setupPluginMenu). This indicate that you forgot to do a make install. If you did compile KPhotoAlbum yourself, then please run make install. If not, please report this as a bug.
KPhotoAlbum will continue execution, but it is not entirely unlikely that it will crash later on due to the missing make install.
" ), i18n("Internal Error") );
m_hasLoadedPlugins = true;
return; // This is no good, but lets try and continue.
}
#ifdef HASKIPI
connect(menu, &QMenu::aboutToShow, this, &Window::loadPlugins);
m_hasLoadedPlugins = false;
#else
menu->setEnabled(false);
m_hasLoadedPlugins = true;
#endif
}
void MainWindow::Window::loadPlugins()
{
#ifdef HASKIPI
Utilities::ShowBusyCursor dummy;
if ( m_hasLoadedPlugins )
return;
m_pluginInterface = new Plugins::Interface( this, QString::fromLatin1("KPhotoAlbum kipi interface") );
connect(m_pluginInterface, &Plugins::Interface::imagesChanged, this, &Window::slotImagesChanged);
QStringList ignores;
ignores << QString::fromLatin1( "CommentsEditor" )
<< QString::fromLatin1( "HelloWorld" );
m_pluginLoader = new KIPI::PluginLoader();
m_pluginLoader->setIgnoredPluginsList( ignores );
m_pluginLoader->setInterface( m_pluginInterface );
m_pluginLoader->init();
connect(m_pluginLoader, &KIPI::PluginLoader::replug, this, &Window::plug);
m_pluginLoader->loadPlugins();
// Setup signals
connect(m_thumbnailView, &ThumbnailView::ThumbnailFacade::selectionChanged, this, &Window::slotSelectionChanged);
m_hasLoadedPlugins = true;
// Make sure selection is updated also when plugin loading is
// delayed. This is needed, because selection might already be
// non-empty when loading the plugins.
slotSelectionChanged(selected().size());
#endif // HASKIPI
}
void MainWindow::Window::plug()
{
#ifdef HASKIPI
unplugActionList( QString::fromLatin1("import_actions") );
unplugActionList( QString::fromLatin1("export_actions") );
unplugActionList( QString::fromLatin1("image_actions") );
unplugActionList( QString::fromLatin1("tool_actions") );
unplugActionList( QString::fromLatin1("batch_actions") );
QList importActions;
QList exportActions;
QList imageActions;
QList toolsActions;
QList batchActions;
KIPI::PluginLoader::PluginList list = m_pluginLoader->pluginList();
Q_FOREACH( const KIPI::PluginLoader::Info *pluginInfo, list ) {
KIPI::Plugin* plugin = pluginInfo->plugin();
if ( !plugin || !pluginInfo->shouldLoad() )
continue;
plugin->setup( this );
QList actions = plugin->actions();
Q_FOREACH( QAction *action, actions ) {
KIPI::Category category = plugin->category( action );
if ( category == KIPI::ImagesPlugin || category == KIPI::CollectionsPlugin )
imageActions.append( action );
else if ( category == KIPI::ImportPlugin )
importActions.append( action );
else if ( category == KIPI::ExportPlugin )
exportActions.append( action );
else if ( category == KIPI::ToolsPlugin )
toolsActions.append( action );
else if ( category == KIPI::BatchPlugin )
batchActions.append( action );
else {
qCWarning(MainWindowLog) << "Unknown category\n";
}
}
KConfigGroup group = KSharedConfig::openConfig()->group( QString::fromLatin1("Shortcuts") );
plugin->actionCollection()->importGlobalShortcuts( &group );
}
setPluginMenuState( "importplugin", importActions );
setPluginMenuState( "exportplugin", exportActions );
setPluginMenuState( "imagesplugins", imageActions );
setPluginMenuState( "batch_plugins", batchActions );
setPluginMenuState( "tool_plugins", toolsActions );
// For this to work I need to pass false as second arg for createGUI
plugActionList( QString::fromLatin1("import_actions"), importActions );
plugActionList( QString::fromLatin1("export_actions"), exportActions );
plugActionList( QString::fromLatin1("image_actions"), imageActions );
plugActionList( QString::fromLatin1("tool_actions"), toolsActions );
plugActionList( QString::fromLatin1("batch_actions"), batchActions );
#endif
}
void MainWindow::Window::setPluginMenuState( const char* name, const QList& actions )
{
QMenu* menu = findChild( QString::fromLatin1(name) );
if ( menu )
menu->setEnabled(actions.count() != 0);
}
void MainWindow::Window::slotImagesChanged( const QList& urls )
{
for( QList::ConstIterator it = urls.begin(); it != urls.end(); ++it ) {
DB::FileName fileName = DB::FileName::fromAbsolutePath((*it).path());
if ( !fileName.isNull()) {
// Plugins may report images outsite of the photodatabase
// This seems to be the case with the border image plugin, which reports the destination image
ImageManager::ThumbnailCache::instance()->removeThumbnail( fileName );
// update MD5sum:
MD5 md5sum = Utilities::MD5Sum( fileName );
fileName.info()->setMD5Sum( md5sum );
}
}
m_statusBar->mp_dirtyIndicator->markDirty();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
DB::ImageSearchInfo MainWindow::Window::currentContext()
{
return m_browser->currentContext();
}
QString MainWindow::Window::currentBrowseCategory() const
{
return m_browser->currentCategory();
}
void MainWindow::Window::slotSelectionChanged( int count )
{
#ifdef HASKIPI
m_pluginInterface->slotSelectionChanged( count != 0 );
#else
Q_UNUSED( count );
#endif
}
void MainWindow::Window::resizeEvent( QResizeEvent* )
{
if ( Settings::SettingsData::ready() && isVisible() )
Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
}
void MainWindow::Window::moveEvent( QMoveEvent * )
{
if ( Settings::SettingsData::ready() && isVisible() )
Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
}
void MainWindow::Window::slotRemoveTokens()
{
if ( !m_tokenEditor )
m_tokenEditor = new TokenEditor( this );
m_tokenEditor->show();
connect(m_tokenEditor, &TokenEditor::finished, m_browser, &Browser::BrowserWidget::go);
}
void MainWindow::Window::slotShowListOfFiles()
{
QStringList list = QInputDialog::getMultiLineText( this,
i18n("Open List of Files"),
i18n("You can open a set of files from KPhotoAlbum's image root by listing the files here.")
)
.split( QChar::fromLatin1('\n'), QString::SkipEmptyParts );
if ( list.isEmpty() )
return;
DB::FileNameList out;
for ( QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) {
QString fileNameStr = Utilities::imageFileNameToAbsolute( *it );
if ( fileNameStr.isNull() )
continue;
const DB::FileName fileName = DB::FileName::fromAbsolutePath(fileNameStr);
if ( !fileName.isNull() )
out.append(fileName);
}
if (out.isEmpty())
KMessageBox::sorry( this, i18n("No images matching your input were found."), i18n("No Matches") );
else
showThumbNails(out);
}
void MainWindow::Window::updateDateBar( const Browser::BreadcrumbList& path )
{
static QString lastPath = QString::fromLatin1("ThisStringShouldNeverBeSeenSoWeUseItAsInitialContent");
if ( path.toString() != lastPath )
updateDateBar();
lastPath = path.toString();
}
void MainWindow::Window::updateDateBar()
{
m_dateBar->setImageDateCollection( DB::ImageDB::instance()->rangeCollection() );
}
void MainWindow::Window::slotShowImagesWithInvalidDate()
{
QPointer finder = new InvalidDateFinder( this );
if ( finder->exec() == QDialog::Accepted )
showThumbNails();
delete finder;
}
void MainWindow::Window::showDateBarTip( const QString& msg )
{
m_statusBar->showMessage( msg, 3000 );
}
void MainWindow::Window::slotJumpToContext()
{
const DB::FileName fileName =m_thumbnailView->currentItem();
if ( !fileName.isNull() ) {
m_browser->addImageView(fileName);
}
}
void MainWindow::Window::setDateRange( const DB::ImageDate& range )
{
DB::ImageDB::instance()->setDateRange( range, m_dateBar->includeFuzzyCounts() );
m_statusBar->mp_partial->showBrowserMatches( this->selected().size() );
m_browser->reload();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::clearDateRange()
{
DB::ImageDB::instance()->clearDateRange();
m_browser->reload();
reloadThumbnails( ThumbnailView::MaintainSelection );
}
void MainWindow::Window::showThumbNails(const DB::FileNameList& items)
{
m_thumbnailView->setImageList(items);
m_statusBar->mp_partial->setMatchCount(items.size());
showThumbNails();
}
void MainWindow::Window::slotRecalcCheckSums()
{
DB::ImageDB::instance()->slotRecalcCheckSums( selected() );
}
void MainWindow::Window::slotShowExifInfo()
{
DB::FileNameList items = selectedOnDisk();
if (!items.isEmpty()) {
Exif::InfoDialog* exifDialog = new Exif::InfoDialog(items.at(0), this);
exifDialog->show();
}
}
void MainWindow::Window::showFeatures()
{
FeatureDialog dialog(this);
dialog.exec();
}
void MainWindow::Window::showImage( const DB::FileName& fileName )
{
launchViewer(DB::FileNameList() << fileName, true, false, false);
}
void MainWindow::Window::slotBuildThumbnails()
{
ImageManager::ThumbnailBuilder::instance()->buildAll( ImageManager::StartNow );
}
void MainWindow::Window::slotBuildThumbnailsIfWanted()
{
ImageManager::ThumbnailCache::instance()->flush();
if ( ! Settings::SettingsData::instance()->incrementalThumbnails())
ImageManager::ThumbnailBuilder::instance()->buildAll( ImageManager::StartDelayed );
}
void MainWindow::Window::slotOrderIncr()
{
m_thumbnailView->setSortDirection( ThumbnailView::OldestFirst );
}
void MainWindow::Window::slotOrderDecr()
{
m_thumbnailView->setSortDirection( ThumbnailView::NewestFirst );
}
void MainWindow::Window::showVideos()
{
#if (KIO_VERSION >= ((5<<16)|(31<<8)|(0)))
KRun::runUrl(QUrl(QString::fromLatin1("http://www.kphotoalbum.org/index.php?page=videos"))
, QString::fromLatin1( "text/html" )
, this
, KRun::RunFlags()
);
#else
// this signature is deprecated in newer kio versions
// TODO: remove this when we don't support Ubuntu 16.04 LTS anymore
KRun::runUrl(QUrl(QString::fromLatin1("http://www.kphotoalbum.org/index.php?page=videos"))
, QString::fromLatin1( "text/html" )
, this
);
#endif
}
void MainWindow::Window::slotStatistics()
{
static StatisticsDialog* dialog = new StatisticsDialog(this);
dialog->show();
}
void MainWindow::Window::slotMarkUntagged()
{
if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()) {
for (const DB::FileName& newFile : selected()) {
newFile.info()->addCategoryInfo(Settings::SettingsData::instance()->untaggedCategory(),
Settings::SettingsData::instance()->untaggedTag());
}
DirtyIndicator::markDirty();
} else {
// Note: the same dialog text is used in
// Browser::OverviewPage::activateUntaggedImagesAction(),
// so if it is changed, be sure to also change it there!
KMessageBox::information(this,
i18n("You have not yet configured which tag to use for indicating untagged images."
"
"
"Please follow these steps to do so:"
"
- In the menu bar choose Settings
"
"- From there choose Configure KPhotoAlbum
"
"- Now choose the Categories icon
"
"- Now configure section Untagged Images
"),
i18n("Feature has not been configured")
);
}
}
void MainWindow::Window::setupStatusBar()
{
m_statusBar = new MainWindow::StatusBar;
setStatusBar( m_statusBar );
setLocked( Settings::SettingsData::instance()->locked(), true, false );
}
void MainWindow::Window::slotRecreateExifDB()
{
Exif::Database::instance()->recreate();
}
void MainWindow::Window::useNextVideoThumbnail()
{
UpdateVideoThumbnail::useNext(selected());
}
void MainWindow::Window::usePreviousVideoThumbnail()
{
UpdateVideoThumbnail::usePrevious(selected());
}
void MainWindow::Window::mergeDuplicates()
{
DuplicateMerger* merger = new DuplicateMerger;
merger->show();
}
void MainWindow::Window::slotThumbnailSizeChanged()
{
QString thumbnailSizeMsg = i18nc( "@info:status",
//xgettext:no-c-format
"Thumbnail width: %1px (storage size: %2px)",
Settings::SettingsData::instance()->actualThumbnailSize(),
Settings::SettingsData::instance()->thumbnailSize()
);
m_statusBar->showMessage( thumbnailSizeMsg, 4000);
}
void MainWindow::Window::createSarchBar()
{
// Set up the search tool bar
SearchBar* bar = new SearchBar( this );
bar->setLineEditEnabled(false);
bar->setObjectName(QString::fromUtf8("searchBar"));
connect(bar, &SearchBar::textChanged, m_browser, &Browser::BrowserWidget::slotLimitToMatch);
connect(bar, &SearchBar::returnPressed, m_browser, &Browser::BrowserWidget::slotInvokeSeleted);
connect(bar, &SearchBar::keyPressed, m_browser, &Browser::BrowserWidget::scrollKeyPressed);
connect(m_browser, &Browser::BrowserWidget::viewChanged, bar, &SearchBar::reset);
connect(m_browser, &Browser::BrowserWidget::isSearchable, bar, &SearchBar::setLineEditEnabled);
}
void MainWindow::Window::executeStartupActions()
{
new ImageManager::ThumbnailBuilder( m_statusBar, this );
if ( ! Settings::SettingsData::instance()->incrementalThumbnails())
ImageManager::ThumbnailBuilder::instance()->buildMissing();
connect( Settings::SettingsData::instance(), SIGNAL(thumbnailSizeChanged(int)), this, SLOT(slotBuildThumbnailsIfWanted()) );
if ( ! FeatureDialog::hasVideoThumbnailer() ) {
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutLengthInfo );
BackgroundTaskManager::JobManager::instance()->addJob(
new BackgroundJobs::SearchForVideosWithoutVideoThumbnailsJob );
}
}
void MainWindow::Window::checkIfMplayerIsInstalled()
{
if (Options::the()->demoMode())
return;
if ( !FeatureDialog::hasVideoThumbnailer() ) {
KMessageBox::information( this,
i18n("Unable to find ffmpeg or MPlayer on the system.
"
"Without either of these, KPhotoAlbum will not be able to display video thumbnails and video lengths. "
"Please install the ffmpeg or MPlayer package
"),
i18n("Video thumbnails are not available"), QString::fromLatin1("mplayerNotInstalled"));
} else {
KMessageBox::enableMessage( QString::fromLatin1("mplayerNotInstalled") );
if ( FeatureDialog::ffmpegBinary().isEmpty() && !FeatureDialog::isMplayer2() ) {
KMessageBox::information( this,
i18n("You have MPlayer installed on your system, but it is unfortunately not version 2. "
"MPlayer2 is on most systems a separate package, please install that if at all possible, "
"as that version has much better support for extracting thumbnails from videos.
"),
i18n("MPlayer is too old"), QString::fromLatin1("mplayerVersionTooOld"));
} else
KMessageBox::enableMessage( QString::fromLatin1("mplayerVersionTooOld") );
}
}
bool MainWindow::Window::anyVideosSelected() const
{
Q_FOREACH(const DB::FileName& fileName, selected()) {
if ( Utilities::isVideo(fileName))
return true;
}
return false;
}
void MainWindow::Window::announceAndroidVersion()
{
// Don't bother people with this information when they are starting KPA the first time
if (DB::ImageDB::instance()->totalCount() < 100)
return;
const QString doNotShowKey = QString::fromLatin1( "announce_android_version_key" );
const QString txt = i18n("Did you know that there is an Android client for KPhotoAlbum?
"
"With the Android client you can view your images from your desktop.
"
"See youtube video or "
"install from google play
" );
KMessageBox::information( this, txt, QString(), doNotShowKey, KMessageBox::AllowLink );
}
void MainWindow::Window::setHistogramVisibilty( bool visible ) const
{
if (visible)
{
m_dateBar->show();
m_dateBarLine->show();
}
else
{
m_dateBar->hide();
m_dateBarLine->hide();
}
}
void MainWindow::Window::slotImageRotated(const DB::FileName& fileName)
{
// An image has been rotated by the annotation dialog or the viewer.
// We have to reload the respective thumbnail to get it in the right angle
ImageManager::ThumbnailCache::instance()->removeThumbnail(fileName);
}
bool MainWindow::Window::dbIsDirty() const
{
return m_statusBar->mp_dirtyIndicator->isSaveDirty();
}
#ifdef HAVE_KGEOMAP
void MainWindow::Window::showPositionBrowser()
{
Browser::PositionBrowserWidget *positionBrowser = positionBrowserWidget();
m_stack->setCurrentWidget(positionBrowser);
updateStates( false );
}
Browser::PositionBrowserWidget* MainWindow::Window::positionBrowserWidget()
{
if (m_positionBrowser == 0) {
m_positionBrowser = createPositionBrowser();
}
return m_positionBrowser;
}
Browser::PositionBrowserWidget* MainWindow::Window::createPositionBrowser()
{
Browser::PositionBrowserWidget* widget = new Browser::PositionBrowserWidget(m_stack);
m_stack->addWidget(widget);
return widget;
}
#endif
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Settings/CategoryPage.cpp b/Settings/CategoryPage.cpp
index 6a20653d..7fee9121 100644
--- a/Settings/CategoryPage.cpp
+++ b/Settings/CategoryPage.cpp
@@ -1,563 +1,563 @@
/* Copyright (C) 2003-2014 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "CategoryPage.h"
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE includes
#include
#include
#include
// Local includes
#include "DB/ImageDB.h"
#include "DB/CategoryCollection.h"
#include "DB/MemberMap.h"
#include "MainWindow/Window.h"
#include "MainWindow/DirtyIndicator.h"
#include "UntaggedGroupBox.h"
#include "SettingsDialog.h"
#include "CategoryItem.h"
Settings::CategoryPage::CategoryPage(QWidget* parent) : QWidget(parent)
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
// The category settings
QGroupBox* categoryGroupBox = new QGroupBox;
mainLayout->addWidget(categoryGroupBox);
categoryGroupBox->setTitle(i18n("Category Settings"));
QHBoxLayout* categoryLayout = new QHBoxLayout(categoryGroupBox);
// Category list
QVBoxLayout* categorySideLayout = new QVBoxLayout;
categoryLayout->addLayout(categorySideLayout);
m_categoriesListWidget = new QListWidget;
connect(m_categoriesListWidget, &QListWidget::itemClicked, this, &CategoryPage::editCategory);
connect(m_categoriesListWidget, &QListWidget::itemSelectionChanged, this, &CategoryPage::editSelectedCategory);
connect(m_categoriesListWidget, &QListWidget::itemChanged, this, &CategoryPage::categoryNameChanged);
// This is needed to fix some odd behavior if the "New" button is double clicked
connect(m_categoriesListWidget, &QListWidget::itemDoubleClicked, this, &CategoryPage::categoryDoubleClicked);
connect(m_categoriesListWidget->itemDelegate(), SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)),
this, SLOT(listWidgetEditEnd(QWidget*,QAbstractItemDelegate::EndEditHint)));
categorySideLayout->addWidget(m_categoriesListWidget);
// New, Delete, and buttons
QHBoxLayout* newDeleteRenameLayout = new QHBoxLayout;
categorySideLayout->addLayout(newDeleteRenameLayout);
m_newCategoryButton = new QPushButton(i18n("New"));
connect(m_newCategoryButton, &QPushButton::clicked, this, &CategoryPage::newCategory);
newDeleteRenameLayout->addWidget(m_newCategoryButton);
m_delItem = new QPushButton(i18n("Delete"));
connect(m_delItem, &QPushButton::clicked, this, &CategoryPage::deleteCurrentCategory);
newDeleteRenameLayout->addWidget(m_delItem);
m_renameItem = new QPushButton(i18n("Rename"));
connect(m_renameItem, &QPushButton::clicked, this, &CategoryPage::renameCurrentCategory);
newDeleteRenameLayout->addWidget(m_renameItem);
// Category settings
QVBoxLayout* rightSideLayout = new QVBoxLayout;
categoryLayout->addLayout(rightSideLayout);
// Header
m_categoryLabel = new QLabel;
m_categoryLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
rightSideLayout->addWidget(m_categoryLabel);
// Pending rename label
m_renameLabel = new QLabel;
m_renameLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
rightSideLayout->addWidget(m_renameLabel);
QDialog *parentDialog = qobject_cast(parent);
connect( parentDialog, &QDialog::rejected, m_renameLabel, &QLabel::clear);
// Some space looks better here :-)
QLabel* spacer = new QLabel;
rightSideLayout->addWidget(spacer);
// Here we start with the actual settings
QGridLayout* settingsLayout = new QGridLayout;
rightSideLayout->addLayout(settingsLayout);
int row = 0;
// Positionable
m_positionableLabel = new QLabel(i18n("Positionable tags:"));
settingsLayout->addWidget(m_positionableLabel, row, 0);
m_positionable = new QCheckBox(i18n("Tags in this category can be associated with areas within images"));
settingsLayout->addWidget(m_positionable, row, 1);
connect(m_positionable, &QCheckBox::clicked, this, &CategoryPage::positionableChanged);
row++;
// Icon
m_iconLabel = new QLabel(i18n("Icon:"));
settingsLayout->addWidget(m_iconLabel, row, 0);
m_icon = new KIconButton;
settingsLayout->addWidget(m_icon, row, 1);
m_icon->setIconSize(32);
m_icon->setIcon(QString::fromUtf8("personsIcon"));
connect(m_icon, &KIconButton::iconChanged, this, &CategoryPage::iconChanged);
row++;
// Thumbnail size
m_thumbnailSizeInCategoryLabel = new QLabel(i18n("Thumbnail Size:"));
settingsLayout->addWidget(m_thumbnailSizeInCategoryLabel, row, 0);
m_thumbnailSizeInCategory = new QSpinBox;
m_thumbnailSizeInCategory->setRange(32, 512);
m_thumbnailSizeInCategory->setSingleStep(32);
settingsLayout->addWidget(m_thumbnailSizeInCategory, row, 1);
connect(m_thumbnailSizeInCategory, static_cast(&QSpinBox::valueChanged), this, &CategoryPage::thumbnailSizeChanged);
row++;
// Preferred View
m_preferredViewLabel = new QLabel(i18n("Preferred view:"));
settingsLayout->addWidget(m_preferredViewLabel, row, 0);
m_preferredView = new QComboBox;
settingsLayout->addWidget(m_preferredView, row, 1);
m_preferredView->addItems(QStringList()
<< i18n("List View")
<< i18n("List View with Custom Thumbnails")
<< i18n("Icon View")
<< i18n("Icon View with Custom Thumbnails"));
connect(m_preferredView, static_cast(&QComboBox::activated), this, &CategoryPage::preferredViewChanged);
rightSideLayout->addStretch();
// Info about the database not being saved
QHBoxLayout* dbNotSavedLayout = new QHBoxLayout;
mainLayout->addLayout(dbNotSavedLayout);
m_dbNotSavedLabel = new QLabel( i18n(""
"The database has unsaved changes. As long as those are "
"not saved,
the names of categories can't be changed "
"and new ones can't be added."
""));
m_dbNotSavedLabel->setWordWrap(true);
dbNotSavedLayout->addWidget(m_dbNotSavedLabel);
m_saveDbNowButton = new QPushButton(i18n("Save the DB now"));
m_saveDbNowButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
connect(m_saveDbNowButton, &QPushButton::clicked, this, &CategoryPage::saveDbNow);
dbNotSavedLayout->addWidget(m_saveDbNowButton);
resetInterface();
// Untagged images
m_untaggedBox = new UntaggedGroupBox;
mainLayout->addWidget(m_untaggedBox);
m_currentCategory = 0;
// This is needed to fix some odd behavior if the "New" button is double clicked
m_editorOpen = false;
m_categoryNamesChanged = false;
}
void Settings::CategoryPage::resetInterface()
{
enableDisable(false);
m_categoriesListWidget->setItemSelected(m_categoriesListWidget->currentItem(), false);
resetCategoryLabel();
m_renameLabel->hide();
}
void Settings::CategoryPage::editSelectedCategory() {
editCategory(m_categoriesListWidget->currentItem());
}
void Settings::CategoryPage::editCategory(QListWidgetItem* i)
{
if (i == 0) {
return;
}
m_categoryNameBeforeEdit = i->text();
Settings::CategoryItem* item = static_cast(i);
m_currentCategory = item;
m_categoryLabel->setText(i18n("Settings for category %1", item->originalName()));
if (m_currentCategory->originalName() != m_categoryNameBeforeEdit) {
m_renameLabel->setText(i18n("Pending change: rename to \"%1\"", m_categoryNameBeforeEdit));
m_renameLabel->show();
} else {
m_renameLabel->clear();
m_renameLabel->hide();
}
m_positionable->setChecked(item->positionable());
m_icon->setIcon(item->icon());
m_thumbnailSizeInCategory->setValue(item->thumbnailSize());
m_preferredView->setCurrentIndex(static_cast(item->viewType()));
enableDisable(true);
if (item->originalName()
== DB::ImageDB::instance()->categoryCollection()
->categoryForSpecial(DB::Category::TokensCategory)->name()) {
m_delItem->setEnabled(false);
m_positionableLabel->setEnabled(false);
m_positionable->setEnabled(false);
m_thumbnailSizeInCategoryLabel->setEnabled(false);
m_thumbnailSizeInCategory->setEnabled(false);
m_preferredViewLabel->setEnabled(false);
m_preferredView->setEnabled(false);
}
}
void Settings::CategoryPage::categoryNameChanged(QListWidgetItem* item)
{
QString newCategoryName = item->text().simplified();
m_categoriesListWidget->blockSignals(true);
item->setText(QString());
m_categoriesListWidget->blockSignals(false);
// Now let's check if the new name is valid :-)
// If it's empty, we're done here. The new name can't be empty.
if (newCategoryName.isEmpty()) {
resetCategory(item);
return;
}
// We don't want to have special category names.
// We do have to search both for the localized version and the C locale version, because a user
// could start KPA e. g. with a German locale and create a "Folder" category (which would not
- // be catched by i18n("Folder")), and then start KPA with the C locale, which would produce a
+ // be caught by i18n("Folder")), and then start KPA with the C locale, which would produce a
// doubled "Folder" category.
if (newCategoryName == i18n("Folder")
|| newCategoryName == QString::fromUtf8("Folder")
|| newCategoryName == i18n("Media Type")
|| newCategoryName == QString::fromUtf8("Media Type")) {
resetCategory(item);
KMessageBox::sorry(this,
i18n("Can't change the name of category \"%1\" to \"%2\":
"
"\"%2\" is a special category name which is reserved and can't "
"be used for a normal category.
",
m_currentCategory->text(), newCategoryName),
i18n("Invalid category name"));
return;
}
// Let's see if we already have a category with this name.
if (m_categoriesListWidget->findItems(newCategoryName, Qt::MatchExactly).size() > 0) {
resetCategory(item);
KMessageBox::sorry(this,
i18n("Can't change the name of category \"%1\" to \"%2\":
"
"A category with this name already exists.
",
m_currentCategory->text(), newCategoryName),
i18n("Invalid category name"));
return;
}
// Let's see if we have any pending name changes that would cause collisions.
for (int i = 0; i < m_categoriesListWidget->count(); i++) {
Settings::CategoryItem* cat = static_cast(m_categoriesListWidget->item(i));
if (cat == m_currentCategory) {
continue;
}
if (newCategoryName == cat->originalName()) {
resetCategory(item);
KMessageBox::sorry(this,
i18n("Can't change the name of category \"%1\" to \"%2\":
"
"There's a pending rename action on the category \"%2\". "
"Please save this change first.
",
m_currentCategory->text(), newCategoryName),
i18n("Unsaved pending renaming action"));
return;
}
}
m_categoriesListWidget->blockSignals(true);
item->setText(newCategoryName);
m_categoriesListWidget->blockSignals(false);
emit categoryChangesPending();
m_untaggedBox->categoryRenamed(m_categoryNameBeforeEdit, newCategoryName);
m_currentCategory->setLabel(newCategoryName);
editCategory(m_currentCategory);
m_categoryNamesChanged = true;
}
void Settings::CategoryPage::resetCategory(QListWidgetItem* item)
{
m_categoriesListWidget->blockSignals(true);
item->setText(m_categoryNameBeforeEdit);
m_categoriesListWidget->blockSignals(false);
}
void Settings::CategoryPage::positionableChanged(bool positionable)
{
if (! m_currentCategory) {
return;
}
if (! positionable) {
int answer = KMessageBox::questionYesNo(this,
i18n("Do you really want to make \"%1\" "
"non-positionable?
"
"All areas linked against this category "
"will be deleted!
",
m_currentCategory->text()));
if (answer == KMessageBox::No) {
m_positionable->setCheckState(Qt::Checked);
return;
}
}
m_currentCategory->setPositionable(positionable);
}
void Settings::CategoryPage::iconChanged(const QString& icon)
{
if(m_currentCategory) {
m_currentCategory->setIcon(icon);
}
}
void Settings::CategoryPage::thumbnailSizeChanged(int size)
{
if (m_currentCategory) {
m_currentCategory->setThumbnailSize(size);
}
}
void Settings::CategoryPage::preferredViewChanged(int i)
{
if (m_currentCategory) {
m_currentCategory->setViewType(static_cast(i));
}
}
void Settings::CategoryPage::newCategory()
{
// This is needed to fix some odd behavior if the "New" button is double clicked
if (m_editorOpen) {
return;
} else {
m_editorOpen = true;
}
// Here starts the real function
QString newCategory = i18n("New category");
QString checkedCategory = newCategory;
int i = 1;
while (m_categoriesListWidget->findItems(checkedCategory, Qt::MatchExactly).size() > 0) {
i++;
checkedCategory = QString::fromUtf8("%1 %2").arg(newCategory).arg(i);
}
m_categoriesListWidget->blockSignals(true);
m_currentCategory = new Settings::CategoryItem(checkedCategory,
QString(),
DB::Category::TreeView,
64,
m_categoriesListWidget);
m_currentCategory->markAsNewCategory();
emit categoryChangesPending();
m_currentCategory->setLabel(checkedCategory);
m_currentCategory->setSelected(true);
m_categoriesListWidget->blockSignals(false);
m_positionable->setChecked(false);
m_icon->setIcon(QIcon());
m_thumbnailSizeInCategory->setValue(64);
enableDisable(true);
editCategory(m_currentCategory);
m_categoriesListWidget->editItem(m_currentCategory);
}
void Settings::CategoryPage::deleteCurrentCategory()
{
int answer = KMessageBox::questionYesNo(this,
i18n("Really delete category \"%1\"?
",
m_currentCategory->text()));
if (answer == KMessageBox::No) {
return;
}
m_untaggedBox->categoryDeleted(m_currentCategory->text());
m_deletedCategories.append(m_currentCategory);
m_categoriesListWidget->takeItem(m_categoriesListWidget->row(m_currentCategory));
m_currentCategory = 0;
m_positionable->setChecked(false);
m_icon->setIcon(QIcon());
m_thumbnailSizeInCategory->setValue(64);
enableDisable(false);
resetCategoryLabel();
editCategory(m_categoriesListWidget->currentItem());
emit categoryChangesPending();
}
void Settings::CategoryPage::renameCurrentCategory()
{
// This is needed to fix some odd behavior if the "New" button is double clicked
m_editorOpen = true;
m_categoriesListWidget->editItem(m_currentCategory);
}
void Settings::CategoryPage::enableDisable(bool b)
{
m_delItem->setEnabled(b);
m_positionableLabel->setEnabled(b);
m_positionable->setEnabled(b);
m_icon->setEnabled(b);
m_iconLabel->setEnabled(b);
m_thumbnailSizeInCategoryLabel->setEnabled(b);
m_thumbnailSizeInCategory->setEnabled(b);
m_preferredViewLabel->setEnabled(b);
m_preferredView->setEnabled(b);
m_categoriesListWidget->blockSignals(true);
if (MainWindow::Window::theMainWindow()->dbIsDirty()) {
m_dbNotSavedLabel->show();
m_saveDbNowButton->show();
m_renameItem->setEnabled(false);
m_newCategoryButton->setEnabled(false);
for (int i = 0; i < m_categoriesListWidget->count(); i++) {
QListWidgetItem* currentItem = m_categoriesListWidget->item(i);
currentItem->setFlags(currentItem->flags() & ~Qt::ItemIsEditable);
}
} else {
m_dbNotSavedLabel->hide();
m_saveDbNowButton->hide();
m_renameItem->setEnabled(b);
m_newCategoryButton->setEnabled(true);
for (int i = 0; i < m_categoriesListWidget->count(); i++) {
QListWidgetItem* currentItem = m_categoriesListWidget->item(i);
currentItem->setFlags(currentItem->flags() | Qt::ItemIsEditable);
}
}
m_categoriesListWidget->blockSignals(false);
}
void Settings::CategoryPage::saveSettings(Settings::SettingsData* opt, DB::MemberMap* memberMap)
{
// Delete items
Q_FOREACH( CategoryItem *item, m_deletedCategories ) {
item->removeFromDatabase();
}
// Created or Modified items
for (int i = 0; i < m_categoriesListWidget->count(); ++i) {
CategoryItem* item = static_cast(m_categoriesListWidget->item(i));
item->submit(memberMap);
}
DB::ImageDB::instance()->memberMap() = *memberMap;
m_untaggedBox->saveSettings(opt);
if (m_categoryNamesChanged) {
// Probably, one or more category names have been edited. Save the database so that
// all thumbnails are referenced with the correct name.
MainWindow::Window::theMainWindow()->slotSave();
m_categoryNamesChanged = false;
}
}
void Settings::CategoryPage::loadSettings(Settings::SettingsData* opt)
{
m_categoriesListWidget->blockSignals(true);
m_categoriesListWidget->clear();
QList categories = DB::ImageDB::instance()->categoryCollection()->categories();
Q_FOREACH( const DB::CategoryPtr category, categories ) {
if (category->type() == DB::Category::PlainCategory
|| category->type() == DB::Category::TokensCategory) {
Settings::CategoryItem *item = new CategoryItem(category->name(),
category->iconName(),
category->viewType(),
category->thumbnailSize(),
m_categoriesListWidget,
category->positionable());
Q_UNUSED(item);
}
}
m_categoriesListWidget->blockSignals(false);
m_untaggedBox->loadSettings(opt);
}
void Settings::CategoryPage::categoryDoubleClicked(QListWidgetItem*)
{
// This is needed to fix some odd behavior if the "New" button is double clicked
m_editorOpen = true;
}
void Settings::CategoryPage::listWidgetEditEnd(QWidget*, QAbstractItemDelegate::EndEditHint)
{
// This is needed to fix some odd behavior if the "New" button is double clicked
m_editorOpen = false;
}
void Settings::CategoryPage::resetCategoryLabel()
{
m_categoryLabel->setText(i18n("choose a category to edit it"));
}
void Settings::CategoryPage::saveDbNow()
{
MainWindow::Window::theMainWindow()->slotSave();
resetInterface();
enableDisable(false);
}
void Settings::CategoryPage::resetCategoryNamesChanged()
{
m_categoryNamesChanged = false;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Settings/SettingsData.cpp b/Settings/SettingsData.cpp
index 41fa9260..1db67495 100644
--- a/Settings/SettingsData.cpp
+++ b/Settings/SettingsData.cpp
@@ -1,549 +1,549 @@
/* Copyright (C) 2003-2018 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "SettingsData.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "DB/CategoryCollection.h"
#include "DB/ImageDB.h"
#define STR(x) QString::fromLatin1(x)
#define value( GROUP, OPTION, DEFAULT ) \
KSharedConfig::openConfig()->group( GROUP ).readEntry( OPTION, DEFAULT ) \
#define setValue( GROUP, OPTION, VALUE ) \
{ \
KConfigGroup group = KSharedConfig::openConfig()->group( GROUP ); \
group.writeEntry( OPTION, VALUE ); \
group.sync(); \
}
#define getValueFunc_( TYPE,FUNC, GROUP,OPTION,DEFAULT ) \
TYPE SettingsData::FUNC() const \
{ return (TYPE) value( GROUP, OPTION, DEFAULT ); }
#define setValueFunc_( FUNC,TYPE, GROUP,OPTION,VALUE ) \
void SettingsData::FUNC( const TYPE v ) \
{ setValue( GROUP, OPTION, VALUE ); }
#define getValueFunc( TYPE,FUNC, GROUP,DEFAULT ) getValueFunc_( TYPE,FUNC, #GROUP,#FUNC,DEFAULT )
#define setValueFunc( FUNC,TYPE, GROUP,OPTION ) setValueFunc_( FUNC,TYPE, #GROUP,#OPTION,v )
// TODO(mfwitten): document parameters.
#define property_( GET_TYPE,GET_FUNC,GET_VALUE, SET_FUNC,SET_TYPE,SET_VALUE, GROUP,OPTION,GET_DEFAULT_1,GET_DEFAULT_2,GET_DEFAULT_2_TYPE ) \
GET_TYPE SettingsData::GET_FUNC() const \
{ \
KConfigGroup g = KSharedConfig::openConfig()->group(GROUP); \
\
if ( !g.hasKey(OPTION) ) \
return GET_DEFAULT_1; \
\
GET_DEFAULT_2_TYPE v = g.readEntry( OPTION, (GET_DEFAULT_2_TYPE)GET_DEFAULT_2 ); \
return (GET_TYPE) GET_VALUE; \
} \
setValueFunc_( SET_FUNC,SET_TYPE, GROUP,OPTION,SET_VALUE )
#define property( GET_TYPE,GET_FUNC, SET_FUNC,SET_TYPE,SET_VALUE, GROUP,OPTION,GET_DEFAULT ) \
getValueFunc_( GET_TYPE,GET_FUNC, GROUP,OPTION,GET_DEFAULT) \
setValueFunc_( SET_FUNC,SET_TYPE, GROUP,OPTION,SET_VALUE )
#define property_copy( GET_FUNC,SET_FUNC, TYPE,GROUP,GET_DEFAULT ) \
property( TYPE,GET_FUNC, SET_FUNC,TYPE,v, #GROUP,#GET_FUNC,GET_DEFAULT )
#define property_ref_( GET_FUNC,SET_FUNC, TYPE,GROUP,GET_DEFAULT ) \
property( TYPE,GET_FUNC, SET_FUNC,TYPE&,v, GROUP,#GET_FUNC,GET_DEFAULT )
#define property_ref( GET_FUNC,SET_FUNC, TYPE,GROUP,GET_DEFAULT ) \
property( TYPE,GET_FUNC, SET_FUNC,TYPE&,v, #GROUP,#GET_FUNC,GET_DEFAULT )
#define property_enum( GET_FUNC,SET_FUNC, TYPE,GROUP,GET_DEFAULT ) \
property( TYPE,GET_FUNC, SET_FUNC,TYPE,(int)v, #GROUP,#GET_FUNC,(int)GET_DEFAULT )
#define property_sset( GET_FUNC,SET_FUNC, GROUP,GET_DEFAULT ) \
property_( StringSet,GET_FUNC,v.toSet(), SET_FUNC,StringSet&,v.toList(), #GROUP,#GET_FUNC,GET_DEFAULT,QStringList(),QStringList )
/**
* smoothScale() is called from the image loading thread, therefore we need
* to cache it this way, rather than going to KConfig.
*/
static bool _smoothScale = true;
using namespace Settings;
const WindowType Settings::MainWindow = "MainWindow";
const WindowType Settings::AnnotationDialog = "AnnotationDialog";
SettingsData* SettingsData::s_instance = nullptr;
SettingsData* SettingsData::instance()
{
if ( ! s_instance )
qFatal("instance called before loading a setup!");
return s_instance;
}
bool SettingsData::ready()
{
return s_instance;
}
void SettingsData::setup( const QString& imageDirectory )
{
if ( !s_instance )
s_instance = new SettingsData( imageDirectory );
}
SettingsData::SettingsData( const QString& imageDirectory )
{
m_hasAskedAboutTimeStamps = false;
QString s = STR( "/" );
m_imageDirectory = imageDirectory.endsWith(s) ? imageDirectory : imageDirectory + s;
_smoothScale = value( "Viewer", "smoothScale", true );
// Split the list of EXIF comments that should be stripped automatically to a list
QStringList commentsToStrip = value( "General", "commentsToStrip", QString::fromLatin1("") ).split(QString::fromLatin1("-,-"), QString::SkipEmptyParts );
for (QString &comment : commentsToStrip )
comment.replace( QString::fromLatin1(",,"), QString::fromLatin1(",") );
m_EXIFCommentsToStrip = commentsToStrip;
}
/////////////////
//// General ////
/////////////////
property_copy( useEXIFRotate , setUseEXIFRotate , bool , General, true )
property_copy( useEXIFComments , setUseEXIFComments , bool , General, true )
property_copy( stripEXIFComments , setStripEXIFComments , bool , General, false )
property_copy( commentsToStrip , setCommentsToStrip , QString , General, QString::fromLatin1("") )
property_copy( searchForImagesOnStart, setSearchForImagesOnStart, bool , General, true )
property_copy( ignoreFileExtension , setIgnoreFileExtension , bool , General, false )
property_copy( skipSymlinks, setSkipSymlinks , bool , General, false )
property_copy( skipRawIfOtherMatches , setSkipRawIfOtherMatches , bool , General, false )
-property_copy( useRawThumbnail , setUseRawThumbnail , bool , General, false )
+property_copy( useRawThumbnail , setUseRawThumbnail , bool , General, true )
property_copy( useRawThumbnailSize , setUseRawThumbnailSize , QSize , General, QSize(1024,768) )
property_copy( useCompressedIndexXML , setUseCompressedIndexXML , bool , General, false )
property_copy( compressBackup , setCompressBackup , bool , General, true )
property_copy( showSplashScreen , setShowSplashScreen , bool , General, true )
property_copy( showHistogram , setShowHistogram , bool , General, true )
property_copy( autoSave , setAutoSave , int , General, 5 )
property_copy( backupCount , setBackupCount , int , General, 5 )
property_enum( tTimeStamps , setTTimeStamps , TimeStampTrust, General, Always )
property_copy( excludeDirectories , setExcludeDirectories , QString , General, QString::fromLatin1("xml,ThumbNails,.thumbs") )
property_copy( recentAndroidAddress , setRecentAndroidAddress , QString , General, QString() )
property_copy( listenForAndroidDevicesOnStartup, setListenForAndroidDevicesOnStartup, bool, General, false)
getValueFunc( QSize,histogramSize, General,QSize(15,30) )
getValueFunc( ViewSortType,viewSortType, General,(int)SortLastUse )
getValueFunc( AnnotationDialog::MatchType, matchType, General,(int)AnnotationDialog::MatchFromWordStart )
void SettingsData::setHistogramSize( const QSize& size )
{
if ( size == histogramSize() )
return;
setValue( "General", "histogramSize", size );
emit histogramSizeChanged( size );
}
void SettingsData::setViewSortType( const ViewSortType tp )
{
if ( tp == viewSortType() )
return;
setValue( "General", "viewSortType", (int)tp );
emit viewSortTypeChanged( tp );
}
void SettingsData::setMatchType( const AnnotationDialog::MatchType mt )
{
if ( mt == matchType() )
return;
setValue( "General", "matchType", (int)mt );
emit matchTypeChanged( mt );
}
bool SettingsData::trustTimeStamps()
{
if ( tTimeStamps() == Always )
return true;
else if ( tTimeStamps() == Never )
return false;
else {
if (!m_hasAskedAboutTimeStamps ) {
QApplication::setOverrideCursor( Qt::ArrowCursor );
QString txt = i18n("When reading time information of images, their EXIF info is used. "
"Exif info may, however, not be supported by your KPhotoAlbum installation, "
"or no valid information may be in the file. "
"As a backup, KPhotoAlbum may use the timestamp of the image - this may, "
"however, not be valid in case the image is scanned in. "
"So the question is, should KPhotoAlbum trust the time stamp on your images?" );
int answer = KMessageBox::questionYesNo( nullptr, txt, i18n("Trust Time Stamps?") );
QApplication::restoreOverrideCursor();
if ( answer == KMessageBox::Yes )
m_trustTimeStamps = true;
else
m_trustTimeStamps = false;
m_hasAskedAboutTimeStamps = true;
}
return m_trustTimeStamps;
}
}
////////////////////////////////
//// File Version Detection ////
////////////////////////////////
property_copy( detectModifiedFiles , setDetectModifiedFiles , bool , FileVersionDetection, false )
property_copy( modifiedFileComponent , setModifiedFileComponent , QString , FileVersionDetection, QString() )
property_copy( originalFileComponent , setOriginalFileComponent , QString , FileVersionDetection, QString() )
property_copy( moveOriginalContents , setMoveOriginalContents , bool , FileVersionDetection, false )
property_copy( autoStackNewFiles , setAutoStackNewFiles , bool , FileVersionDetection, true )
property_copy( copyFileComponent , setCopyFileComponent , QString , FileVersionDetection, "(.[^.]+)$" )
property_copy( copyFileReplacementComponent , setCopyFileReplacementComponent , QString , FileVersionDetection, "-edited\\1")
////////////////////
//// Thumbnails ////
////////////////////
property_copy( displayLabels , setDisplayLabels , bool , Thumbnails, true )
property_copy( displayCategories , setDisplayCategories , bool , Thumbnails, false )
property_copy( autoShowThumbnailView , setAutoShowThumbnailView , int , Thumbnails, 0 )
property_copy( showNewestThumbnailFirst, setShowNewestFirst , bool , Thumbnails, false )
property_copy( thumbnailDisplayGrid , setThumbnailDisplayGrid , bool , Thumbnails, false )
property_copy( previewSize , setPreviewSize , int , Thumbnails, 256 )
property_copy( thumbnailSpace , setThumbnailSpace , int , Thumbnails, 4 )
// not available via GUI, but should be consistent (and maybe confgurable for powerusers):
property_copy( minimumThumbnailSize , setMinimumThumbnailSize , int , Thumbnails, 32 )
property_copy( maximumThumbnailSize , setMaximumThumbnailSize , int , Thumbnails, 4096 )
property_enum( thumbnailAspectRatio , setThumbnailAspectRatio , ThumbnailAspectRatio, Thumbnails, Aspect_4_3 )
property_ref( backgroundColor , setBackgroundColor , QString , Thumbnails, QColor(Qt::darkGray).name() )
property_copy( incrementalThumbnails , setIncrementalThumbnails , bool , Thumbnails, true )
// database specific so that changing it doesn't invalidate the thumbnail cache for other databases:
getValueFunc_( int, thumbnailSize, groupForDatabase("Thumbnails"), "thumbSize", 150)
void SettingsData::setThumbnailSize( int value )
{
// enforce limits:
value = qBound( minimumThumbnailSize(), value, maximumThumbnailSize());
if ( value != thumbnailSize() )
emit thumbnailSizeChanged(value);
setValue( groupForDatabase("Thumbnails"), "thumbSize", value );
}
int SettingsData::actualThumbnailSize() const \
{
// this is database specific since it's a derived value of thumbnailSize
int retval = value( groupForDatabase("Thumbnails"), "actualThumbSize", 0 );
// if no value has been set, use thumbnailSize
if ( retval == 0 )
retval = thumbnailSize();
return retval;
}
void SettingsData::setActualThumbnailSize( int value )
{
QPixmapCache::clear();
// enforce limits:
value = qBound( minimumThumbnailSize(), value, thumbnailSize());
if ( value != actualThumbnailSize())
{
setValue( groupForDatabase("Thumbnails"), "actualThumbSize", value );
emit actualThumbnailSizeChanged(value);
}
}
////////////////
//// Viewer ////
////////////////
property_ref ( viewerSize , setViewerSize , QSize , Viewer, QSize(1024,768) )
property_ref ( slideShowSize , setSlideShowSize , QSize , Viewer, QSize(1024,768) )
property_copy( launchViewerFullScreen , setLaunchViewerFullScreen , bool , Viewer, false )
property_copy( launchSlideShowFullScreen, setLaunchSlideShowFullScreen, bool , Viewer, false )
property_copy( showInfoBox , setShowInfoBox , bool , Viewer, true )
property_copy( showLabel , setShowLabel , bool , Viewer, true )
property_copy( showDescription , setShowDescription , bool , Viewer, true )
property_copy( showDate , setShowDate , bool , Viewer, true )
property_copy( showImageSize , setShowImageSize , bool , Viewer, true )
property_copy( showRating , setShowRating , bool , Viewer, true )
property_copy( showTime , setShowTime , bool , Viewer, true )
property_copy( showFilename , setShowFilename , bool , Viewer, false )
property_copy( showEXIF , setShowEXIF , bool , Viewer, true )
property_copy( slideShowInterval , setSlideShowInterval , int , Viewer, 5 )
property_copy( viewerCacheSize , setViewerCacheSize , int , Viewer, 195 )
property_copy( infoBoxWidth , setInfoBoxWidth , int , Viewer, 400 )
property_copy( infoBoxHeight , setInfoBoxHeight , int , Viewer, 300 )
property_enum( infoBoxPosition , setInfoBoxPosition , Position , Viewer, Bottom )
property_enum( viewerStandardSize , setViewerStandardSize , StandardViewSize, Viewer, FullSize )
bool SettingsData::smoothScale() const
{
return _smoothScale;
}
void SettingsData::setSmoothScale( bool b )
{
_smoothScale = b;
setValue( "Viewer", "smoothScale", b );
}
////////////////////
//// Categories ////
////////////////////
setValueFunc( setAlbumCategory,QString&, General,albumCategory )
QString SettingsData::albumCategory() const
{
QString category = value( "General", "albumCategory", STR("") );
if ( !DB::ImageDB::instance()->categoryCollection()->categoryNames().contains( category ) )
{
category = DB::ImageDB::instance()->categoryCollection()->categoryNames()[0];
const_cast(this)->setAlbumCategory( category );
}
return category;
}
property_ref( untaggedCategory, setUntaggedCategory, QString, General, i18n("Events"))
property_ref( untaggedTag, setUntaggedTag, QString, General, i18n("untagged"))
property_copy( untaggedImagesTagVisible, setUntaggedImagesTagVisible, bool, General, false)
//////////////
//// Exif ////
//////////////
property_sset( exifForViewer, setExifForViewer, Exif, StringSet() )
property_sset( exifForDialog, setExifForDialog, Exif, Exif::Info::instance()->standardKeys() )
property_ref ( iptcCharset , setIptcCharset , QString, Exif, QString() )
/////////////////////
//// Exif Import ////
/////////////////////
property_copy( updateExifData , setUpdateExifData , bool , ExifImport, true )
property_copy( updateImageDate , setUpdateImageDate , bool , ExifImport, false )
property_copy( useModDateIfNoExif , setUseModDateIfNoExif , bool , ExifImport, true )
property_copy( updateOrientation , setUpdateOrientation , bool , ExifImport, false )
property_copy( updateDescription , setUpdateDescription , bool , ExifImport, false )
///////////////////////
//// Miscellaneous ////
///////////////////////
property_copy( delayLoadingPlugins, setDelayLoadingPlugins, bool, Plug-ins, true )
property_ref_(
HTMLBaseDir, setHTMLBaseDir, QString,
groupForDatabase( "HTML Settings" ),
QString::fromLocal8Bit(qgetenv( "HOME" )) + STR( "/public_html" ) )
property_ref_(
HTMLBaseURL, setHTMLBaseURL, QString,
groupForDatabase( "HTML Settings" ),
STR( "file://" ) + HTMLBaseDir() )
property_ref_(
HTMLDestURL, setHTMLDestURL, QString,
groupForDatabase( "HTML Settings" ),
STR( "file://" ) + HTMLBaseDir() )
property_ref_(
HTMLCopyright, setHTMLCopyright, QString,
groupForDatabase( "HTML Settings" ),
STR( "" ) )
property_ref_(
HTMLDate, setHTMLDate, int,
groupForDatabase( "HTML Settings" ),
true )
property_ref_(
HTMLTheme, setHTMLTheme, int,
groupForDatabase( "HTML Settings" ),
-1 )
property_ref_(
HTMLKimFile, setHTMLKimFile, int,
groupForDatabase( "HTML Settings" ),
true )
property_ref_(
HTMLInlineMovies, setHTMLInlineMovies, int,
groupForDatabase( "HTML Settings" ),
true )
property_ref_(
HTML5Video, setHTML5Video, int,
groupForDatabase( "HTML Settings" ),
true )
property_ref_(
HTML5VideoGenerate, setHTML5VideoGenerate, int,
groupForDatabase( "HTML Settings" ),
true )
property_ref_(
HTMLThumbSize, setHTMLThumbSize, int,
groupForDatabase( "HTML Settings" ),
128 )
property_ref_(
HTMLNumOfCols, setHTMLNumOfCols, int,
groupForDatabase( "HTML Settings" ),
5 )
property_ref_(
HTMLSizes, setHTMLSizes, QString,
groupForDatabase( "HTML Settings" ),
STR("") )
property_ref_(
HTMLIncludeSelections, setHTMLIncludeSelections, QString,
groupForDatabase( "HTML Settings" ),
STR("") )
property_ref_( password, setPassword, QString, groupForDatabase( "Privacy Settings" ), STR("") )
QDate SettingsData::fromDate() const
{
QString date = value( "Miscellaneous", "fromDate", STR("") );
return date.isEmpty() ? QDate( QDate::currentDate().year(), 1, 1 ) : QDate::fromString( date, Qt::ISODate );
}
void SettingsData::setFromDate( const QDate& date)
{
if (date.isValid())
setValue( "Miscellaneous", "fromDate", date.toString( Qt::ISODate ) );
}
QDate SettingsData::toDate() const
{
QString date = value( "Miscellaneous", "toDate", STR("") );
return date.isEmpty() ? QDate( QDate::currentDate().year()+1, 1, 1 ) : QDate::fromString( date, Qt::ISODate );
}
void SettingsData::setToDate( const QDate& date)
{
if (date.isValid())
setValue( "Miscellaneous", "toDate", date.toString( Qt::ISODate ) );
}
QString SettingsData::imageDirectory() const
{
return m_imageDirectory;
}
QString SettingsData::groupForDatabase( const char* setting ) const
{
return STR("%1 - %2").arg( STR(setting) ).arg( imageDirectory() );
}
DB::ImageSearchInfo SettingsData::currentLock() const
{
return DB::ImageSearchInfo::loadLock();
}
void SettingsData::setCurrentLock( const DB::ImageSearchInfo& info, bool exclude )
{
info.saveLock();
setValue( groupForDatabase( "Privacy Settings" ), "exclude", exclude );
}
bool SettingsData::lockExcludes() const
{
return value( groupForDatabase( "Privacy Settings" ), "exclude", false );
}
getValueFunc_( bool,locked, groupForDatabase("Privacy Settings"),"locked",false )
void SettingsData::setLocked( bool lock, bool force )
{
if ( lock == locked() && !force )
return;
setValue( groupForDatabase( "Privacy Settings" ), "locked", lock );
emit locked( lock, lockExcludes() );
}
void SettingsData::setWindowGeometry( WindowType win, const QRect& geometry )
{
setValue( "Window Geometry", win, geometry );
}
QRect SettingsData::windowGeometry( WindowType win ) const
{
return value( "Window Geometry", win, QRect(0,0,800,600) );
}
bool Settings::SettingsData::hasUntaggedCategoryFeatureConfigured() const
{
return DB::ImageDB::instance()->categoryCollection()->categoryNames().contains( untaggedCategory() )
&& DB::ImageDB::instance()->categoryCollection()->categoryForName( untaggedCategory())->items().contains( untaggedTag() );
}
double Settings::SettingsData::getThumbnailAspectRatio() const
{
double ratio = 1.0;
switch (Settings::SettingsData::instance()->thumbnailAspectRatio()) {
case Settings::Aspect_16_9:
ratio = 9.0 / 16;
break;
case Settings::Aspect_4_3:
ratio = 3.0 / 4;
break;
case Settings::Aspect_3_2:
ratio = 2.0 / 3;
break;
case Settings::Aspect_9_16:
ratio = 16 / 9.0;
break;
case Settings::Aspect_3_4:
ratio = 4 / 3.0;
break;
case Settings::Aspect_2_3:
ratio = 3 / 2.0;
break;
case Settings::Aspect_1_1:
ratio = 1.0;
break;
}
return ratio;
}
QStringList Settings::SettingsData::EXIFCommentsToStrip()
{
return m_EXIFCommentsToStrip;
}
void Settings::SettingsData::setEXIFCommentsToStrip(QStringList EXIFCommentsToStrip)
{
m_EXIFCommentsToStrip = EXIFCommentsToStrip;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Utilities/Util.cpp b/Utilities/Util.cpp
index 3689c892..22fa69d6 100644
--- a/Utilities/Util.cpp
+++ b/Utilities/Util.cpp
@@ -1,854 +1,854 @@
/* Copyright (C) 2003-2010 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "Util.h"
#include "Logging.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "JpeglibWithFix.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern "C" {
#include
#include
#include
#include
#include
#include
#include
#include
}
namespace {
// Determined experimentally to yield best results (on Seagate 2TB 2.5" disk,
// 5400 RPM). Performance is very similar at 524288. Above that, performance
// was significantly worse. Below that, performance also deteriorated.
// This assumes use of one image scout thread (see DB/ImageScout.cpp). Without
// a scout thread, performance was about 10-15% worse.
constexpr int MD5_BUFFER_SIZE = 262144;
}
/**
* Add a line label + info text to the result text if info is not empty.
* If the result already contains something, a HTML newline is added first.
* To be used in createInfoText().
*/
static void AddNonEmptyInfo(const QString &label, const QString &info,
QString *result) {
if (info.isEmpty())
return;
if (!result->isEmpty())
*result += QString::fromLatin1("
");
result->append(label).append(info);
}
/**
* Given an ImageInfoPtr this function will create an HTML blob about the
* image. The blob is used in the viewer and in the tool tip box from the
* thumbnail view.
*
* As the HTML text is created, the parameter linkMap is filled with
* information about hyperlinks. The map maps from an index to a pair of
* (categoryName, categoryItem). This linkMap is used when the user selects
* one of the hyberlinks.
*/
QString Utilities::createInfoText( DB::ImageInfoPtr info, QMap< int,QPair >* linkMap )
{
Q_ASSERT( info );
QString result;
if ( Settings::SettingsData::instance()->showFilename() ) {
AddNonEmptyInfo(i18n("File Name: "), info->fileName().relative(), &result);
}
if ( Settings::SettingsData::instance()->showDate() ) {
AddNonEmptyInfo(i18n("Date: "), info->date().toString( Settings::SettingsData::instance()->showTime() ? true : false ),
&result);
}
/* XXX */
if ( Settings::SettingsData::instance()->showImageSize() && info->mediaType() == DB::Image) {
const QSize imageSize = info->size();
// Do not add -1 x -1 text
if (imageSize.width() >= 0 && imageSize.height() >= 0) {
const double megapix = imageSize.width() * imageSize.height() / 1000000.0;
QString info = i18nc("width x height","%1x%2"
,QString::number(imageSize.width())
,QString::number(imageSize.height()));
if (megapix > 0.05) {
info += i18nc("short for: x megapixels"," (%1MP)"
,QString::number(megapix, 'f', 1));
}
const double aspect = (double) imageSize.width() / (double) imageSize.height();
if (aspect > 1)
info += i18nc("aspect ratio"," (%1:1)"
,QLocale::system().toString(aspect, 'f', 2));
else if (aspect >= 0.995 && aspect < 1.005)
info += i18nc("aspect ratio"," (1:1)");
else
info += i18nc("aspect ratio"," (1:%1)"
,QLocale::system().toString(1.0/aspect, 'f', 2));
AddNonEmptyInfo(i18n("Image Size: "), info, &result);
}
}
if ( Settings::SettingsData::instance()->showRating() ) {
if ( info->rating() != -1 ) {
if ( ! result.isEmpty() )
result += QString::fromLatin1("
");
QUrl rating;
rating.setScheme(QString::fromLatin1("kratingwidget"));
// we don't use the host part, but if we don't set it, we can't use port:
rating.setHost(QString::fromLatin1("int"));
rating.setPort(qMin( qMax( static_cast(0), info->rating() ), static_cast(10)));
result += QString::fromLatin1("").arg( rating.toString(QUrl::None) );
}
}
QList categories = DB::ImageDB::instance()->categoryCollection()->categories();
int link = 0;
Q_FOREACH( const DB::CategoryPtr category, categories ) {
const QString categoryName = category->name();
if ( category->doShow() ) {
StringSet items = info->itemsOfCategory( categoryName );
if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()
&& ! Settings::SettingsData::instance()->untaggedImagesTagVisible()) {
if (categoryName == Settings::SettingsData::instance()->untaggedCategory()) {
if (items.contains(Settings::SettingsData::instance()->untaggedTag())) {
items.remove(Settings::SettingsData::instance()->untaggedTag());
}
}
}
if (!items.empty()) {
QString title = QString::fromUtf8("%1: ").arg(category->name());
QString infoText;
bool first = true;
Q_FOREACH( const QString &item, items) {
if ( first )
first = false;
else
infoText += QString::fromLatin1( ", " );
if ( linkMap ) {
++link;
(*linkMap)[link] = QPair( categoryName, item );
infoText += QString::fromLatin1( "%2").arg( link ).arg( item );
infoText += formatAge(category, item, info);
}
else
infoText += item;
}
AddNonEmptyInfo(title, infoText, &result);
}
}
}
if ( Settings::SettingsData::instance()->showLabel()) {
AddNonEmptyInfo(i18n("Label: "), info->label(), &result);
}
if ( Settings::SettingsData::instance()->showDescription() && !info->description().trimmed().isEmpty() ) {
AddNonEmptyInfo(i18n("Description: "), info->description(),
&result);
}
QString exifText;
if ( Settings::SettingsData::instance()->showEXIF() ) {
typedef QMap ExifMap;
typedef ExifMap::const_iterator ExifMapIterator;
ExifMap exifMap = Exif::Info::instance()->infoForViewer( info->fileName(), Settings::SettingsData::instance()->iptcCharset() );
for( ExifMapIterator exifIt = exifMap.constBegin(); exifIt != exifMap.constEnd(); ++exifIt ) {
if ( exifIt.key().startsWith( QString::fromLatin1( "Exif." ) ) )
for ( QStringList::const_iterator valuesIt = exifIt.value().constBegin(); valuesIt != exifIt.value().constEnd(); ++valuesIt ) {
QString exifName = exifIt.key().split( QChar::fromLatin1('.') ).last();
AddNonEmptyInfo(QString::fromLatin1( "%1: ").arg(exifName),
*valuesIt, &exifText);
}
}
QString iptcText;
for( ExifMapIterator exifIt = exifMap.constBegin(); exifIt != exifMap.constEnd(); ++exifIt ) {
if ( !exifIt.key().startsWith( QString::fromLatin1( "Exif." ) ) )
for ( QStringList::const_iterator valuesIt = exifIt.value().constBegin(); valuesIt != exifIt.value().constEnd(); ++valuesIt ) {
QString iptcName = exifIt.key().split( QChar::fromLatin1('.') ).last();
AddNonEmptyInfo(QString::fromLatin1( "%1: ").arg(iptcName),
*valuesIt, &iptcText);
}
}
if ( !iptcText.isEmpty() ) {
if ( exifText.isEmpty() )
exifText = iptcText;
else
exifText += QString::fromLatin1( "
" ) + iptcText;
}
}
if ( !result.isEmpty() && !exifText.isEmpty() )
result += QString::fromLatin1( "
" );
result += exifText;
return result;
}
using DateSpec = QPair;
DateSpec dateDiff(const QDate& birthDate, const QDate& imageDate)
{
const int bday = birthDate.day();
const int iday = imageDate.day();
const int bmonth = birthDate.month();
const int imonth = imageDate.month();
const int byear = birthDate.year();
const int iyear = imageDate.year();
// Image before birth
const int diff = birthDate.daysTo(imageDate);
if (diff < 0)
return qMakePair(0, 'I');
if (diff < 31)
return qMakePair(diff, 'D');
int months = (iyear-byear)*12;
months += (imonth-bmonth);
months += (iday >= bday) ? 0 : -1;
if ( months < 24)
return qMakePair(months, 'M');
else
return qMakePair(months/12, 'Y');
}
QString formatDate(const DateSpec& date)
{
if (date.second == 'I')
return {};
else if (date.second == 'D')
return i18np("1 day", "%1 days", date.first);
else if (date.second == 'M')
return i18np("1 month", "%1 months", date.first);
else
return i18np("1 year", "%1 years", date.first);
}
void test() {
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,7,11))) == QString::fromLatin1("0 days"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,10))) == QString::fromLatin1("30 days"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,11))) == QString::fromLatin1("1 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,12))) == QString::fromLatin1("1 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,9,10))) == QString::fromLatin1("1 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,9,11))) == QString::fromLatin1("2 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,10))) == QString::fromLatin1("10 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,11))) == QString::fromLatin1("11 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,12))) == QString::fromLatin1("11 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,10))) == QString::fromLatin1("11 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,11))) == QString::fromLatin1("12 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,12))) == QString::fromLatin1("12 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,12,11))) == QString::fromLatin1("17 month"));
Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1973,7,11))) == QString::fromLatin1("2 years"));
}
QString Utilities::formatAge(DB::CategoryPtr category, const QString &item, DB::ImageInfoPtr info)
{
// test(); // I wish I could get my act together to set up a test suite.
const QDate birthDate = category->birthDate(item);
const QDate start = info->date().start().date();
const QDate end = info->date().end().date();
if (birthDate.isNull() || start.isNull())
return {};
if ( start == end)
return QString::fromUtf8(" (%1)").arg(formatDate(dateDiff(birthDate, start)));
else {
DateSpec lower = dateDiff(birthDate,start);
DateSpec upper = dateDiff(birthDate,end);
if (lower == upper)
return QString::fromUtf8(" (%1)").arg(formatDate(lower));
else if (lower.second == 'I')
return QString::fromUtf8(" (< %1)").arg(formatDate(upper));
else {
if (lower.second == upper.second)
return QString::fromUtf8(" (%1-%2)").arg(lower.first).arg(formatDate(upper));
else
return QString::fromUtf8(" (%1-%2)").arg(formatDate(lower)).arg(formatDate(upper));
}
}
}
void Utilities::checkForBackupFile( const QString& fileName, const QString& message )
{
QString backupName = QFileInfo( fileName ).absolutePath() + QString::fromLatin1("/.#") + QFileInfo( fileName ).fileName();
QFileInfo backUpFile( backupName);
QFileInfo indexFile( fileName );
if ( !backUpFile.exists() || indexFile.lastModified() > backUpFile.lastModified() || backUpFile.size() == 0 )
if ( !( backUpFile.exists() && !message.isNull() ) )
return;
int code;
if ( message.isNull() )
code = KMessageBox::questionYesNo( nullptr, i18n("Autosave file '%1' exists (size %3 KB) and is newer than '%2'. "
"Should the autosave file be used?", backupName, fileName, backUpFile.size() >> 10 ),
i18n("Found Autosave File") );
else if ( backUpFile.size() > 0 )
code = KMessageBox::warningYesNo( nullptr,i18n( "Error: Cannot use current database file '%1':
%2
"
"Do you want to use autosave (%3 - size %4 KB) instead of exiting?
"
"(Manually verifying and copying the file might be a good idea)
", fileName, message, backupName, backUpFile.size() >> 10 ),
i18n("Recover from Autosave?") );
else {
KMessageBox::error( nullptr, i18n( "Error: %1
Also autosave file is empty, check manually "
"if numbered backup files exist and can be used to restore index.xml.
", message ) );
exit(-1);
}
if ( code == KMessageBox::Yes ) {
QFile in( backupName );
if ( in.open( QIODevice::ReadOnly ) ) {
QFile out( fileName );
if (out.open( QIODevice::WriteOnly ) ) {
char data[1024];
int len;
while ( (len = in.read( data, 1024 ) ) )
out.write( data, len );
}
}
} else if ( !message.isNull() )
exit(-1);
}
bool Utilities::ctrlKeyDown()
{
return QApplication::keyboardModifiers() & Qt::ControlModifier;
}
void Utilities::copyList( const QStringList& from, const QString& directoryTo )
{
for( QStringList::ConstIterator it = from.constBegin(); it != from.constEnd(); ++it ) {
QString destFile = directoryTo + QString::fromLatin1( "/" ) + QFileInfo(*it).fileName();
if ( ! QFileInfo( destFile ).exists() ) {
const bool ok = copy( *it, destFile );
if ( !ok ) {
KMessageBox::error( nullptr, i18n("Unable to copy '%1' to '%2'.", *it , destFile ), i18n("Error Running Demo") );
exit(-1);
}
}
}
}
QString Utilities::setupDemo()
{
QString demoDir = QString::fromLatin1( "%1/kphotoalbum-demo-%2" ).arg(QDir::tempPath()).arg(QString::fromLocal8Bit( qgetenv( "LOGNAME" ) ));
QFileInfo fi(demoDir);
if ( ! fi.exists() ) {
bool ok = QDir().mkdir( demoDir );
if ( !ok ) {
KMessageBox::error( nullptr, i18n("Unable to create directory '%1' needed for demo.", demoDir ), i18n("Error Running Demo") );
exit(-1);
}
}
// index.xml
QString demoDB = locateDataFile(QString::fromLatin1("demo/index.xml"));
if ( demoDB.isEmpty() )
{
qCDebug(UtilitiesLog) << "No demo database in standard locations:" << QStandardPaths::standardLocations(QStandardPaths::DataLocation);
exit(-1);
}
QString configFile = demoDir + QString::fromLatin1( "/index.xml" );
copy(demoDB, configFile);
// Images
const QStringList kpaDemoDirs = QStandardPaths::locateAll(
QStandardPaths::DataLocation,
QString::fromLatin1("demo"),
QStandardPaths::LocateDirectory);
QStringList images;
Q_FOREACH(const QString &dir, kpaDemoDirs)
{
QDirIterator it(dir, QStringList() << QStringLiteral("*.jpg") << QStringLiteral("*.avi"));
while (it.hasNext()) {
images.append(it.next());
}
}
copyList( images, demoDir );
// CategoryImages
QString catDir = demoDir + QString::fromLatin1("/CategoryImages");
fi = QFileInfo(catDir);
if ( ! fi.exists() ) {
bool ok = QDir().mkdir( catDir );
if ( !ok ) {
KMessageBox::error( nullptr, i18n("Unable to create directory '%1' needed for demo.", catDir ), i18n("Error Running Demo") );
exit(-1);
}
}
const QStringList kpaDemoCatDirs = QStandardPaths::locateAll(
QStandardPaths::DataLocation,
QString::fromLatin1("demo/CategoryImages"),
QStandardPaths::LocateDirectory);
QStringList catImages;
Q_FOREACH(const QString &dir, kpaDemoCatDirs)
{
QDirIterator it(dir, QStringList() << QStringLiteral("*.jpg"));
while (it.hasNext()) {
catImages.append(it.next());
}
}
copyList( catImages, catDir );
return configFile;
}
bool Utilities::copy( const QString& from, const QString& to )
{
if ( QFileInfo(to).exists())
QDir().remove(to);
return QFile::copy(from,to);
}
bool Utilities::makeHardLink( const QString& from, const QString& to )
{
if (link(from.toLocal8Bit().constData(), to.toLocal8Bit().constData()) != 0)
return false;
else
return true;
}
bool Utilities::makeSymbolicLink( const QString& from, const QString& to )
{
if (symlink(from.toLocal8Bit().constData(), to.toLocal8Bit().constData()) != 0)
return false;
else
return true;
}
bool Utilities::canReadImage( const DB::FileName& fileName )
{
bool fastMode = !Settings::SettingsData::instance()->ignoreFileExtension();
QMimeDatabase::MatchMode mode = fastMode ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
QMimeDatabase db;
QMimeType mimeType = db.mimeTypeForFile( fileName.absolute(), mode );
return QImageReader::supportedMimeTypes().contains( mimeType.name().toUtf8() )
|| ImageManager::ImageDecoder::mightDecode( fileName );
}
QString Utilities::locateDataFile(const QString& fileName)
{
return QStandardPaths::locate(QStandardPaths::DataLocation, fileName);
}
QString Utilities::readFile( const QString& fileName )
{
if ( fileName.isEmpty() ) {
KMessageBox::error( nullptr, i18n("No file name given!
") );
return QString();
}
QFile file( fileName );
if ( !file.open( QIODevice::ReadOnly ) ) {
//KMessageBox::error( nullptr, i18n("Could not open file %1").arg( fileName ) );
return QString();
}
QTextStream stream( &file );
QString content = stream.readAll();
file.close();
return content;
}
struct myjpeg_error_mgr : public jpeg_error_mgr
{
jmp_buf setjmp_buffer;
};
extern "C"
{
static void myjpeg_error_exit(j_common_ptr cinfo)
{
myjpeg_error_mgr* myerr =
(myjpeg_error_mgr*) cinfo->err;
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
//kWarning() << buffer;
longjmp(myerr->setjmp_buffer, 1);
}
}
namespace Utilities
{
static bool loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size );
}
bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim, char *membuf, size_t membufSize)
{
bool ok;
struct stat statbuf;
if ( stat( QFile::encodeName(imageFile.absolute()).constData(), &statbuf ) == -1 )
return false;
if ( ! membuf || statbuf.st_size > (int) membufSize ) {
qCDebug(UtilitiesLog) << "loadJPEG (slow path) " << imageFile.relative() << " " << statbuf.st_size << " " << membufSize;
FILE* inputFile=fopen( QFile::encodeName(imageFile.absolute()).constData(), "rb");
if(!inputFile)
return false;
ok = loadJPEGInternal( img, inputFile, fullSize, dim, NULL, 0 );
fclose(inputFile);
} else {
// May be more than is needed, but less likely to fragment memory this
// way.
int inputFD = open( QFile::encodeName(imageFile.absolute()).constData(), O_RDONLY );
if ( inputFD == -1 ) {
return false;
}
unsigned bytesLeft = statbuf.st_size;
unsigned offset = 0;
while ( bytesLeft > 0 ) {
int bytes = read(inputFD, membuf + offset, bytesLeft);
if (bytes <= 0) {
(void) close(inputFD);
return false;
}
offset += bytes;
bytesLeft -= bytes;
}
ok = loadJPEGInternal( img, NULL, fullSize, dim, membuf, statbuf.st_size );
(void) close(inputFD);
}
return ok;
}
bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim)
{
return loadJPEG( img, imageFile, fullSize, dim, NULL, 0 );
}
bool Utilities::loadJPEG(QImage *img, const QByteArray &data, QSize* fullSize, int dim)
{
return loadJPEGInternal(img, nullptr, fullSize, dim, data.data(), data.size());
}
bool Utilities::loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size )
{
struct jpeg_decompress_struct cinfo;
struct myjpeg_error_mgr jerr;
// JPEG error handling - thanks to Marcus Meissner
cinfo.err = jpeg_std_error(&jerr);
cinfo.err->error_exit = myjpeg_error_exit;
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
return false;
}
jpeg_create_decompress(&cinfo);
if (inputFile)
jpeg_stdio_src(&cinfo, inputFile);
else
jpeg_mem_src(&cinfo, (unsigned char *) membuf, membuf_size);
jpeg_read_header(&cinfo, TRUE);
*fullSize = QSize( cinfo.image_width, cinfo.image_height );
int imgSize = qMax(cinfo.image_width, cinfo.image_height);
//libjpeg supports a sort of scale-while-decoding which speeds up decoding
int scale=1;
if (dim != -1) {
while(dim*scale*2<=imgSize) {
scale*=2;
}
if(scale>8) scale=8;
}
cinfo.scale_num=1;
cinfo.scale_denom=scale;
// Create QImage
jpeg_start_decompress(&cinfo);
switch(cinfo.output_components) {
case 3:
case 4:
*img = QImage(
cinfo.output_width, cinfo.output_height, QImage::Format_RGB32);
if (img->isNull())
return false;
break;
case 1: // B&W image
*img = QImage(
cinfo.output_width, cinfo.output_height, QImage::Format_Indexed8);
if (img->isNull())
return false;
img->setColorCount(256);
for (int i=0; i<256; i++)
img->setColor(i, qRgb(i,i,i));
break;
default:
return false;
}
QVector linesVector;
linesVector.reserve(img->height());
for (int i = 0; i < img->height(); ++i)
linesVector.push_back(img->scanLine(i));
uchar** lines = linesVector.data();
while (cinfo.output_scanline < cinfo.output_height)
jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline,
cinfo.output_height);
jpeg_finish_decompress(&cinfo);
// Expand 24->32 bpp
if ( cinfo.output_components == 3 ) {
for (uint j=0; jscanLine(j) + cinfo.output_width*3;
- QRgb *out = (QRgb*)( img->scanLine(j) );
+ QRgb *out = reinterpret_cast( img->scanLine(j) );
for (uint i=cinfo.output_width; i--; ) {
in-=3;
out[i] = qRgb(in[0], in[1], in[2]);
}
}
}
/*int newMax = qMax(cinfo.output_width, cinfo.output_height);
int newx = size_*cinfo.output_width / newMax;
int newy = size_*cinfo.output_height / newMax;*/
jpeg_destroy_decompress(&cinfo);
//image = img.smoothScale(newx,newy);
return true;
}
bool Utilities::isJPEG( const DB::FileName& fileName )
{
QString format= QString::fromLocal8Bit( QImageReader::imageFormat( fileName.absolute() ) );
return format == QString::fromLocal8Bit( "jpeg" );
}
namespace Utilities
{
QString normalizedFileName( const QString& fileName )
{
return QFileInfo(fileName).absoluteFilePath();
}
QString dereferenceSymLinks( const QString& fileName )
{
QFileInfo fi(fileName);
int rounds = 256;
while (fi.isSymLink() && --rounds > 0)
fi = QFileInfo(fi.readLink());
if (rounds == 0)
return QString();
return fi.filePath();
}
}
QString Utilities::stripEndingForwardSlash( const QString& fileName )
{
static QString slash = QString::fromLatin1("/");
if ( fileName.endsWith( slash ) )
return fileName.left( fileName.length()-1);
else
return fileName;
}
QString Utilities::relativeFolderName( const QString& fileName)
{
int index= fileName.lastIndexOf( QChar::fromLatin1('/'), -1);
if (index == -1)
return QString();
else
return fileName.left( index );
}
void Utilities::deleteDemo()
{
QString dir = QString::fromLatin1( "%1/kphotoalbum-demo-%2" ).arg(QDir::tempPath()).arg(QString::fromLocal8Bit( qgetenv( "LOGNAME" ) ) );
QUrl demoUrl = QUrl::fromLocalFile( dir );
KJob *delDemoJob = KIO::del( demoUrl );
KJobWidgets::setWindow( delDemoJob, MainWindow::Window::theMainWindow());
delDemoJob->exec();
}
QString Utilities::absoluteImageFileName( const QString& relativeName )
{
return stripEndingForwardSlash( Settings::SettingsData::instance()->imageDirectory() ) + QString::fromLatin1( "/" ) + relativeName;
}
QString Utilities::imageFileNameToAbsolute( const QString& fileName )
{
if ( fileName.startsWith( Settings::SettingsData::instance()->imageDirectory() ) )
return fileName;
else if ( fileName.startsWith( QString::fromLatin1("file://") ) )
return imageFileNameToAbsolute( fileName.mid( 7 ) ); // 7 == length("file://")
else if ( fileName.startsWith( QString::fromLatin1("/") ) )
return QString(); // Not within our image root
else
return absoluteImageFileName( fileName );
}
bool operator>( const QPoint& p1, const QPoint& p2)
{
return p1.y() > p2.y() || (p1.y() == p2.y() && p1.x() > p2.x() );
}
bool operator<( const QPoint& p1, const QPoint& p2)
{
return p1.y() < p2.y() || ( p1.y() == p2.y() && p1.x() < p2.x() );
}
const QSet& Utilities::supportedVideoExtensions()
{
static QSet videoExtensions;
if ( videoExtensions.empty() ) {
videoExtensions.insert( QString::fromLatin1( "3gp" ) );
videoExtensions.insert( QString::fromLatin1( "avi" ) );
videoExtensions.insert( QString::fromLatin1( "mp4" ) );
videoExtensions.insert( QString::fromLatin1( "m4v" ) );
videoExtensions.insert( QString::fromLatin1( "mpeg" ) );
videoExtensions.insert( QString::fromLatin1( "mpg" ) );
videoExtensions.insert( QString::fromLatin1( "qt" ) );
videoExtensions.insert( QString::fromLatin1( "mov" ) );
videoExtensions.insert( QString::fromLatin1( "moov" ) );
videoExtensions.insert( QString::fromLatin1( "qtvr" ) );
videoExtensions.insert( QString::fromLatin1( "rv" ) );
videoExtensions.insert( QString::fromLatin1( "3g2" ) );
videoExtensions.insert( QString::fromLatin1( "fli" ) );
videoExtensions.insert( QString::fromLatin1( "flc" ) );
videoExtensions.insert( QString::fromLatin1( "mkv" ) );
videoExtensions.insert( QString::fromLatin1( "mng" ) );
videoExtensions.insert( QString::fromLatin1( "asf" ) );
videoExtensions.insert( QString::fromLatin1( "asx" ) );
videoExtensions.insert( QString::fromLatin1( "wmp" ) );
videoExtensions.insert( QString::fromLatin1( "wmv" ) );
videoExtensions.insert( QString::fromLatin1( "ogm" ) );
videoExtensions.insert( QString::fromLatin1( "rm" ) );
videoExtensions.insert( QString::fromLatin1( "flv" ) );
videoExtensions.insert( QString::fromLatin1( "webm" ) );
videoExtensions.insert( QString::fromLatin1( "mts" ) );
videoExtensions.insert( QString::fromLatin1( "ogg" ) );
videoExtensions.insert( QString::fromLatin1( "ogv" ) );
videoExtensions.insert( QString::fromLatin1( "m2ts" ) );
}
return videoExtensions;
}
bool Utilities::isVideo( const DB::FileName& fileName )
{
QFileInfo fi( fileName.relative() );
QString ext = fi.suffix().toLower();
return supportedVideoExtensions().contains( ext );
}
bool Utilities::isRAW( const DB::FileName& fileName )
{
return ImageManager::RAWImageDecoder::isRAW( fileName );
}
QImage Utilities::scaleImage(const QImage &image, int w, int h, Qt::AspectRatioMode mode )
{
return image.scaled( w, h, mode, Settings::SettingsData::instance()->smoothScale() ? Qt::SmoothTransformation : Qt::FastTransformation );
}
QImage Utilities::scaleImage(const QImage &image, const QSize& s, Qt::AspectRatioMode mode )
{
return scaleImage( image, s.width(), s.height(), mode );
}
QString Utilities::cStringWithEncoding( const char *c_str, const QString& charset )
{
QTextCodec* codec = QTextCodec::codecForName( charset.toLatin1() );
if (!codec)
codec = QTextCodec::codecForLocale();
return codec->toUnicode( c_str );
}
DB::MD5 Utilities::MD5Sum( const DB::FileName& fileName )
{
DB::MD5 checksum;
QFile file( fileName.absolute() );
if ( file.open( QIODevice::ReadOnly ) )
{
QCryptographicHash md5calculator(QCryptographicHash::Md5);
while ( !file.atEnd() ) {
QByteArray md5Buffer( file.read( MD5_BUFFER_SIZE ) );
md5calculator.addData( md5Buffer );
}
file.close();
checksum = DB::MD5(QString::fromLatin1(md5calculator.result().toHex()));
}
return checksum;
}
QColor Utilities::contrastColor( const QColor& col )
{
if ( col.red() < 127 && col.green() < 127 && col.blue() < 127 )
return Qt::white;
else
return Qt::black;
}
void Utilities::saveImage( const DB::FileName& fileName, const QImage& image, const char* format )
{
const QFileInfo info(fileName.absolute());
QDir().mkpath(info.path());
const bool ok = image.save(fileName.absolute(),format);
Q_ASSERT(ok); Q_UNUSED(ok);
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/org.kde.kphotoalbum-import.desktop b/org.kde.kphotoalbum-import.desktop
index 127cc32a..af82a687 100644
--- a/org.kde.kphotoalbum-import.desktop
+++ b/org.kde.kphotoalbum-import.desktop
@@ -1,119 +1,120 @@
[Desktop Entry]
Name=KPhotoAlbum
Name[ast]=KPhotoAlbum
Name[be]=KPhotoAlbum
Name[bg]=KPhotoAlbum
Name[bs]=KPhotoAlbum
Name[ca]=KPhotoAlbum
Name[ca@valencia]=KPhotoAlbum
Name[cs]=KPhotoAlbum
Name[da]=KPhotoAlbum
Name[de]=KPhotoAlbum
Name[el]=KPhotoAlbum
Name[en_GB]=KPhotoAlbum
Name[eo]=KPhotoAlbum
Name[es]=KPhotoAlbum
Name[et]=KPhotoAlbum
+Name[eu]=KPhotoAlbum
Name[fi]=KPhotoAlbum
Name[fr]=KPhotoAlbum
Name[ga]=KPhotoAlbum
Name[gl]=KPhotoAlbum
Name[he]=KPhotoAlbum
Name[hi]=केफोटोएलबम
Name[hne]=केफोटोएलबम
Name[hr]=KPhotoAlbum
Name[hu]=KPhotoAlbum
Name[is]=KPhotoAlbum
Name[it]=KPhotoAlbum
Name[ja]=KPhotoAlbum
Name[km]=KPhotoAlbum
Name[ko]=KPhotoAlbum
Name[ku]=KPhotoAlbum
Name[lt]=KPhotoAlbum
Name[lv]=KPhotoAlbum
Name[ml]=കെഫോട്ടോആല്ബം
Name[mr]=के-फोटो-अल्बम
Name[ms]=KPhotoAlbum
Name[nb]=KPhotoAlbum
Name[nds]=KPhotoAlbum
Name[nl]=KPhotoAlbum
Name[nn]=KPhotoAlbum
Name[pa]=ਕੇ-ਫੋਟੋ-ਐਲਬਮ
Name[pl]=KPhotoAlbum
Name[pt]=KPhotoAlbum
Name[pt_BR]=KPhotoAlbum
Name[ro]=KAlbumFoto
Name[ru]=KPhotoAlbum
Name[sk]=KPhotoAlbum
Name[sl]=KPhotoAlbum
Name[sv]=Kfotoalbum
Name[tr]=KPhotoAlbum
Name[ug]=KPhotoAlbum
Name[uk]=KPhotoAlbum
Name[vi]=KPhotoAlbum
Name[x-test]=xxKPhotoAlbumxx
Name[zh_CN]=KPhotoAlbum
Name[zh_TW]=KPhotoAlbum
Exec=kphotoalbum --import %u
GenericName=Photo Album
GenericName[be]=Фотаальбом
GenericName[bg]=KPhotoAlbum
GenericName[bs]=Foto album
GenericName[ca]=Àlbum de fotografies
GenericName[ca@valencia]=Àlbum de fotografies
GenericName[cs]=Foto album
GenericName[da]=Fotoalbum
GenericName[de]=Fotoalbum
GenericName[el]=Φωτογραφικό άλμπουμ
GenericName[en_GB]=Photo Album
GenericName[eo]=Albumo de Fotoj
GenericName[es]=Álbum de fotos
GenericName[et]=Fotoalbum
-GenericName[eu]=Argazkien albuma
+GenericName[eu]=Argazki albuma
GenericName[fi]=Valokuva-albumi
GenericName[fr]=Album de photos
GenericName[ga]=Albam Grianghraf
GenericName[gl]=Álbum de fotos
GenericName[he]=אלבום תמונות
GenericName[hi]=फोटो एलबम
GenericName[hne]=फोटो एलबम
GenericName[hr]=Fotografski album
GenericName[hu]=Fényképalbum
GenericName[is]=Myndaalbúm
GenericName[it]=Album fotografico
GenericName[ja]=フォトアルバム
GenericName[km]=អាល់ប៊ុមរូបថត
GenericName[ko]=사진 앨범
GenericName[ku]=Albûma Wêneyan
GenericName[lt]=Nuotraukų albumas
GenericName[lv]=Foto albums
GenericName[ml]=ഫോട്ടോ ആല്ബം
GenericName[mr]=फोटो अल्बम
GenericName[ms]=Album Gambar
GenericName[nb]=Fotoalbum
GenericName[nds]=Fotoalbum
GenericName[nl]=Fotoalbum
GenericName[nn]=Fotoalbum
GenericName[pa]=ਫੋਟੋ ਐਲਬਮ
GenericName[pl]=Album na zdjęcia
GenericName[pt]=Álbum de Fotografias
GenericName[pt_BR]=Álbum de fotografias
GenericName[ro]=Album foto
GenericName[ru]=Работа с фотоальбомом
GenericName[sk]=Album fotiek
GenericName[sl]=Album fotografij
GenericName[sv]=Fotoalbum
GenericName[tr]=Fotoğraf Albümü
GenericName[ug]=سۈرەت ئالبومى
GenericName[uk]=Фотоальбом
GenericName[vi]=Tập ảnh chụp
GenericName[x-test]=xxPhoto Albumxx
GenericName[zh_CN]=相册
GenericName[zh_TW]=相簿
Terminal=false
Type=Application
X-KDE-StartupNotify=true
Icon=kphotoalbum
Categories=Qt;KDE;Graphics;
NoDisplay=true
MimeType=application/vnd.kde.kphotoalbum-import;
diff --git a/org.kde.kphotoalbum.appdata.xml b/org.kde.kphotoalbum.appdata.xml
index c4df1140..3e6e3cdd 100644
--- a/org.kde.kphotoalbum.appdata.xml
+++ b/org.kde.kphotoalbum.appdata.xml
@@ -1,95 +1,94 @@
org.kde.kphotoalbum
CC0-1.0
GPL-2.0
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
KPhotoAlbum
Kfotoalbum
KPhotoAlbum
KPhotoAlbum
xxKPhotoAlbumxx
KPhotoAlbum
KDE image management software
Programari per a gestionar les imatges al KDE
Programari per a gestionar les imatges al KDE
KDE-Bildverwaltungssoftware
KDE image management software
Software de gestión de imágenes para KDE
Logiciel de gestion d'images KDE
Software de xestión de imaxes de KDE
Perangkat lunak pengelolaan citra KDE
Software KDE per la gestione delle immagini
Software voor beheer van afbeeldingen
Oprogramowanie do zarządzania obrazami w KDE
Aplicação de gestão de imagens do KDE
Software de gerenciamento de imagens do KDE
KDE softvér na manipuláciu s obrázkami
KDE bildhanteringsprogramvara
KDE resim yönetim yazılımı
Програма для керування зображеннями у KDE
xxKDE image management softwarexx
KPhotoAlbum is an application for tagging and managing a photo collection and making it searchable.
El KPhotoAlbum és una aplicació per etiquetar i gestionar una col·lecció de fotografies i fer-les fàcils de trobar.
El KPhotoAlbum és una aplicació per etiquetar i gestionar una col·lecció de fotografies i fer-les fàcils de trobar.
KPhotoalbum ist eine Anwendung zum verschlagworten, verwalten und durchsuchen Ihrer Fotosammlung.
KPhotoAlbum is an application for tagging and managing a photo collection and making it searchable.
KPhotoAlbum es una aplicación para etiquetar y gestionar una colección de fotografías que permite realizar búsquedas.
KPhotoAlbum est une application permettant de gérer une collection de photos, en y définissant des balises et en y effectuant des recherches.
KPhotoAlbum é un aplicativo para etiquetar e xestionar unha colección de fotos e permitir buscar nela.
KPhotoAlbum adalah sebuah aplikasi untuk penandaan dan pengelolaan sebuah koleksi foto dan membuatnya dapat dicari.
KPhotoAlbum è un'applicazione per etichettare e gestire una raccolta di immagini in modo da poter eseguire ricerche al suo interno.
KPhotoAlbum is een toepassing voor aanbrengen van tags en beheer van een fotoverzameling en deze doorzoekbaar maken.
KPhotoAlbum jest programem do znaczenia i zarządzania zbiorami zdjęć.
O KPhotoAlbum é uma aplicação para marcar e gerir uma colecção de fotografias, tornando-a fácil de pesquisar.
KPhotoAlbum é um aplicativo para etiquetar e gerenciar uma coleção de fotos e torná-la pesquisável.
KPhotoAlbum je aplikácia na označovanie a správu kolekcie fotografií a jej prehľadávanie.
Kfotoalbum är ett program för att etikettera och hantera en fotosamling och göra den sökbar.
KPhotoAlbum resim koleksiyonunu etiketlemek ve yöneten ve onu aranabilir yapan için bir uygulamadır.
KPhotoAlbum — програма для створення міток у збірці фотографій та керування збіркою із можливостями пошуку зображень.
xxKPhotoAlbum is an application for tagging and managing a photo collection and making it searchable.xx
https://www.kphotoalbum.org/kphotoalbum_big.jpg
- The main screen screen in KPhotoAlbum
+ The main screen in KPhotoAlbum
La pantalla principal del KPhotoAlbum
La pantalla principal del KPhotoAlbum
- The main screen screen in KPhotoAlbum
+ Hlavní obrazovka pro KPhotoAlbum
+ The main screen in KPhotoAlbum
La ventana principal de KPhotoAlbum
L'écran principal de KPhotoAlbum
- A pantalla principal de KPhotoAlbum
- Layar layar utama dalam KPhotoAlbum
Het hoofdscherm in KPhotoAlbum
- O ecrã principal do KPhotoAlbum
+ O ecrã principal no KPhotoAlbum
A tela principal do KPhotoAlbum
Huvudskärmen i Kfotoalbum
Знімок головного вікна KPhotoAlbum
- xxThe main screen screen in KPhotoAlbumxx
+ xxThe main screen in KPhotoAlbumxx
http://kphotoalbum.org
https://bugs.kde.org/enter_bug.cgi?format=guided&product=kphotoalbum
kphotoalbum@mail.kdab.com
KDE
kphotoalbum
diff --git a/org.kde.kphotoalbum.desktop b/org.kde.kphotoalbum.desktop
index b64a7dff..cc7dfae3 100644
--- a/org.kde.kphotoalbum.desktop
+++ b/org.kde.kphotoalbum.desktop
@@ -1,118 +1,119 @@
[Desktop Entry]
Name=KPhotoAlbum
Name[ast]=KPhotoAlbum
Name[be]=KPhotoAlbum
Name[bg]=KPhotoAlbum
Name[bs]=KPhotoAlbum
Name[ca]=KPhotoAlbum
Name[ca@valencia]=KPhotoAlbum
Name[cs]=KPhotoAlbum
Name[da]=KPhotoAlbum
Name[de]=KPhotoAlbum
Name[el]=KPhotoAlbum
Name[en_GB]=KPhotoAlbum
Name[eo]=KPhotoAlbum
Name[es]=KPhotoAlbum
Name[et]=KPhotoAlbum
+Name[eu]=KPhotoAlbum
Name[fi]=KPhotoAlbum
Name[fr]=KPhotoAlbum
Name[ga]=KPhotoAlbum
Name[gl]=KPhotoAlbum
Name[he]=KPhotoAlbum
Name[hi]=केफोटोएलबम
Name[hne]=केफोटोएलबम
Name[hr]=KPhotoAlbum
Name[hu]=KPhotoAlbum
Name[is]=KPhotoAlbum
Name[it]=KPhotoAlbum
Name[ja]=KPhotoAlbum
Name[km]=KPhotoAlbum
Name[ko]=KPhotoAlbum
Name[ku]=KPhotoAlbum
Name[lt]=KPhotoAlbum
Name[lv]=KPhotoAlbum
Name[ml]=കെഫോട്ടോആല്ബം
Name[mr]=के-फोटो-अल्बम
Name[ms]=KPhotoAlbum
Name[nb]=KPhotoAlbum
Name[nds]=KPhotoAlbum
Name[nl]=KPhotoAlbum
Name[nn]=KPhotoAlbum
Name[pa]=ਕੇ-ਫੋਟੋ-ਐਲਬਮ
Name[pl]=KPhotoAlbum
Name[pt]=KPhotoAlbum
Name[pt_BR]=KPhotoAlbum
Name[ro]=KAlbumFoto
Name[ru]=KPhotoAlbum
Name[sk]=KPhotoAlbum
Name[sl]=KPhotoAlbum
Name[sv]=Kfotoalbum
Name[tr]=KPhotoAlbum
Name[ug]=KPhotoAlbum
Name[uk]=KPhotoAlbum
Name[vi]=KPhotoAlbum
Name[x-test]=xxKPhotoAlbumxx
Name[zh_CN]=KPhotoAlbum
Name[zh_TW]=KPhotoAlbum
Exec=kphotoalbum
GenericName=Photo Album
GenericName[be]=Фотаальбом
GenericName[bg]=KPhotoAlbum
GenericName[bs]=Foto album
GenericName[ca]=Àlbum de fotografies
GenericName[ca@valencia]=Àlbum de fotografies
GenericName[cs]=Foto album
GenericName[da]=Fotoalbum
GenericName[de]=Fotoalbum
GenericName[el]=Φωτογραφικό άλμπουμ
GenericName[en_GB]=Photo Album
GenericName[eo]=Albumo de Fotoj
GenericName[es]=Álbum de fotos
GenericName[et]=Fotoalbum
-GenericName[eu]=Argazkien albuma
+GenericName[eu]=Argazki albuma
GenericName[fi]=Valokuva-albumi
GenericName[fr]=Album de photos
GenericName[ga]=Albam Grianghraf
GenericName[gl]=Álbum de fotos
GenericName[he]=אלבום תמונות
GenericName[hi]=फोटो एलबम
GenericName[hne]=फोटो एलबम
GenericName[hr]=Fotografski album
GenericName[hu]=Fényképalbum
GenericName[is]=Myndaalbúm
GenericName[it]=Album fotografico
GenericName[ja]=フォトアルバム
GenericName[km]=អាល់ប៊ុមរូបថត
GenericName[ko]=사진 앨범
GenericName[ku]=Albûma Wêneyan
GenericName[lt]=Nuotraukų albumas
GenericName[lv]=Foto albums
GenericName[ml]=ഫോട്ടോ ആല്ബം
GenericName[mr]=फोटो अल्बम
GenericName[ms]=Album Gambar
GenericName[nb]=Fotoalbum
GenericName[nds]=Fotoalbum
GenericName[nl]=Fotoalbum
GenericName[nn]=Fotoalbum
GenericName[pa]=ਫੋਟੋ ਐਲਬਮ
GenericName[pl]=Album na zdjęcia
GenericName[pt]=Álbum de Fotografias
GenericName[pt_BR]=Álbum de fotografias
GenericName[ro]=Album foto
GenericName[ru]=Работа с фотоальбомом
GenericName[sk]=Album fotiek
GenericName[sl]=Album fotografij
GenericName[sv]=Fotoalbum
GenericName[tr]=Fotoğraf Albümü
GenericName[ug]=سۈرەت ئالبومى
GenericName[uk]=Фотоальбом
GenericName[vi]=Tập ảnh chụp
GenericName[x-test]=xxPhoto Albumxx
GenericName[zh_CN]=相册
GenericName[zh_TW]=相簿
Terminal=false
Type=Application
X-KDE-StartupNotify=true
Icon=kphotoalbum
X-DocPath=kphotoalbum/index.html
Categories=Qt;KDE;Graphics;Photography;
diff --git a/scripts/org.kde.kphotoalbum.open-raw.desktop b/scripts/org.kde.kphotoalbum.open-raw.desktop
index fd03147c..d0fafec0 100644
--- a/scripts/org.kde.kphotoalbum.open-raw.desktop
+++ b/scripts/org.kde.kphotoalbum.open-raw.desktop
@@ -1,86 +1,88 @@
# Copyright 2012 Miika Turkia
#
# Copy this file to /usr/local/share/applications/
# Update the Exec and Icon variables as appropriate
# Default icon comes from kipi-plugins-common package
[Desktop Entry]
Version=1.0
Name=Open in RAW editor
Name[bs]=Otvori RAW editor
Name[ca]=Obre en un editor RAW
Name[ca@valencia]=Obri en un editor RAW
Name[cs]=Otevřít v editoru RAW
Name[da]=Åbn i RAW-editor
Name[de]=Im RAW-Editor öffnen
Name[el]=Άνοιγμα σε επεξεργαστή RAW
Name[en_GB]=Open in RAW editor
Name[es]=Abrir en el editor RAW
Name[et]=Avamine toorfailide redaktoris
+Name[eu]=Ireki RAW editorean
Name[fi]=Avaa RAW-ohjelmassa
Name[fr]=Ouvrir dans un éditeur de fichiers bruts
Name[ga]=Oscail in eagarthóir RAW
Name[gl]=Abrir nun editor de RAW
Name[hu]=Megnyitás RAW szerkesztőben
Name[is]=Opna í RAW-myndvinnsluforriti
Name[it]=Apri nell'editor RAW
Name[km]=បើកក្នុងកម្មវិធីកែសម្រួល RAW
Name[ko]=RAW 편집기로 열기
Name[lt]=Atverti RAW redaktoriuje
Name[mr]=RAW संपादकात उघडा
Name[nb]=Åpne i RAW-redigering
Name[nds]=Mit RAW-Editor opmaken
Name[nl]=In RAW-bewerker openen
Name[nn]=Opna i program for råformatfiler
Name[pl]=Otwieranie w edytorze RAW
Name[pt]=Abrir no editor RAW
Name[pt_BR]=Abrir no editor RAW
Name[sk]=Otvoriť v RAW editore
Name[sl]=Odpri v urejevalniku RAW
Name[sv]=Öppna i editor för obehandlade bilder
Name[tr]=RAW resim düzenleyici ile aç
Name[uk]=Відкрити у редакторі цифрових негативів
Name[x-test]=xxOpen in RAW editorxx
Name[zh_TW]=在 RAW 編輯器開啟
Comment=Send RAW files to external editor on command line
Comment[bs]=Šalje RAW datoteke vanjskom uređivaču na komandnoj liniji
Comment[ca]=Envia els fitxers RAW a un editor extern a través de la línia d'ordres
Comment[ca@valencia]=Envia els fitxers RAW a un editor extern a través de la línia d'ordres
Comment[cs]=Poslat soubory RAW do externího editoru na příkazovém řádku
Comment[da]=Send RAW-filer til ekstern editor på kommandolinjen
Comment[de]=RAW-Dateien zu einem externen Editor auf der Befehlszeile senden
Comment[el]=Αποστολή RAW αρχείων σε εξωτερικό επεξεργαστή από τη γραμμή εντολών
Comment[en_GB]=Send RAW files to external editor on command line
Comment[es]=Enviar los archivos RAW a un editor externo por la línea de órdenes
Comment[et]=Toorfailide saatmine välisesse redaktorisse käsureal
+Comment[eu]=Bidali RAW fitxategiak kanpoko editorera komando lerroaren bidez
Comment[fi]=Avaa RAW-tiedostot toisella ohjelmalla
Comment[fr]=Envoie les fichiers bruts dans un éditeur externe en ligne de commandes
Comment[ga]=Seol comhaid RAW chuig eagarthóir seachtrach ar líne na n-orduithe
Comment[gl]=Enviar ficheiros en RAW a un editor externo na liña de ordes
Comment[hu]=RAW-fájlok küldése külső szerkesztőbe parancssorból
Comment[is]=Opnaðu RAW-skrár í utanaðkomandi myndvinnsluforriti frá skipanalínu
Comment[it]=Invia tutti i file RAW all'editor esterno sulla riga di comando
Comment[km]=ផ្ញើឯកសារ RAW ទៅកម្មវិធីកែសម្រួលខាងក្រៅនៅលើបន្ទាត់ពាក្យបញ្ជា
Comment[ko]=RAW 파일을 명령행 외부 편집기로 보내기
Comment[lt]=Siųsti RAW failus į Išorinį redaktorių komandos eilutėje
Comment[mr]=RAW फाईल्स आदेश ओळीवरील बाहेरच्या संपादकाकडे पाठवा
Comment[nb]=Send RAW-filer til ekstern redigerer på kommandolinja
Comment[nds]=RAW-Dateien op de Befehlsreeg en extern Editor tostüern
Comment[nl]=RAW-bestanden verzenden naar de externe bewerker op de opdrachtregel
Comment[nn]=Send råformatfiler til eksternt redigeringsprogram på kommandolinja
Comment[pl]=Wyślij pliki RAW do zewnętrznego edytora na wiersz poleceń
Comment[pt]=Enviar os ficheiros RAW para o editor externo na linha de comandos
Comment[pt_BR]=Enviar os arquivos RAW para o editor externo na linha de comando
Comment[sk]=Poslať RAW súbory do externého editora alebo príkazového riadku
Comment[sl]=Pošljite datoteke RAW v zunanji urejevalnik preko ukazne vrstice
Comment[sv]=Skicka obehandlade bilder till en extern editor på kommandoraden
Comment[tr]=RAW dosyaları komut satırından bir dış düzenleyiciye gönderin
Comment[uk]=Надіслати файли цифрових негативів до зовнішнього редактора за допомогою командного рядка
Comment[x-test]=xxSend RAW files to external editor on command linexx
Comment[zh_TW]=以命令列傳送 RAW 檔案到外部編輯器
Exec=open-raw.pl %F
Terminal=false
Icon=open-raw
Type=Application
Categories=Graphics;Photography;
MimeType=image/x-canon-crw;image/x-canon-cr2;image/x-sony-arw;image/x-nikon-nef;image/x-olympus-orf;image/x-pentax-pef;image/x-adobe-dng;image/x-raw;image/gif;image/tiff;image/jpeg;