diff --git a/NEWS b/NEWS index 4214767357..31ccbc735a 100644 --- a/NEWS +++ b/NEWS @@ -1,36 +1,37 @@ ***************************************************************************************************** digiKam 5.9.0 - Release date: 2018-02-25 NEW FEATURES: General : Libraw updated to last 0.18.7. BUGFIXES FROM BUGZILLA: 001 ==> 388977 - Error during upgrading core database schema from v8 to v9. 002 ==> 388867 - problem migrating databaseschema from 8 to 9. 003 ==> 388824 - UpdateSchemaFromV7ToV9 fails due to foreign key contraint in 5.8.0-01. 004 ==> 389042 - Auto-rotation/flip Images delete face tags. 005 ==> 337243 - Tool-Tips ignoring custom font size setting. 006 ==> 388320 - Selecting print layout does not stay selected. 007 ==> 389246 - Single click confirmation of suggested face tag. 008 ==> 389208 - Request addition of "Lens" to Tool-Tips (Icon Items). 009 ==> 389342 - Tags are copied although moving of file fails because file with the same name already exists in target location. 010 ==> 389420 - Thumbnails diminishing in size after switching Views. 011 ==> 389493 - Collection browse dialog should be consistent with other browse dialogs on KDE. 012 ==> 389512 - Timeline Thumbnails Not Shown. 013 ==> 389651 - Custom font size is not applied until you enter and exit the settings menu. 014 ==> 388345 - AppImage MySQL connection issues. 015 ==> 389827 - Ignores albums selection. 016 ==> 389835 - Incorrect DateTime manipulation if timestamp is before unix epoch time. 017 ==> 390121 - Thumbnail size is too small. 018 ==> 390136 - digiKam crashes when launching scan and recognize faces. 019 ==> 390325 - Segmentation fault - QMutex::lock(). 020 ==> 390443 - FAQ entry unaccurate - Thumbnail generation fails on video files. 021 ==> 390529 - Import from scanner not enabled. 022 ==> 390683 - Batch processing creates 0 byte files on Fujifilm RAF RAW images with lens correction. 023 ==> 390688 - Show pick and color label in thumbnail list [patch]. 024 ==> 390162 - digiKam-5.9.0 appimage fail to start. 025 ==> 384853 - Ungrouping images is not working. 026 ==> 390902 - Right sidebar icons not displayed. -027 ==> +027 ==> 391312 - Rename with time template can add colons (:) forbidden character in Windows 10. +028 ==> diff --git a/libs/album/albumpropsedit.cpp b/libs/album/albumpropsedit.cpp index 076ed821d6..637a6c8bcd 100644 --- a/libs/album/albumpropsedit.cpp +++ b/libs/album/albumpropsedit.cpp @@ -1,470 +1,476 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2003-03-09 * Description : Album properties dialog. * * Copyright (C) 2003-2004 by Renchi Raju * Copyright (C) 2005 by Tom Albers * Copyright (C) 2006-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "albumpropsedit.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes +#include "digikam_config.h" #include "dlayoutbox.h" #include "coredb.h" #include "album.h" #include "albummanager.h" #include "applicationsettings.h" #include "coredbaccess.h" #include "dxmlguiwindow.h" #include "dexpanderbox.h" #include "ddatepicker.h" namespace Digikam { class AlbumDatePicker : public DDatePicker { public: explicit AlbumDatePicker(QWidget* const widget) : DDatePicker(widget) { } ~AlbumDatePicker() { } void dateLineEnterPressed() { lineEnterPressed(); } }; // -------------------------------------------------------------------------------- class AlbumPropsEdit::Private { public: Private() : buttons(0), categoryCombo(0), parentCombo(0), titleEdit(0), commentsEdit(0), datePicker(0), album(0) { } QDialogButtonBox* buttons; QComboBox* categoryCombo; QComboBox* parentCombo; QLineEdit* titleEdit; QTextEdit* commentsEdit; AlbumDatePicker* datePicker; PAlbum* album; }; AlbumPropsEdit::AlbumPropsEdit(PAlbum* const album, bool create) : QDialog(0), d(new Private) { setModal(true); setWindowTitle(create ? i18n("New Album") : i18n("Edit Album")); d->buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); d->album = album; QWidget* const page = new QWidget(this); QLabel* const logo = new QLabel(page); logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48))); QLabel* const topLabel = new QLabel(page); if (create) { topLabel->setText(i18n("Create new Album in
\"%1\"
", album->title())); } else { topLabel->setText(i18n("\"%1\"
Album Properties
", album->title())); } topLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); topLabel->setWordWrap(false); DLineWidget* const topLine = new DLineWidget(Qt::Horizontal); // -------------------------------------------------------- QLabel* const titleLabel = new QLabel(page); titleLabel->setText(i18n("&Title:")); d->titleEdit = new QLineEdit(page); d->titleEdit->setClearButtonEnabled(true); titleLabel->setBuddy(d->titleEdit); +#ifdef Q_OS_WIN + QRegExp titleRx(QLatin1String("[^/:]+")); +#else QRegExp titleRx(QLatin1String("[^/]+")); +#endif + QValidator* const titleValidator = new QRegExpValidator(titleRx, this); d->titleEdit->setValidator(titleValidator); d->titleEdit->setPlaceholderText(i18n("Enter album title here...")); QLabel* const categoryLabel = new QLabel(page); categoryLabel->setText(i18n("Ca&tegory:")); d->categoryCombo = new QComboBox(page); d->categoryCombo->setEditable(true); categoryLabel->setBuddy(d->categoryCombo); QLabel* const parentLabel = new QLabel(page); parentLabel->setText(i18n("Ch&ild Of:")); d->parentCombo = new QComboBox(page); parentLabel->setBuddy(d->parentCombo); QLabel* const commentsLabel = new QLabel(page); commentsLabel->setText(i18n("Ca&ption:")); d->commentsEdit = new QTextEdit(page); commentsLabel->setBuddy(d->commentsEdit); d->commentsEdit->setWordWrapMode(QTextOption::WordWrap); d->commentsEdit->setPlaceholderText(i18n("Enter album caption here...")); d->commentsEdit->setAcceptRichText(false); QLabel* const dateLabel = new QLabel(page); dateLabel->setText(i18n("Album &date:")); d->datePicker = new AlbumDatePicker(page); dateLabel->setBuddy(d->datePicker); DHBox* const buttonRow = new DHBox(page); QPushButton* const dateLowButton = new QPushButton(i18nc("Selects the date of the oldest image", "&Oldest"), buttonRow); QPushButton* const dateAvgButton = new QPushButton(i18nc("Calculates the average date", "&Average"), buttonRow); QPushButton* const dateHighButton = new QPushButton(i18nc("Selects the date of the newest image", "Newest"), buttonRow); if (create) { setTabOrder(d->titleEdit, d->categoryCombo); setTabOrder(d->categoryCombo, d->parentCombo); setTabOrder(d->parentCombo, d->commentsEdit); setTabOrder(d->commentsEdit, d->datePicker); } else { setTabOrder(d->titleEdit, d->categoryCombo); setTabOrder(d->categoryCombo, d->commentsEdit); setTabOrder(d->commentsEdit, d->datePicker); d->parentCombo->hide(); parentLabel->hide(); } d->commentsEdit->setTabChangesFocus(true); // -------------------------------------------------------- QGridLayout* const grid = new QGridLayout(); grid->addWidget(logo, 0, 0, 1, 1); grid->addWidget(topLabel, 0, 1, 1, 1); grid->addWidget(topLine, 1, 0, 1, 2); grid->addWidget(titleLabel, 2, 0, 1, 1); grid->addWidget(d->titleEdit, 2, 1, 1, 1); grid->addWidget(categoryLabel, 3, 0, 1, 1); grid->addWidget(d->categoryCombo, 3, 1, 1, 1); if (create) { grid->addWidget(parentLabel, 4, 0, 1, 1); grid->addWidget(d->parentCombo, 4, 1, 1, 1); grid->addWidget(commentsLabel, 5, 0, 1, 1, Qt::AlignLeft | Qt::AlignTop); grid->addWidget(d->commentsEdit, 5, 1, 1, 1); grid->addWidget(dateLabel, 6, 0, 1, 1, Qt::AlignLeft | Qt::AlignTop); grid->addWidget(d->datePicker, 6, 1, 1, 1); grid->addWidget(buttonRow, 7, 1, 1, 1); } else { grid->addWidget(commentsLabel, 4, 0, 1, 1, Qt::AlignLeft | Qt::AlignTop); grid->addWidget(d->commentsEdit, 4, 1, 1, 1); grid->addWidget(dateLabel, 5, 0, 1, 1, Qt::AlignLeft | Qt::AlignTop); grid->addWidget(d->datePicker, 5, 1, 1, 1); grid->addWidget(buttonRow, 6, 1, 1, 1); } grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setContentsMargins(QMargins()); page->setLayout(grid); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(page); vbx->addWidget(d->buttons); setLayout(vbx); // Initialize --------------------------------------------- ApplicationSettings* const settings = ApplicationSettings::instance(); if (settings) { d->categoryCombo->addItem(QString()); QStringList Categories = settings->getAlbumCategoryNames(); d->categoryCombo->addItems(Categories); int categoryIndex = Categories.indexOf(album->category()); if (categoryIndex != -1) { // + 1 because of the empty item d->categoryCombo->setCurrentIndex(categoryIndex + 1); } } if (create) { d->titleEdit->setText(i18n("New Album")); d->datePicker->setDate(QDate::currentDate()); d->parentCombo->addItem(i18n("Selected Album (Default)")); d->parentCombo->addItem(i18nc("top level folder of album","Root of current collection")); } else { d->titleEdit->setText(album->title()); d->commentsEdit->setText(album->caption()); d->datePicker->setDate(album->date()); } d->titleEdit->selectAll(); d->titleEdit->setFocus(); // -- slots connections ------------------------------------------- connect(d->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(slotTitleChanged(QString))); connect(dateLowButton, SIGNAL(clicked()), this, SLOT(slotDateLowButtonClicked())); connect(dateAvgButton, SIGNAL(clicked()), this, SLOT(slotDateAverageButtonClicked())); connect(dateHighButton, SIGNAL(clicked()), this, SLOT(slotDateHighButtonClicked())); connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(slotHelp())); } AlbumPropsEdit::~AlbumPropsEdit() { delete d; } QString AlbumPropsEdit::title() const { return d->titleEdit->text(); } QString AlbumPropsEdit::comments() const { return d->commentsEdit->document()->toPlainText(); } QDate AlbumPropsEdit::date() const { // See bug #267944 : update calendar view if user enter a date in text field. d->datePicker->dateLineEnterPressed(); return d->datePicker->date(); } int AlbumPropsEdit::parent() const { return d->parentCombo->currentIndex(); } QString AlbumPropsEdit::category() const { QString name = d->categoryCombo->currentText(); if (name.isEmpty()) { name = i18n("Uncategorized Album"); } return name; } QStringList AlbumPropsEdit::albumCategories() const { QStringList Categories; ApplicationSettings* const settings = ApplicationSettings::instance(); if (settings) { Categories = settings->getAlbumCategoryNames(); } QString currentCategory = d->categoryCombo->currentText(); if (Categories.indexOf(currentCategory) == -1) { Categories.append(currentCategory); } Categories.sort(); return Categories; } bool AlbumPropsEdit::editProps(PAlbum* const album, QString& title, QString& comments, QDate& date, QString& category, QStringList& albumCategories) { QPointer dlg = new AlbumPropsEdit(album); bool ok = (dlg->exec() == QDialog::Accepted); title = dlg->title(); comments = dlg->comments(); date = dlg->date(); category = dlg->category(); albumCategories = dlg->albumCategories(); delete dlg; return ok; } bool AlbumPropsEdit::createNew(PAlbum* const parent, QString& title, QString& comments, QDate& date, QString& category, QStringList& albumCategories, int& parentSelector) { QPointer dlg = new AlbumPropsEdit(parent, true); bool ok = (dlg->exec() == QDialog::Accepted); title = dlg->title(); comments = dlg->comments(); date = dlg->date(); category = dlg->category(); albumCategories = dlg->albumCategories(); parentSelector = dlg->parent(); delete dlg; return ok; } void AlbumPropsEdit::slotTitleChanged(const QString& newtitle) { QRegExp emptyTitle = QRegExp(QLatin1String("^\\s*$")); bool enable = (!emptyTitle.exactMatch(newtitle) && !newtitle.isEmpty()); d->buttons->button(QDialogButtonBox::Ok)->setEnabled(enable); } void AlbumPropsEdit::slotDateLowButtonClicked() { setCursor(Qt::WaitCursor); QDate lowDate = CoreDbAccess().db()->getAlbumLowestDate(d->album->id()); if (lowDate.isValid()) { d->datePicker->setDate(lowDate); } setCursor(Qt::ArrowCursor); } void AlbumPropsEdit::slotDateHighButtonClicked() { setCursor(Qt::WaitCursor); QDate highDate = CoreDbAccess().db()->getAlbumHighestDate(d->album->id()); if (highDate.isValid()) { d->datePicker->setDate(highDate); } setCursor(Qt::ArrowCursor); } void AlbumPropsEdit::slotDateAverageButtonClicked() { setCursor(Qt::WaitCursor); QDate avDate = CoreDbAccess().db()->getAlbumAverageDate(d->album->id()); setCursor(Qt::ArrowCursor); if (avDate.isValid()) { d->datePicker->setDate(avDate); } else { QMessageBox::critical(this, i18n("Could Not Calculate Average"), i18n("Could not calculate date average for this album.")); } } void AlbumPropsEdit::slotHelp() { DXmlGuiWindow::openHandbook(); } } // namespace Digikam diff --git a/utilities/advancedrename/advancedrenamemanager.cpp b/utilities/advancedrename/advancedrenamemanager.cpp index 1c0c207af7..ddaafea134 100644 --- a/utilities/advancedrename/advancedrenamemanager.cpp +++ b/utilities/advancedrename/advancedrenamemanager.cpp @@ -1,484 +1,492 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-08-08 * Description : a class that manages the files to be renamed * * Copyright (C) 2009-2012 by Andi Clemens * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "advancedrenamemanager.h" // C++ includes #include // Qt includes #include #include #include // Local includes #include "digikam_debug.h" +#include "digikam_config.h" #include "advancedrenamewidget.h" #include "parsesettings.h" #include "parser.h" #include "defaultrenameparser.h" #include "importrenameparser.h" #include "imageinfo.h" namespace Digikam { struct SortByNameCaseInsensitive { bool operator() (const QString& s1, const QString& s2) const { return s1.toLower() < s2.toLower(); } }; struct SortByDate { bool operator() (const QString& s1, const QString& s2) const { QFileInfo fi1(s1); QFileInfo fi2(s2); return fi1.created() < fi2.created(); } }; struct SortBySize { bool operator() (const QString& s1, const QString& s2) const { QFileInfo fi1(s1); QFileInfo fi2(s2); return fi1.size() < fi2.size(); } }; class AdvancedRenameManager::Private { public: Private() : parser(0), widget(0), parserType(AdvancedRenameManager::DefaultParser), sortAction(AdvancedRenameManager::SortCustom), sortDirection(AdvancedRenameManager::SortAscending), startIndex(1) { } QStringList files; QMap fileIndexMap; QMap folderIndexMap; QMap fileGroupIndexMap; QMap fileDatesMap; QMap renamedFiles; Parser* parser; AdvancedRenameWidget* widget; AdvancedRenameManager::ParserType parserType; AdvancedRenameManager::SortAction sortAction; AdvancedRenameManager::SortDirection sortDirection; int startIndex; }; AdvancedRenameManager::AdvancedRenameManager() : d(new Private) { setParserType(DefaultParser); } AdvancedRenameManager::AdvancedRenameManager(const QList& files) : d(new Private) { setParserType(DefaultParser); addFiles(files); } AdvancedRenameManager::~AdvancedRenameManager() { clearAll(); delete d->parser; delete d; } void AdvancedRenameManager::setSortAction(SortAction action) { d->sortAction = action; initialize(); QList list; foreach(const QString& file, d->files) { list << QUrl::fromLocalFile(file); } emit signalSortingChanged(list); } AdvancedRenameManager::SortAction AdvancedRenameManager::sortAction() const { return d->sortAction; } void AdvancedRenameManager::setSortDirection(SortDirection direction) { d->sortDirection = direction; initialize(); QList list; foreach(const QString& file, d->files) { list << QUrl::fromLocalFile(file); } emit signalSortingChanged(list); } AdvancedRenameManager::SortDirection AdvancedRenameManager::sortDirection() const { return d->sortDirection; } void AdvancedRenameManager::setStartIndex(int index) { d->startIndex = index < 1 ? 1 : index; initialize(); } void AdvancedRenameManager::setWidget(AdvancedRenameWidget* widget) { if (!widget) { d->widget = 0; return; } d->widget = widget; setParserType(d->parserType); } void AdvancedRenameManager::setParserType(ParserType type) { delete d->parser; if (type == ImportParser) { d->parser = new ImportRenameParser(); } else { d->parser = new DefaultRenameParser(); } d->parserType = type; if (d->widget) { d->widget->setParser(d->parser); if (type == ImportParser) { d->widget->setLayoutStyle(AdvancedRenameWidget::LayoutCompact); } else { d->widget->setLayoutStyle(AdvancedRenameWidget::LayoutNormal); } } } Parser* AdvancedRenameManager::getParser() const { return d->parser; } void AdvancedRenameManager::parseFiles() { if (!d->widget) { return; } parseFiles(d->widget->parseString()); } void AdvancedRenameManager::parseFiles(const ParseSettings& _settings) { if (!d->widget) { if (!(_settings.parseString.isEmpty())) { parseFiles(_settings.parseString, _settings); } } else { parseFiles(d->widget->parseString(), _settings); } } void AdvancedRenameManager::parseFiles(const QString& parseString) { if (!d->parser) { return; } d->parser->reset(); foreach(const QString& file, d->files) { QUrl url = QUrl::fromLocalFile(file); ParseSettings settings; settings.fileUrl = url; settings.parseString = parseString; settings.startIndex = d->startIndex; settings.creationTime = d->fileDatesMap[file]; settings.manager = this; d->renamedFiles[file] = d->parser->parse(settings); } } void AdvancedRenameManager::parseFiles(const QString& parseString, const ParseSettings& _settings) { if (!d->parser) { return; } d->parser->reset(); foreach(const QString& file, d->files) { QUrl url = QUrl::fromLocalFile(file); ParseSettings settings = _settings; settings.fileUrl = url; settings.parseString = parseString; settings.startIndex = d->startIndex; settings.manager = this; d->renamedFiles[file] = d->parser->parse(settings); } } void AdvancedRenameManager::addFiles(const QList& files) { foreach(const ParseSettings& ps, files) { addFile(ps.fileUrl.toLocalFile(), ps.creationTime); } initialize(); } void AdvancedRenameManager::clearMappings() { d->fileIndexMap.clear(); d->folderIndexMap.clear(); d->fileGroupIndexMap.clear(); d->renamedFiles.clear(); } void AdvancedRenameManager::clearAll() { d->files.clear(); clearMappings(); } void AdvancedRenameManager::reset() { clearAll(); resetState(); } void AdvancedRenameManager::resetState() { d->parser->reset(); d->startIndex = 1; } void AdvancedRenameManager::initializeFileList() { QStringList tmpFiles = d->files; switch (d->sortAction) { case SortName: { std::sort(tmpFiles.begin(), tmpFiles.end(), SortByNameCaseInsensitive()); break; } case SortDate: { std::sort(tmpFiles.begin(), tmpFiles.end(), SortByDate()); break; } case SortSize: { std::sort(tmpFiles.begin(), tmpFiles.end(), SortBySize()); break; } case SortCustom: default: break; } if (d->sortAction != SortCustom && d->sortDirection == SortDescending) { std::reverse(tmpFiles.begin(), tmpFiles.end()); } d->files = tmpFiles; } QStringList AdvancedRenameManager::fileList() const { return d->files; } QMap AdvancedRenameManager::newFileList() const { return d->renamedFiles; } bool AdvancedRenameManager::initialize() { if (d->files.isEmpty()) { return false; } // clear mappings clearMappings(); // initialize the file list according to the sort action and direction initializeFileList(); // fill normal index map { int counter = 1; foreach(const QString& file, d->files) { d->fileIndexMap[file] = counter++; } } // fill file group index map { int counter = 1; foreach(const QString& file, d->files) { if (!d->fileGroupIndexMap.contains(fileGroupKey(file))) { d->fileGroupIndexMap[fileGroupKey(file)] = counter++; } } } // fill folder group index map { QMap > dirMap; foreach(const QString& file, d->files) { QFileInfo fi(file); QString path = fi.absolutePath(); if (!path.isEmpty()) { if (!dirMap.contains(path)) { dirMap[path] = QList(); } dirMap[path].push_back(file); } } foreach(const QString& dir, dirMap.keys()) { int index = 0; foreach(const QString& f, dirMap[dir]) { if (!d->folderIndexMap.contains(f)) { d->folderIndexMap[f] = ++index; } } } } return true; } QString AdvancedRenameManager::fileGroupKey(const QString& filename) const { QFileInfo fi(filename); QString tmp = fi.absoluteFilePath().left(fi.absoluteFilePath().lastIndexOf(fi.suffix())); return tmp; } int AdvancedRenameManager::indexOfFile(const QString& filename) { return d->fileIndexMap.value(filename, -1); } int AdvancedRenameManager::indexOfFolder(const QString& filename) { return d->folderIndexMap.value(filename, -1); } int AdvancedRenameManager::indexOfFileGroup(const QString& filename) { return d->fileGroupIndexMap.value(fileGroupKey(filename), -1); } QString AdvancedRenameManager::newName(const QString& filename) const { - return d->renamedFiles.value(filename, filename); + QString newName = d->renamedFiles.value(filename, filename); + +#ifdef Q_OS_WIN + // For the Windows file system, we need to replace unsupported characters. + newName.replace(QLatin1Char(':'), QLatin1Char('-')); +#endif + + return newName; } void AdvancedRenameManager::addFile(const QString& filename) const { d->files << filename; } void AdvancedRenameManager::addFile(const QString& filename, const QDateTime& datetime) const { d->files << filename; d->fileDatesMap[filename] = datetime; } } // namespace Digikam