diff --git a/AnnotationDialog/DateEdit.cpp b/AnnotationDialog/DateEdit.cpp
index e15113b8..8ff75308 100644
--- a/AnnotationDialog/DateEdit.cpp
+++ b/AnnotationDialog/DateEdit.cpp
@@ -1,350 +1,362 @@
/* 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.
*/
/**
* A date editing widget that consists of an editable combo box.
* The combo box contains the date in text form, and clicking the combo
* box arrow will display a 'popup' style date picker.
*
* This widget also supports advanced features like allowing the user
* to type in the day name to get the date. The following keywords
* are supported (in the native language): tomorrow, yesterday, today,
* monday, tuesday, wednesday, thursday, friday, saturday, sunday.
*
* @author Cornelius Schumacher
* @author Mike Pilone
* @author David Jarvie
* @author Jesper Pedersen
*/
#include "DateEdit.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AnnotationDialog::DateEdit::DateEdit(bool isStartEdit, QWidget *parent)
: QComboBox(parent)
, m_defaultValue(QDate::currentDate())
, m_ReadOnly(false)
, m_DiscardNextMousePress(false)
, m_IsStartEdit(isStartEdit)
{
setEditable(true);
setMaxCount(1); // need at least one entry for popup to work
m_value = m_defaultValue;
QString today = QDate::currentDate().toString(QString::fromLatin1("dd. MMM yyyy"));
addItem(QString::fromLatin1(""));
setCurrentIndex(0);
setItemText(0, QString::fromLatin1(""));
setMinimumSize(sizeHint());
m_DateFrame = new QFrame;
m_DateFrame->setWindowFlags(Qt::Popup);
QVBoxLayout *layout = new QVBoxLayout(m_DateFrame);
m_DateFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
m_DateFrame->setLineWidth(3);
m_DateFrame->hide();
m_DateFrame->installEventFilter(this);
m_DatePicker = new KDatePicker(m_value, m_DateFrame);
layout->addWidget(m_DatePicker);
connect(lineEdit(), &QLineEdit::editingFinished, this, &DateEdit::lineEnterPressed);
connect(this, &QComboBox::currentTextChanged, this, &DateEdit::slotTextChanged);
connect(m_DatePicker, &KDatePicker::dateEntered, this, &DateEdit::dateEntered);
connect(m_DatePicker, &KDatePicker::dateSelected, this, &DateEdit::dateSelected);
// Create the keyword list. This will be used to match against when the user
// enters information.
m_KeywordMap[i18n("tomorrow")] = 1;
m_KeywordMap[i18n("today")] = 0;
m_KeywordMap[i18n("yesterday")] = -1;
QString dayName;
for (int i = 1; i <= 7; ++i) {
dayName = QLocale().dayName(i, QLocale::LongFormat).toLower();
m_KeywordMap[dayName] = i + 100;
}
lineEdit()->installEventFilter(this); // handle keyword entry
m_TextChanged = false;
m_HandleInvalid = false;
}
AnnotationDialog::DateEdit::~DateEdit()
{
}
void AnnotationDialog::DateEdit::setDate(const QDate &newDate)
{
QString dateString = QString::fromLatin1("");
if (newDate.isValid())
dateString = DB::ImageDate(newDate).toString(false);
m_TextChanged = false;
// We do not want to generate a signal here, since we explicitly setting
// the date
bool b = signalsBlocked();
blockSignals(true);
setItemText(0, dateString);
blockSignals(b);
m_value = newDate;
}
void AnnotationDialog::DateEdit::setHandleInvalid(bool handleInvalid)
{
m_HandleInvalid = handleInvalid;
}
bool AnnotationDialog::DateEdit::handlesInvalid() const
{
return m_HandleInvalid;
}
void AnnotationDialog::DateEdit::setReadOnly(bool readOnly)
{
m_ReadOnly = readOnly;
lineEdit()->setReadOnly(readOnly);
}
bool AnnotationDialog::DateEdit::isReadOnly() const
{
return m_ReadOnly;
}
bool AnnotationDialog::DateEdit::validate(const QDate &)
{
return true;
}
QDate AnnotationDialog::DateEdit::date() const
{
QDate dt;
readDate(dt, 0);
return dt;
}
QDate AnnotationDialog::DateEdit::defaultDate() const
{
return m_defaultValue;
}
void AnnotationDialog::DateEdit::setDefaultDate(const QDate &date)
{
m_defaultValue = date;
}
void AnnotationDialog::DateEdit::showPopup()
{
if (m_ReadOnly)
return;
QRect desk = QApplication::desktop()->availableGeometry(this);
// ensure that the popup is fully visible even when the DateEdit is off-screen
QPoint popupPoint = mapToGlobal(QPoint(0, 0));
if (popupPoint.x() < desk.left()) {
popupPoint.setX(desk.x());
} else if (popupPoint.x() + width() > desk.right()) {
popupPoint.setX(desk.right() - width());
}
int dateFrameHeight = m_DateFrame->sizeHint().height();
if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
popupPoint.setY(popupPoint.y() - dateFrameHeight);
} else {
popupPoint.setY(popupPoint.y() + height());
}
m_DateFrame->move(popupPoint);
QDate date;
readDate(date, 0);
if (date.isValid()) {
m_DatePicker->setDate(date);
} else {
m_DatePicker->setDate(m_defaultValue);
}
m_DateFrame->show();
}
void AnnotationDialog::DateEdit::dateSelected(QDate newDate)
{
if ((m_HandleInvalid || newDate.isValid()) && validate(newDate)) {
setDate(newDate);
emit dateChanged(newDate);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ emit dateChanged(DB::ImageDate(newDate.startOfDay(), newDate.startOfDay()));
+#else
emit dateChanged(DB::ImageDate(QDateTime(newDate), QDateTime(newDate)));
+#endif
m_DateFrame->hide();
}
}
void AnnotationDialog::DateEdit::dateEntered(QDate newDate)
{
if ((m_HandleInvalid || newDate.isValid()) && validate(newDate)) {
setDate(newDate);
emit dateChanged(newDate);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ emit dateChanged(DB::ImageDate(newDate.startOfDay(), newDate.startOfDay()));
+#else
emit dateChanged(DB::ImageDate(QDateTime(newDate), QDateTime(newDate)));
+#endif
}
}
void AnnotationDialog::DateEdit::lineEnterPressed()
{
if (!m_TextChanged)
return;
QDate date;
QDate end;
if (readDate(date, &end) && (m_HandleInvalid || date.isValid()) && validate(date)) {
// Update the edit. This is needed if the user has entered a
// word rather than the actual date.
setDate(date);
emit(dateChanged(date));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ emit dateChanged(DB::ImageDate(date.startOfDay(), end.startOfDay()));
+#else
emit dateChanged(DB::ImageDate(QDateTime(date), QDateTime(end)));
+#endif
} else {
// Invalid or unacceptable date - revert to previous value
setDate(m_value);
emit invalidDateEntered();
}
}
bool AnnotationDialog::DateEdit::inputIsValid() const
{
QDate date;
return readDate(date, 0) && date.isValid();
}
/* Reads the text from the line edit. If the text is a keyword, the
* word will be translated to a date. If the text is not a keyword, the
* text will be interpreted as a date.
* Returns true if the date text is blank or valid, false otherwise.
*/
bool AnnotationDialog::DateEdit::readDate(QDate &result, QDate *end) const
{
QString text = currentText();
if (text.isEmpty()) {
result = QDate();
} else if (m_KeywordMap.contains(text.toLower())) {
QDate today = QDate::currentDate();
int i = m_KeywordMap[text.toLower()];
if (i >= 100) {
/* A day name has been entered. Convert to offset from today.
* This uses some math tricks to figure out the offset in days
* to the next date the given day of the week occurs. There
* are two cases, that the new day is >= the current day, which means
* the new day has not occurred yet or that the new day < the current day,
* which means the new day is already passed (so we need to find the
* day in the next week).
*/
i -= 100;
int currentDay = today.dayOfWeek();
if (i >= currentDay)
i -= currentDay;
else
i += 7 - currentDay;
}
result = today.addDays(i);
} else {
result = DB::ImageDate::parseDate(text, m_IsStartEdit);
if (end)
*end = DB::ImageDate::parseDate(text, false);
return result.isValid();
}
return true;
}
void AnnotationDialog::DateEdit::keyPressEvent(QKeyEvent *event)
{
int step = 0;
if (event->key() == Qt::Key_Up)
step = 1;
else if (event->key() == Qt::Key_Down)
step = -1;
setDate(m_value.addDays(step));
QComboBox::keyPressEvent(event);
}
/* Checks for a focus out event. The display of the date is updated
* to display the proper date when the focus leaves.
*/
bool AnnotationDialog::DateEdit::eventFilter(QObject *obj, QEvent *e)
{
if (obj == lineEdit()) {
if (e->type() == QEvent::Wheel) {
// Up and down arrow keys step the date
QWheelEvent *we = dynamic_cast(e);
Q_ASSERT(we != nullptr);
int step = 0;
step = we->delta() > 0 ? 1 : -1;
if (we->orientation() == Qt::Vertical) {
setDate(m_value.addDays(step));
}
}
} else {
// It's a date picker event
switch (e->type()) {
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress: {
QMouseEvent *me = (QMouseEvent *)e;
if (!m_DateFrame->rect().contains(me->pos())) {
QPoint globalPos = m_DateFrame->mapToGlobal(me->pos());
if (QApplication::widgetAt(globalPos) == this) {
// The date picker is being closed by a click on the
// DateEdit widget. Avoid popping it up again immediately.
m_DiscardNextMousePress = true;
}
}
break;
}
default:
break;
}
}
return false;
}
void AnnotationDialog::DateEdit::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton && m_DiscardNextMousePress) {
m_DiscardNextMousePress = false;
return;
}
QComboBox::mousePressEvent(e);
}
void AnnotationDialog::DateEdit::slotTextChanged(const QString &)
{
m_TextChanged = true;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DB/ImageDate.cpp b/DB/ImageDate.cpp
index 7a1383ba..d82409d0 100644
--- a/DB/ImageDate.cpp
+++ b/DB/ImageDate.cpp
@@ -1,400 +1,427 @@
/* 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 "ImageDate.h"
#include
#include
#include
using namespace DB;
ImageDate::ImageDate(const QDate &date)
{
m_start = QDateTime(date, QTime(0, 0, 0));
m_end = QDateTime(date, QTime(0, 0, 0));
}
ImageDate::ImageDate(const QDateTime &date)
{
m_start = date;
m_end = date;
}
bool ImageDate::operator<=(const ImageDate &other) const
{
// This operator is used by QMap when checking for equal elements, thus we need the second part too.
return m_start < other.m_start || (m_start == other.m_start && m_end <= other.m_end);
}
ImageDate::ImageDate()
{
}
bool ImageDate::isNull() const
{
return m_start.isNull();
}
bool ImageDate::isFuzzy() const
{
return m_start != m_end;
}
static bool isFirstSecOfMonth(const QDateTime &date)
{
return date.date().day() == 1 && date.time().hour() == 0 && date.time().minute() == 0;
}
static bool isLastSecOfMonth(QDateTime date)
{
return isFirstSecOfMonth(date.addSecs(1));
}
static bool isFirstSecOfDay(const QDateTime &time)
{
return time.time().hour() == 0 && time.time().minute() == 0 && time.time().second() == 0;
}
static bool isLastSecOfDay(const QDateTime &time)
{
return time.time().hour() == 23 && time.time().minute() == 59 && time.time().second() == 59;
}
QString ImageDate::toString(bool withTime) const
{
if (m_start.isNull())
return QString();
if (m_start == m_end) {
if (withTime && !isFirstSecOfDay(m_start))
return m_start.toString(QString::fromLatin1("d. MMM yyyy hh:mm:ss"));
else
return m_start.toString(QString::fromLatin1("d. MMM yyyy"));
}
// start is different from end.
if (isFirstSecOfMonth(m_start) && isLastSecOfMonth(m_end)) {
if (m_start.date().month() == 1 && m_end.date().month() == 12) {
if (m_start.date().year() == m_end.date().year()) {
// 2005
return QString::number(m_start.date().year());
} else {
// 2005-2006
return QString::fromLatin1("%1 - %2").arg(m_start.date().year()).arg(m_end.date().year());
}
} else {
// a whole month, but not a whole year.
if (m_start.date().year() == m_end.date().year() && m_start.date().month() == m_end.date().month()) {
// jan 2005
return QString::fromLatin1("%1 %2")
.arg(QLocale().standaloneMonthName(m_start.date().month(), QLocale::ShortFormat))
.arg(m_start.date().year());
} else {
// jan 2005 - feb 2006
return QString::fromLatin1("%1 %2 - %3 %4")
.arg(QLocale().standaloneMonthName(m_start.date().month(), QLocale::ShortFormat))
.arg(m_start.date().year())
.arg(QLocale().standaloneMonthName(m_end.date().month(), QLocale::ShortFormat))
.arg(m_end.date().year());
}
}
}
if (!withTime || (isFirstSecOfDay(m_start) && isLastSecOfDay(m_end))) {
if (m_start.date() == m_end.date()) {
// A whole day
return m_start.toString(QString::fromLatin1("d. MMM yyyy"));
} else {
// A day range
return QString::fromLatin1("%1 - %2")
.arg(m_start.toString(QString::fromLatin1("d. MMM yyyy")))
.arg(m_end.toString(QString::fromLatin1("d. MMM yyyy")));
}
}
// Range smaller than one day.
if (withTime && (!isFirstSecOfDay(m_start) || !isLastSecOfDay(m_end)))
return QString::fromLatin1("%1 - %2")
.arg(m_start.toString(QString::fromLatin1("d. MMM yyyy hh:mm")))
.arg(m_end.toString(QString::fromLatin1("d. MMM yyyy hh:mm")));
else
return QString::fromLatin1("%1 - %2")
.arg(m_start.toString(QString::fromLatin1("d. MMM yyyy")))
.arg(m_end.toString(QString::fromLatin1("d. MMM yyyy")));
}
bool ImageDate::operator==(const ImageDate &other) const
{
return m_start == other.m_start && m_end == other.m_end;
}
bool ImageDate::operator!=(const ImageDate &other) const
{
return !(*this == other);
}
QString ImageDate::formatRegexp()
{
static QString str;
if (str.isEmpty()) {
str = QString::fromLatin1("^((\\d\\d?)([-. /]+|$))?((");
QStringList months = monthNames();
for (QStringList::ConstIterator monthIt = months.constBegin(); monthIt != months.constEnd(); ++monthIt)
str += QString::fromLatin1("%1|").arg(*monthIt);
str += QString::fromLatin1("\\d?\\d)([-. /]+|$))?(\\d\\d(\\d\\d)?)?$");
}
return str;
}
QDateTime ImageDate::start() const
{
return m_start;
}
QDateTime ImageDate::end() const
{
return m_end;
}
bool ImageDate::operator<(const ImageDate &other) const
{
return start() < other.start() || (start() == other.start() && end() < other.end());
}
ImageDate::ImageDate(const QDateTime &start, const QDateTime &end)
{
if (!start.isValid() || !end.isValid() || start <= end) {
m_start = start;
m_end = end;
} else {
m_start = end;
m_end = start;
}
}
ImageDate::ImageDate(const QDate &start, const QDate &end)
{
if (!start.isValid() || !end.isValid() || start <= end) {
m_start = QDateTime(start, QTime(0, 0, 0));
m_end = QDateTime(end, QTime(23, 59, 59));
} else {
m_start = QDateTime(end, QTime(0, 0, 0));
m_end = QDateTime(start, QTime(23, 59, 59));
}
}
static QDate addMonth(int year, int month)
{
if (month == 12) {
year++;
month = 1;
} else
month++;
return QDate(year, month, 1);
}
ImageDate::ImageDate(int yearFrom, int monthFrom, int dayFrom, int yearTo, int monthTo, int dayTo, int hourFrom, int minuteFrom, int secondFrom)
{
if (yearFrom <= 0) {
m_start = QDateTime();
m_end = QDateTime();
return;
}
if (monthFrom <= 0) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_start = QDate(yearFrom, 1, 1).startOfDay();
+ m_end = QDate(yearFrom + 1, 1, 1).startOfDay().addSecs(-1);
+#else
m_start = QDateTime(QDate(yearFrom, 1, 1));
m_end = QDateTime(QDate(yearFrom + 1, 1, 1)).addSecs(-1);
+#endif
} else if (dayFrom <= 0) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_start = QDate(yearFrom, monthFrom, 1).startOfDay();
+ m_end = addMonth(yearFrom, monthFrom).startOfDay().addSecs(-1);
+#else
m_start = QDateTime(QDate(yearFrom, monthFrom, 1));
m_end = QDateTime(addMonth(yearFrom, monthFrom)).addSecs(-1);
+#endif
} else if (hourFrom < 0) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_start = QDate(yearFrom, monthFrom, dayFrom).startOfDay();
+ m_end = QDate(yearFrom, monthFrom, dayFrom).addDays(1).startOfDay().addSecs(-1);
+#else
m_start = QDateTime(QDate(yearFrom, monthFrom, dayFrom));
m_end = QDateTime(QDate(yearFrom, monthFrom, dayFrom).addDays(1)).addSecs(-1);
+#endif
} else if (minuteFrom < 0) {
m_start = QDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, 0, 0));
m_end = QDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, 23, 59));
} else if (secondFrom < 0) {
m_start = QDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, 0));
m_end = QDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, 59));
} else {
m_start = QDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, secondFrom));
m_end = m_start;
}
if (yearTo > 0) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_end = QDate(yearTo + 1, 1, 1).startOfDay().addSecs(-1);
+#else
m_end = QDateTime(QDate(yearTo + 1, 1, 1)).addSecs(-1);
+#endif
if (monthTo > 0) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_end = addMonth(yearTo, monthTo).startOfDay().addSecs(-1);
+#else
m_end = QDateTime(addMonth(yearTo, monthTo)).addSecs(-1);
+#endif
if (dayTo > 0) {
if (dayFrom == dayTo && monthFrom == monthTo && yearFrom == yearTo)
m_end = m_start;
else
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ m_end = QDate(yearTo, monthTo, dayTo).addDays(1).startOfDay().addSecs(-1);
+#else
m_end = QDateTime(QDate(yearTo, monthTo, dayTo).addDays(1)).addSecs(-1);
+#endif
}
}
// It should not be possible here for m_end < m_start.
Q_ASSERT(m_start <= m_end);
}
}
QDate ImageDate::parseDate(const QString &date, bool startDate)
{
int year = 0;
int month = 0;
int day = 0;
QRegExp regexp(formatRegexp(), Qt::CaseInsensitive);
if (regexp.exactMatch(date)) {
QString dayStr = regexp.cap(2);
QString monthStr = regexp.cap(5).toLower();
QString yearStr = regexp.cap(7);
if (dayStr.length() != 0)
day = dayStr.toInt();
if (yearStr.length() != 0) {
year = yearStr.toInt();
if (year < 50)
year += 2000;
if (year < 100)
year += 1900;
}
if (monthStr.length() != 0) {
int index = monthNames().indexOf(monthStr);
if (index != -1)
month = (index % 12) + 1;
else
month = monthStr.toInt();
}
if (year == 0)
year = QDate::currentDate().year();
if (month == 0) {
if (startDate) {
month = 1;
day = 1;
} else {
month = 12;
day = 31;
}
} else if (day == 0) {
if (startDate)
day = 1;
else
day = QDate(year, month, 1).daysInMonth();
}
return QDate(year, month, day);
} else
return QDate();
}
bool ImageDate::hasValidTime() const
{
return m_start == m_end;
}
ImageDate::ImageDate(const QDate &start, QDate end, const QTime &time)
{
if (!end.isValid())
end = start;
if (start == end && time.isValid()) {
m_start = QDateTime(start, time);
m_end = m_start;
} else {
if (start > end) {
m_end = QDateTime(start, QTime(0, 0, 0));
m_start = QDateTime(end, QTime(23, 59, 59));
} else {
m_start = QDateTime(start, QTime(0, 0, 0));
m_end = QDateTime(end, QTime(23, 59, 59));
}
}
}
ImageDate::MatchType ImageDate::isIncludedIn(const ImageDate &searchRange) const
{
if (searchRange.start() <= start() && searchRange.end() >= end())
return ExactMatch;
if (searchRange.start() <= end() && searchRange.end() >= start()) {
return RangeMatch;
}
return DontMatch;
}
bool ImageDate::includes(const QDateTime &date) const
{
return ImageDate(date).isIncludedIn(*this) == ExactMatch;
}
void ImageDate::extendTo(const ImageDate &other)
{
if (other.isNull())
return;
if (isNull()) {
m_start = other.m_start;
m_end = other.m_end;
} else {
if (other.m_start < m_start)
m_start = other.m_start;
if (other.m_end > m_end)
m_end = other.m_end;
}
}
QStringList DB::ImageDate::monthNames()
{
static QStringList res;
if (res.isEmpty()) {
for (int i = 1; i <= 12; ++i) {
res << QLocale().standaloneMonthName(i, QLocale::ShortFormat);
}
for (int i = 1; i <= 12; ++i) {
res << QLocale().standaloneMonthName(i, QLocale::LongFormat);
}
res << i18nc("Abbreviated month name", "jan") << i18nc("Abbreviated month name", "feb")
<< i18nc("Abbreviated month name", "mar") << i18nc("Abbreviated month name", "apr")
<< i18nc("Abbreviated month name", "may") << i18nc("Abbreviated month name", "jun")
<< i18nc("Abbreviated month name", "jul") << i18nc("Abbreviated month name", "aug")
<< i18nc("Abbreviated month name", "sep") << i18nc("Abbreviated month name", "oct")
<< i18nc("Abbreviated month name", "nov") << i18nc("Abbreviated month name", "dec");
res << QString::fromLatin1("jan") << QString::fromLatin1("feb") << QString::fromLatin1("mar") << QString::fromLatin1("apr")
<< QString::fromLatin1("may") << QString::fromLatin1("jun") << QString::fromLatin1("jul") << QString::fromLatin1("aug")
<< QString::fromLatin1("sep") << QString::fromLatin1("oct") << QString::fromLatin1("nov") << QString::fromLatin1("dec");
for (int i = 1; i <= 12; ++i) {
res << QLocale().monthName(i, QLocale::ShortFormat);
}
for (int i = 1; i <= 12; ++i) {
res << QLocale().monthName(i, QLocale::LongFormat);
}
for (QStringList::iterator it = res.begin(); it != res.end(); ++it)
*it = it->toLower();
}
return res;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/XMLDB/FileReader.cpp b/XMLDB/FileReader.cpp
index c853ed71..5d74ff7a 100644
--- a/XMLDB/FileReader.cpp
+++ b/XMLDB/FileReader.cpp
@@ -1,597 +1,601 @@
/* Copyright (C) 2003-2020 The KPhotoAlbum Development Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// Local includes
#include "FileReader.h"
#include "CompressFileInfo.h"
#include "Database.h"
#include "Logging.h"
#include "XMLCategory.h"
#include
#include
// KDE includes
#include
// Qt includes
#include
#include
#include
#include
#include
#include
#include
void XMLDB::FileReader::read(const QString &configFile)
{
static QString versionString = QString::fromUtf8("version");
static QString compressedString = QString::fromUtf8("compressed");
ReaderPtr reader = readConfigFile(configFile);
ElementInfo info = reader->readNextStartOrStopElement(QString::fromUtf8("KPhotoAlbum"));
if (!info.isStartToken)
reader->complainStartElementExpected(QString::fromUtf8("KPhotoAlbum"));
m_fileVersion = reader->attribute(versionString, QString::fromLatin1("1")).toInt();
if (m_fileVersion > Database::fileVersion()) {
DB::UserFeedback ret = m_db->uiDelegate().warningContinueCancel(
QString::fromLatin1("index.xml version %1 is newer than %2!").arg(m_fileVersion).arg(Database::fileVersion()), i18n("The database file (index.xml) is from a newer version of KPhotoAlbum!
"
"Chances are you will be able to read this file, but when writing it back, "
"information saved in the newer version will be lost
"),
i18n("index.xml version mismatch"), QString::fromLatin1("checkDatabaseFileVersion"));
if (ret != DB::UserFeedback::Confirm)
exit(-1);
}
setUseCompressedFileFormat(reader->attribute(compressedString).toInt());
m_db->m_members.setLoading(true);
loadCategories(reader);
loadImages(reader);
loadBlockList(reader);
loadMemberGroups(reader);
//loadSettings(reader);
repairDB();
m_db->m_members.setLoading(false);
checkIfImagesAreSorted();
checkIfAllImagesHaveSizeAttributes();
}
void XMLDB::FileReader::createSpecialCategories()
{
// Setup the "Folder" category
m_folderCategory = new XMLCategory(i18n("Folder"), QString::fromLatin1("folder"),
DB::Category::TreeView, 32, false);
m_folderCategory->setType(DB::Category::FolderCategory);
// The folder category is not stored in the index.xml file,
// but older versions of KPhotoAlbum stored a stub entry, which we need to remove first:
if (m_db->m_categoryCollection.categoryForName(m_folderCategory->name()))
m_db->m_categoryCollection.removeCategory(m_folderCategory->name());
m_db->m_categoryCollection.addCategory(m_folderCategory);
dynamic_cast(m_folderCategory.data())->setShouldSave(false);
// Setup the "Tokens" category
DB::CategoryPtr tokenCat;
if (m_fileVersion >= 7) {
tokenCat = m_db->m_categoryCollection.categoryForSpecial(DB::Category::TokensCategory);
} else {
// Before version 7, the "Tokens" category name wasn't stored to the settings. So ...
// look for a literal "Tokens" category ...
tokenCat = m_db->m_categoryCollection.categoryForName(QString::fromUtf8("Tokens"));
if (!tokenCat) {
// ... and a translated "Tokens" category if we don't have the literal one.
tokenCat = m_db->m_categoryCollection.categoryForName(i18n("Tokens"));
}
if (tokenCat) {
// in this case we need to give the tokens category its special meaning:
m_db->m_categoryCollection.removeCategory(tokenCat->name());
tokenCat->setType(DB::Category::TokensCategory);
m_db->m_categoryCollection.addCategory(tokenCat);
}
}
if (!tokenCat) {
// Create a new "Tokens" category
tokenCat = new XMLCategory(i18n("Tokens"), QString::fromUtf8("tag"),
DB::Category::TreeView, 32, true);
tokenCat->setType(DB::Category::TokensCategory);
m_db->m_categoryCollection.addCategory(tokenCat);
}
// KPhotoAlbum 2.2 did not write the tokens to the category section,
// so unless we do this small trick they will not show up when importing.
for (char ch = 'A'; ch <= 'Z'; ++ch) {
tokenCat->addItem(QString::fromUtf8("%1").arg(QChar::fromLatin1(ch)));
}
// Setup the "Media Type" category
DB::CategoryPtr mediaCat;
mediaCat = new XMLCategory(i18n("Media Type"), QString::fromLatin1("view-categories"),
DB::Category::TreeView, 32, false);
mediaCat->addItem(i18n("Image"));
mediaCat->addItem(i18n("Video"));
mediaCat->setType(DB::Category::MediaTypeCategory);
dynamic_cast(mediaCat.data())->setShouldSave(false);
// The media type is not stored in the media category,
// but older versions of KPhotoAlbum stored a stub entry, which we need to remove first:
if (m_db->m_categoryCollection.categoryForName(mediaCat->name()))
m_db->m_categoryCollection.removeCategory(mediaCat->name());
m_db->m_categoryCollection.addCategory(mediaCat);
}
void XMLDB::FileReader::loadCategories(ReaderPtr reader)
{
static QString nameString = QString::fromUtf8("name");
static QString iconString = QString::fromUtf8("icon");
static QString viewTypeString = QString::fromUtf8("viewtype");
static QString showString = QString::fromUtf8("show");
static QString thumbnailSizeString = QString::fromUtf8("thumbnailsize");
static QString positionableString = QString::fromUtf8("positionable");
static QString metaString = QString::fromUtf8("meta");
static QString tokensString = QString::fromUtf8("tokens");
static QString valueString = QString::fromUtf8("value");
static QString idString = QString::fromUtf8("id");
static QString birthDateString = QString::fromUtf8("birthDate");
static QString categoriesString = QString::fromUtf8("Categories");
static QString categoryString = QString::fromUtf8("Category");
ElementInfo info = reader->readNextStartOrStopElement(categoriesString);
if (!info.isStartToken)
reader->complainStartElementExpected(categoriesString);
while (reader->readNextStartOrStopElement(categoryString).isStartToken) {
const QString categoryName = unescape(reader->attribute(nameString));
if (!categoryName.isNull()) {
// Read Category info
QString icon = reader->attribute(iconString);
DB::Category::ViewType type = (DB::Category::ViewType)reader->attribute(viewTypeString, QString::fromLatin1("0")).toInt();
int thumbnailSize = reader->attribute(thumbnailSizeString, QString::fromLatin1("32")).toInt();
bool show = (bool)reader->attribute(showString, QString::fromLatin1("1")).toInt();
bool positionable = (bool)reader->attribute(positionableString, QString::fromLatin1("0")).toInt();
bool tokensCat = reader->attribute(metaString) == tokensString;
DB::CategoryPtr cat = m_db->m_categoryCollection.categoryForName(categoryName);
bool repairMode = false;
if (cat) {
DB::UserFeedback choice = m_db->uiDelegate().warningContinueCancel(
QString::fromUtf8("Line %1, column %2: duplicate category '%3'")
.arg(reader->lineNumber())
.arg(reader->columnNumber())
.arg(categoryName),
i18n("Line %1, column %2: duplicate category '%3'
"
"Choose continue to ignore the duplicate category and try an automatic repair, "
"or choose cancel to quit.
",
reader->lineNumber(),
reader->columnNumber(),
categoryName),
i18n("Error in database file"));
if (choice == DB::UserFeedback::Confirm)
repairMode = true;
else
exit(-1);
} else {
cat = new XMLCategory(categoryName, icon, type, thumbnailSize, show, positionable);
if (tokensCat)
cat->setType(DB::Category::TokensCategory);
m_db->m_categoryCollection.addCategory(cat);
}
// Read values
QStringList items;
while (reader->readNextStartOrStopElement(valueString).isStartToken) {
QString value = reader->attribute(valueString);
if (reader->hasAttribute(idString)) {
int id = reader->attribute(idString).toInt();
if (id != 0) {
static_cast(cat.data())->setIdMapping(value, id);
} else {
if (useCompressedFileFormat()) {
qCWarning(XMLDBLog) << "Tag" << categoryName << "/" << value << "has id=0!";
m_repairTagsWithNullIds = true;
static_cast(cat.data())->setIdMapping(value, id, XMLCategory::IdMapping::UnsafeMapping);
}
// else just don't set the id mapping so that a new id gets assigned
}
}
if (reader->hasAttribute(birthDateString))
cat->setBirthDate(value, QDate::fromString(reader->attribute(birthDateString), Qt::ISODate));
items.append(value);
reader->readEndElement();
}
if (repairMode) {
// merge with duplicate category
qCInfo(XMLDBLog) << "Repairing category " << categoryName << ": merging items "
<< cat->items() << " with " << items;
items.append(cat->items());
items.removeDuplicates();
}
cat->setItems(items);
}
}
createSpecialCategories();
if (m_fileVersion < 7) {
m_db->uiDelegate().information(
QString::fromLatin1("Standard category names are no longer used since index.xml "
"version 7. Standard categories will be left untranslated from now on."),
i18nc("Leave \"Folder\" and \"Media Type\" untranslated below, those will show up with "
"these exact names. Thanks :-)",
"This version of KPhotoAlbum does not translate \"standard\" categories "
"any more.
"
"This may mean that – if you use a locale other than English – some of your "
"categories are now displayed in English.
"
"You can manually rename your categories any time and then save your database."
"
"
"In some cases, you may get two additional empty categories, \"Folder\" and "
"\"Media Type\". You can delete those.
"),
i18n("Changed standard category names"));
}
}
void XMLDB::FileReader::loadImages(ReaderPtr reader)
{
static QString fileString = QString::fromUtf8("file");
static QString imagesString = QString::fromUtf8("images");
static QString imageString = QString::fromUtf8("image");
ElementInfo info = reader->readNextStartOrStopElement(imagesString);
if (!info.isStartToken)
reader->complainStartElementExpected(imagesString);
while (reader->readNextStartOrStopElement(imageString).isStartToken) {
const QString fileNameStr = reader->attribute(fileString);
if (fileNameStr.isNull()) {
qCWarning(XMLDBLog, "Element did not contain a file attribute");
return;
}
const DB::FileName dbFileName = DB::FileName::fromRelativePath(fileNameStr);
DB::ImageInfoPtr info = load(dbFileName, reader);
if (m_db->md5Map()->containsFile(dbFileName)) {
if (m_db->md5Map()->contains(info->MD5Sum())) {
qCWarning(XMLDBLog) << "Merging duplicate entry for file" << dbFileName.relative();
DB::ImageInfoPtr existingInfo = m_db->info(dbFileName);
existingInfo->merge(*info);
} else {
m_db->uiDelegate().error(
QString::fromUtf8("Conflicting information for file '%1': duplicate entry with different MD5 sum! Bailing out...")
.arg(dbFileName.relative()),
i18n("Line %1, column %2: duplicate entry for file '%3' with different MD5 sum.
"
"Manual repair required!
",
reader->lineNumber(),
reader->columnNumber(),
dbFileName.relative()),
i18n("Error in database file"));
exit(-1);
}
} else {
m_db->m_images.append(info);
m_db->m_md5map.insert(info->MD5Sum(), dbFileName);
}
}
}
void XMLDB::FileReader::loadBlockList(ReaderPtr reader)
{
static QString fileString = QString::fromUtf8("file");
static QString blockListString = QString::fromUtf8("blocklist");
static QString blockString = QString::fromUtf8("block");
ElementInfo info = reader->peekNext();
if (info.isStartToken && info.tokenName == blockListString) {
reader->readNextStartOrStopElement(blockListString);
while (reader->readNextStartOrStopElement(blockString).isStartToken) {
QString fileName = reader->attribute(fileString);
if (!fileName.isEmpty())
m_db->m_blockList.insert(DB::FileName::fromRelativePath(fileName));
reader->readEndElement();
}
}
}
void XMLDB::FileReader::loadMemberGroups(ReaderPtr reader)
{
static QString categoryString = QString::fromUtf8("category");
static QString groupNameString = QString::fromUtf8("group-name");
static QString memberString = QString::fromUtf8("member");
static QString membersString = QString::fromUtf8("members");
static QString memberGroupsString = QString::fromUtf8("member-groups");
ElementInfo info = reader->peekNext();
if (info.isStartToken && info.tokenName == memberGroupsString) {
reader->readNextStartOrStopElement(memberGroupsString);
while (reader->readNextStartOrStopElement(memberString).isStartToken) {
QString category = reader->attribute(categoryString);
QString group = reader->attribute(groupNameString);
if (reader->hasAttribute(memberString)) {
QString member = reader->attribute(memberString);
m_db->m_members.addMemberToGroup(category, group, member);
} else {
const QStringList members = reader->attribute(membersString).split(QString::fromLatin1(","), QString::SkipEmptyParts);
for (const QString &memberItem : members) {
DB::CategoryPtr catPtr = m_db->m_categoryCollection.categoryForName(category);
if (!catPtr) { // category was not declared in "Categories"
qCWarning(XMLDBLog) << "File corruption in index.xml. Inserting missing category: " << category;
catPtr = new XMLCategory(category, QString::fromUtf8("dialog-warning"), DB::Category::TreeView, 32, false);
m_db->m_categoryCollection.addCategory(catPtr);
}
XMLCategory *cat = static_cast(catPtr.data());
QString member = cat->nameForId(memberItem.toInt());
if (member.isNull())
continue;
m_db->m_members.addMemberToGroup(category, group, member);
}
if (members.size() == 0) {
// Groups are stored even if they are empty, so we also have to read them.
// With no members, the above for loop will not be executed.
m_db->m_members.addGroup(category, group);
}
}
reader->readEndElement();
}
}
}
/*
void XMLDB::FileReader::loadSettings(ReaderPtr reader)
{
static QString settingsString = QString::fromUtf8("settings");
static QString settingString = QString::fromUtf8("setting");
static QString keyString = QString::fromUtf8("key");
static QString valueString = QString::fromUtf8("value");
ElementInfo info = reader->peekNext();
if (info.isStartToken && info.tokenName == settingsString) {
reader->readNextStartOrStopElement(settingString);
while(reader->readNextStartOrStopElement(settingString).isStartToken) {
if (reader->hasAttribute(keyString) && reader->hasAttribute(valueString)) {
m_db->m_settings.insert(unescape(reader->attribute(keyString)),
unescape(reader->attribute(valueString)));
} else {
qWarning() << "File corruption in index.xml. Setting either lacking a key or a "
<< "value attribute. Ignoring this entry.";
}
reader->readEndElement();
}
}
}
*/
void XMLDB::FileReader::checkIfImagesAreSorted()
{
if (m_db->uiDelegate().isDialogDisabled(QString::fromLatin1("checkWhetherImagesAreSorted")))
return;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QDateTime last = QDate(1900, 1, 1).startOfDay();
+#else
QDateTime last(QDate(1900, 1, 1));
+#endif
bool wrongOrder = false;
for (DB::ImageInfoListIterator it = m_db->m_images.begin(); !wrongOrder && it != m_db->m_images.end(); ++it) {
if (last > (*it)->date().start() && (*it)->date().start().isValid())
wrongOrder = true;
last = (*it)->date().start();
}
if (wrongOrder) {
m_db->uiDelegate().information(
QString::fromLatin1("Database is not sorted by date."), i18n("Your images/videos are not sorted, which means that navigating using the date bar "
"will only work suboptimally.
"
"In the Maintenance menu, you can find Display Images with Incomplete Dates "
"which you can use to find the images that are missing date information.
"
"You can then select the images that you have reason to believe have a correct date "
"in either their Exif data or on the file, and execute Maintenance->Read Exif Info "
"to reread the information.
"
"Finally, once all images have their dates set, you can execute "
"Maintenance->Sort All by Date & Time to sort them in the database.
"),
i18n("Images/Videos Are Not Sorted"), QString::fromLatin1("checkWhetherImagesAreSorted"));
}
}
void XMLDB::FileReader::checkIfAllImagesHaveSizeAttributes()
{
if (m_db->uiDelegate().isDialogDisabled(QString::fromLatin1("checkWhetherAllImagesIncludesSize")))
return;
if (m_db->s_anyImageWithEmptySize) {
m_db->uiDelegate().information(
QString::fromLatin1("Found image(s) without size information."), i18n("Not all the images in the database have information about image sizes; this is needed to "
"get the best result in the thumbnail view. To fix this, simply go to the Maintenance menu, "
"and first choose Remove All Thumbnails, and after that choose Build Thumbnails.
"
"Not doing so will result in extra space around images in the thumbnail view - that is all - so "
"there is no urgency in doing it.
"),
i18n("Not All Images Have Size Information"), QString::fromLatin1("checkWhetherAllImagesIncludesSize"));
}
}
void XMLDB::FileReader::repairDB()
{
if (m_repairTagsWithNullIds) {
// the m_repairTagsWithNullIds is set in loadCategories()
// -> care is taken so that multiple tags with id=0 all end up in the IdMap
// afterwards, loadImages() applies fixes to the affected images
// -> this happens in XMLDB::Database::possibleLoadCompressedCategories()
// i.e. the zero ids still require cleanup:
qCInfo(XMLDBLog) << "Database contained tags with id=0 (possibly related to bug #415415). Assigning new ids for affected categories...";
QString message = i18nc("repair merged tags",
"Inconsistencies were found and repaired in your database. "
"Some categories now contain tags that were merged during the repair.
"
"The following tags require manual inspection:"
"
");
QString logSummary = QString::fromLatin1("List of tags where manual inspection is required:\n");
bool manualRepairNeeded = false;
for (auto category : m_db->categoryCollection()->categories()) {
XMLCategory *xmlCategory = static_cast(category.data());
QStringList tags = xmlCategory->namesForId(0);
if (tags.size() > 1) {
manualRepairNeeded = true;
message += i18nc("repair merged tags", "- %1:
", category->name());
for (auto tagName : tags) {
message += i18nc("repair merged tags", "%1
", tagName);
logSummary += QString::fromLatin1("%1/%2\n").arg(category->name(), tagName);
}
message += i18nc("repair merged tags", " ");
}
xmlCategory->clearNullIds();
}
message += i18nc("repair merged tags",
"
"
"All affected images have also been marked with a tag "
"KPhotoAlbum - manual repair needed.
");
if (manualRepairNeeded) {
m_db->uiDelegate().information(logSummary, message, i18n("Database repair required"));
}
}
}
DB::ImageInfoPtr XMLDB::FileReader::load(const DB::FileName &fileName, ReaderPtr reader)
{
DB::ImageInfoPtr info = XMLDB::Database::createImageInfo(fileName, reader, m_db);
m_nextStackId = qMax(m_nextStackId, info->stackId() + 1);
info->createFolderCategoryItem(m_folderCategory, m_db->m_members);
return info;
}
XMLDB::ReaderPtr XMLDB::FileReader::readConfigFile(const QString &configFile)
{
ReaderPtr reader = ReaderPtr(new XmlReader(m_db->uiDelegate(), configFile));
QFile file(configFile);
if (!file.exists()) {
// Load a default setup
QFile file(QStandardPaths::locate(QStandardPaths::DataLocation, QString::fromLatin1("default-setup")));
if (!file.open(QIODevice::ReadOnly)) {
m_db->uiDelegate().information(
QString::fromLatin1("default-setup not found in standard paths."), i18n("KPhotoAlbum was unable to load a default setup, which indicates an installation error
"
"If you have installed KPhotoAlbum yourself, then you must remember to set the environment variable "
"KDEDIRS, to point to the topmost installation directory.
"
"If you for example ran cmake with -DCMAKE_INSTALL_PREFIX=/usr/local/kde, then you must use the following "
"environment variable setup (this example is for Bash and compatible shells):
"
"export KDEDIRS=/usr/local/kde
"
"In case you already have KDEDIRS set, simply append the string as if you where setting the PATH "
"environment variable
"),
i18n("No default setup file found"));
} else {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
QString str = stream.readAll();
// Replace the default setup's category and tag names with localized ones
str = str.replace(QString::fromUtf8("People"), i18n("People"));
str = str.replace(QString::fromUtf8("Places"), i18n("Places"));
str = str.replace(QString::fromUtf8("Events"), i18n("Events"));
str = str.replace(QString::fromUtf8("untagged"), i18n("untagged"));
str = str.replace(QRegExp(QString::fromLatin1("imageDirectory=\"[^\"]*\"")), QString::fromLatin1(""));
str = str.replace(QRegExp(QString::fromLatin1("htmlBaseDir=\"[^\"]*\"")), QString::fromLatin1(""));
str = str.replace(QRegExp(QString::fromLatin1("htmlBaseURL=\"[^\"]*\"")), QString::fromLatin1(""));
reader->addData(str);
}
} else {
if (!file.open(QIODevice::ReadOnly)) {
m_db->uiDelegate().error(
QString::fromLatin1("Unable to open '%1' for reading").arg(configFile), i18n("Unable to open '%1' for reading", configFile), i18n("Error Running Demo"));
exit(-1);
}
reader->addData(file.readAll());
#if 0
QString errMsg;
int errLine;
int errCol;
if ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol )) {
file.close();
// If parsing index.xml fails let's see if we could use a backup instead
Utilities::checkForBackupFile( configFile, i18n( "line %1 column %2 in file %3: %4", errLine , errCol , configFile , errMsg ) );
if ( !file.open( QIODevice::ReadOnly ) || ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol ) ) ) {
KMessageBox::error( messageParent(), i18n( "Failed to recover the backup: %1", errMsg ) );
exit(-1);
}
}
#endif
}
// Now read the content of the file.
#if 0
QDomElement top = doc.documentElement();
if ( top.isNull() ) {
KMessageBox::error( messageParent(), i18n("Error in file %1: No elements found", configFile ) );
exit(-1);
}
if ( top.tagName().toLower() != QString::fromLatin1( "kphotoalbum" ) &&
top.tagName().toLower() != QString::fromLatin1( "kimdaba" ) ) { // KimDaBa compatibility
KMessageBox::error( messageParent(), i18n("Error in file %1: expected 'KPhotoAlbum' as top element but found '%2'", configFile , top.tagName() ) );
exit(-1);
}
#endif
file.close();
return reader;
}
/**
* @brief Unescape a string used as an XML attribute name.
*
* @see XMLDB::FileWriter::escape
*
* @param str the string to be unescaped
* @return the unescaped string
*/
QString XMLDB::FileReader::unescape(const QString &str)
{
static bool hashUsesCompressedFormat = useCompressedFileFormat();
static QHash s_cache;
if (hashUsesCompressedFormat != useCompressedFileFormat())
s_cache.clear();
if (s_cache.contains(str))
return s_cache[str];
QString tmp(str);
// Matches encoded characters in attribute names
QRegExp rx(QString::fromLatin1("(_.)([0-9A-F]{2})"));
int pos = 0;
// Unencoding special characters if compressed XML is selected
if (useCompressedFileFormat()) {
while ((pos = rx.indexIn(tmp, pos)) != -1) {
QString before = rx.cap(1) + rx.cap(2);
QString after = QString::fromLatin1(QByteArray::fromHex(rx.cap(2).toLocal8Bit()));
tmp.replace(pos, before.length(), after);
pos += after.length();
}
} else
tmp.replace(QString::fromLatin1("_"), QString::fromLatin1(" "));
s_cache.insert(str, tmp);
return tmp;
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/XMLDB/XMLImageDateCollection.cpp b/XMLDB/XMLImageDateCollection.cpp
index 796e6453..5fa052da 100644
--- a/XMLDB/XMLImageDateCollection.cpp
+++ b/XMLDB/XMLImageDateCollection.cpp
@@ -1,141 +1,153 @@
/* Copyright (C) 2003-2020 Jesper K. Pedersen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "XMLImageDateCollection.h"
#include
#include
void XMLDB::XMLImageDateCollection::add(const DB::ImageDate &date)
{
m_startIndex.insertMulti(date.start(), date);
}
void XMLDB::XMLImageDateCollection::buildIndex()
{
StartIndexMap::ConstIterator startSearch = m_startIndex.constBegin();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QDateTime biggestEnd = QDate(1900, 1, 1).startOfDay();
+#else
QDateTime biggestEnd = QDateTime(QDate(1900, 1, 1));
+#endif
for (StartIndexMap::ConstIterator it = m_startIndex.constBegin();
it != m_startIndex.constEnd();
++it) {
// We want a monotonic mapping end-date -> smallest-in-start-index.
// Since we go through the start index sorted, lowest first, we just
// have to keep the last pointer as long as we find smaller end-dates.
// This should be rare as it only occurs if there are images that
// actually represent a range not just a point in time.
if (it.value().end() >= biggestEnd) {
biggestEnd = it.value().end();
startSearch = it;
}
m_endIndex.insert(it.value().end(), startSearch);
}
}
/**
Previously, counting the elements was done by going through all elements
and count the matches for a particular range, this unfortunately had
O(n) complexity multiplied by m ranges we would get O(mn).
Henner Zeller rewrote it to its current state. The main idea now is to
have all dates sorted so that it is possible to only look at the
requested range. Since it is not points in time, we can't have just a
simple sorted list. So we have two sorted maps, the m_startIndex and
m_endIndex. m_startIndex is sorted by the start time of all ImageDates
(which are in fact ranges)
If we would just look for Images that start _after_ the query-range, we
would miscount, because there might be Image ranges starting before the
query time but whose end time reaches into the query range this is what
the m_endIndex is for: it is sortd by end-date; here we look for
everything that is >= our query start. its value() part is basically a
pointer to the position in the m_startIndex where we actually have to
start looking.
The rest is simple: we determine the interesting start in
m_startIndex using the m_endIndex and iterate through it until the
elements in that sorted list have a start time that is larger than the
query-end-range .. there will no more elements coming.
The above uses the fact that a QMap::constIterator iterates the map in
sorted order.
**/
DB::ImageCount XMLDB::XMLImageDateCollection::count(const DB::ImageDate &range)
{
if (m_cache.contains(range))
return m_cache[range];
int exact = 0, rangeMatch = 0;
// We start searching in ranges that overlap our start search range, i.e.
// where the end-date is higher than our search start.
EndIndexMap::Iterator endSearch = m_endIndex.lowerBound(range.start());
if (endSearch != m_endIndex.end()) {
for (StartIndexMap::ConstIterator it = endSearch.value();
it != m_startIndex.constEnd() && it.key() < range.end();
++it) {
DB::ImageDate::MatchType tp = it.value().isIncludedIn(range);
switch (tp) {
case DB::ImageDate::ExactMatch:
exact++;
break;
case DB::ImageDate::RangeMatch:
rangeMatch++;
break;
case DB::ImageDate::DontMatch:
break;
}
}
}
DB::ImageCount res(exact, rangeMatch);
m_cache.insert(range, res); // TODO(hzeller) this might go now
return res;
}
QDateTime XMLDB::XMLImageDateCollection::lowerLimit() const
{
if (!m_startIndex.empty()) {
// skip null dates:
for (StartIndexMap::ConstIterator it = m_startIndex.constBegin(); it != m_startIndex.constEnd(); ++it) {
if (it.key().isValid())
return it.key();
}
}
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ return QDate(1900, 1, 1).startOfDay();
+#else
return QDateTime(QDate(1900, 1, 1));
+#endif
}
QDateTime XMLDB::XMLImageDateCollection::upperLimit() const
{
if (!m_endIndex.empty()) {
EndIndexMap::ConstIterator highest = m_endIndex.constEnd();
--highest;
return highest.key();
}
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ return QDate(2100, 1, 1).startOfDay();
+#else
return QDateTime(QDate(2100, 1, 1));
+#endif
}
XMLDB::XMLImageDateCollection::XMLImageDateCollection(const DB::ImageInfoList &list)
{
for (const auto &image : list) {
add(image->date());
}
buildIndex();
}
// vi:expandtab:tabstop=4 shiftwidth=4: