diff --git a/AnnotationDialog/AreaTagSelectDialog.cpp b/AnnotationDialog/AreaTagSelectDialog.cpp
index c91aa9df..01dbc73e 100644
--- a/AnnotationDialog/AreaTagSelectDialog.cpp
+++ b/AnnotationDialog/AreaTagSelectDialog.cpp
@@ -1,126 +1,126 @@
-/* Copyright (C) 2016 Tobias Leupold
+/* Copyright (C) 2016-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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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. If not, see .
*/
// Local includes
#include "AreaTagSelectDialog.h"
#include "CompletableLineEdit.h"
#include "Dialog.h"
#include "ListSelect.h"
#include "ResizableFrame.h"
// KDE includes
#include
// Qt includes
#include
#include
#include
#include
#include
AnnotationDialog::AreaTagSelectDialog::AreaTagSelectDialog(AnnotationDialog::ResizableFrame *area,
ListSelect *ls,
QPixmap areaImage,
Dialog *dialog)
: QDialog(area)
, m_area(area)
, m_listSelect(ls)
, m_dialog(dialog)
, m_usedTags(dialog->positionedTags(ls->category()))
{
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
setModal(true);
QGridLayout *mainLayout = new QGridLayout(this);
m_areaImageLabel = new QLabel();
m_areaImageLabel->setAlignment(Qt::AlignTop);
m_areaImageLabel->setPixmap(areaImage);
// span 2 rows:
mainLayout->addWidget(m_areaImageLabel, 0, 0, 2, 1);
CompletableLineEdit *tagSelect = new CompletableLineEdit(ls, this);
ls->connectLineEdit(tagSelect);
tagSelect->setAlignment(Qt::AlignTop);
mainLayout->addWidget(tagSelect, 0, 1);
m_messageLabel = new QLabel();
mainLayout->addWidget(m_messageLabel, 1, 1);
m_messageLabel->hide();
QMenu *tagMenu = new QMenu();
m_area->addTagActions(tagMenu);
mainLayout->addWidget(tagMenu, 2, 0, 1, 2);
connect(tagMenu, &QMenu::triggered, this, &QDialog::accept);
connect(tagSelect, &KLineEdit::returnPressed, this, &AreaTagSelectDialog::slotSetTag);
connect(tagSelect, &QLineEdit::textChanged, this, &AreaTagSelectDialog::slotValidateTag);
connect(this, &QDialog::finished, this, &AreaTagSelectDialog::slotFinished);
}
void AnnotationDialog::AreaTagSelectDialog::slotSetTag(const QString &tag)
{
QString enteredText = tag.trimmed();
if (m_dialog->positionableTagAvailable(m_listSelect->category(), enteredText)) {
const auto currentTagData = m_area->tagData();
// was there already a tag associated?
if (!currentTagData.first.isEmpty()) {
// Deselect the tag
m_dialog->listSelectForCategory(currentTagData.first)->deselectTag(currentTagData.second);
m_area->removeTagData();
}
m_area->setTagData(m_listSelect->category(), enteredText);
this->accept();
}
}
void AnnotationDialog::AreaTagSelectDialog::slotValidateTag(const QString &tag)
{
if (m_usedTags.contains(tag.trimmed())) {
m_messageLabel->show();
m_messageLabel->setText(i18n("Tag is already used for another area"));
adjustSize();
} else {
m_messageLabel->clear();
adjustSize();
}
}
void AnnotationDialog::AreaTagSelectDialog::slotFinished()
{
// remove filter from listSelect
m_listSelect->showOnlyItemsMatching(QString());
}
void AnnotationDialog::AreaTagSelectDialog::paintEvent(QPaintEvent *)
{
- QColor backgroundColor = Qt::white;
+ QColor backgroundColor = palette().base().color();
backgroundColor.setAlpha(160);
QPainter painter(this);
painter.fillRect(rect(), backgroundColor);
}
void AnnotationDialog::AreaTagSelectDialog::moveToArea(QPoint areaTopLeft)
{
move(areaTopLeft - (m_areaImageLabel->mapToGlobal(QPoint(0, 0)) - mapToGlobal(QPoint(0, 0))));
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/BackgroundTaskManager/JobModel.cpp b/BackgroundTaskManager/JobModel.cpp
index c4ca9c31..4c15039a 100644
--- a/BackgroundTaskManager/JobModel.cpp
+++ b/BackgroundTaskManager/JobModel.cpp
@@ -1,185 +1,188 @@
-/* Copyright (C) 2012-2019 The KPhotoAlbum Development Team
+/* Copyright (C) 2012-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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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. If not, see .
*/
#include "JobModel.h"
#include "CompletedJobInfo.h"
#include "JobInfo.h"
#include "JobManager.h"
#include
#include
+#include
+#include
#include
#include
#include
#include
namespace BackgroundTaskManager
{
JobModel::JobModel(QObject *parent)
: QAbstractTableModel(parent)
, blinkStateOn(true)
{
connect(JobManager::instance(), SIGNAL(jobStarted(JobInterface *)), this, SLOT(jobStarted(JobInterface *)));
connect(JobManager::instance(), SIGNAL(jobEnded(JobInterface *)), this, SLOT(jobEnded(JobInterface *)));
// Make the current task blink
QTimer *timer = new QTimer(this);
timer->start(500);
connect(timer, &QTimer::timeout, this, &JobModel::heartbeat);
}
JobModel::~JobModel()
{
qDeleteAll(m_previousJobs);
}
int JobModel::rowCount(const QModelIndex &index) const
{
if (index.isValid())
return 0;
else
return m_previousJobs.count() + JobManager::instance()->activeJobCount() + JobManager::instance()->futureJobCount();
}
int JobModel::columnCount(const QModelIndex &) const
{
return 4;
}
QVariant JobModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const int row = index.row();
const int col = index.column();
JobInfo *current = info(row);
if (!current)
return QVariant();
if (role == Qt::DisplayRole) {
switch (col) {
case IDCol:
return current->jobIndex();
case TitleCol:
return current->title();
case DetailsCol:
return current->details();
case ElapsedCol:
return current->elapsed();
default:
return QVariant();
}
} else if (role == Qt::DecorationRole && col == TitleCol)
return statusImage(current->state);
else if (role == Qt::TextAlignmentRole)
return index.column() == IDCol ? Qt::AlignRight : Qt::AlignLeft;
return QVariant();
}
QVariant JobModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
return QVariant();
switch (section) {
case IDCol:
return i18nc("@title:column Background job id", "ID");
case TitleCol:
return i18nc("@title:column Background job title", "Title");
case DetailsCol:
return i18nc("@title:column Additional information on background job", "Details");
case ElapsedCol:
return i18nc("@title:column Elapsed time", "Elapsed");
default:
return QVariant();
}
}
void JobModel::reset()
{
// FIXME: this is just a stand-in replacement for a call to the deprecated
// QAbstractTableModel::reset();
// fix this by replacing the calls to reset() using:
// beginInsertRows()
// beginRemoveRows()
// beginMoveRows()
beginResetModel();
endResetModel();
}
void JobModel::jobEnded(JobInterface *job)
{
m_previousJobs.append(new CompletedJobInfo(job));
reset();
}
void JobModel::jobStarted(JobInterface *job)
{
connect(job, SIGNAL(changed()), this, SLOT(reset()));
reset();
}
void JobModel::heartbeat()
{
beginResetModel();
blinkStateOn = !blinkStateOn;
// optional improvement: emit dataChanged for running jobs only
endResetModel();
}
JobInfo *JobModel::info(int row) const
{
if (row < m_previousJobs.count())
return m_previousJobs[row];
row -= m_previousJobs.count();
if (row < JobManager::instance()->activeJobCount())
return JobManager::instance()->activeJob(row);
row -= JobManager::instance()->activeJobCount();
Q_ASSERT(row < JobManager::instance()->futureJobCount());
return JobManager::instance()->futureJob(row);
}
QPixmap JobModel::statusImage(JobInfo::State state) const
{
QColor color;
if (state == JobInfo::Running)
- color = blinkStateOn ? Qt::green : Qt::gray;
+ color = blinkStateOn ? Qt::green : qApp->palette().mid().color();
else if (state == JobInfo::Completed)
color = Qt::red;
else
color = QColor(Qt::yellow).darker();
KLed led;
led.setColor(color);
- QPalette pal = led.palette();
- pal.setColor(QPalette::Window, Qt::white);
- led.setPalette(pal);
+ QPixmap pixmap = led.grab();
+ // creating the mask by heuristic is expensive, so do it only once:
+ static QBitmap s_ledMask = pixmap.createHeuristicMask();
+ pixmap.setMask(s_ledMask);
- return led.grab();
+ return pixmap;
}
} // namespace BackgroundTaskManager
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/BackgroundTaskManager/StatusIndicator.cpp b/BackgroundTaskManager/StatusIndicator.cpp
index 0a8ce5f8..1aa21f46 100644
--- a/BackgroundTaskManager/StatusIndicator.cpp
+++ b/BackgroundTaskManager/StatusIndicator.cpp
@@ -1,106 +1,106 @@
-/* Copyright (C) 2012-2019 The KPhotoAlbum Development Team
+/* Copyright (C) 2012-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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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. If not, see .
*/
#include "StatusIndicator.h"
#include "JobManager.h"
#include "JobViewer.h"
#include
#include
#include
#include
#include
namespace BackgroundTaskManager
{
StatusIndicator::StatusIndicator(QWidget *parent)
: KLed(Qt::green, parent)
, m_timer(new QTimer(this))
, m_jobViewer(nullptr)
{
connect(m_timer, &QTimer::timeout, this, &StatusIndicator::flicker);
setCursor(Qt::PointingHandCursor);
connect(JobManager::instance(), SIGNAL(jobStarted(JobInterface *)), this, SLOT(maybeStartFlicker()));
}
bool StatusIndicator::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
showToolTip(dynamic_cast(event));
return true;
}
return KLed::event(event);
}
void StatusIndicator::mouseReleaseEvent(QMouseEvent *)
{
if (!m_jobViewer)
m_jobViewer = new JobViewer;
m_jobViewer->setVisible(!m_jobViewer->isVisible());
}
void StatusIndicator::flicker()
{
QColor newColor;
if (!JobManager::instance()->hasActiveJobs()) {
m_timer->stop();
- newColor = Qt::gray;
+ newColor = palette().mid().color();
} else if (JobManager::instance()->isPaused() && !JobManager::instance()->hasActiveJobs())
newColor = QColor(Qt::yellow).lighter();
else
- newColor = (color() == Qt::gray ? currentColor() : Qt::gray);
+ newColor = (color() == palette().mid().color() ? currentColor() : palette().mid().color());
setColor(newColor);
}
void StatusIndicator::maybeStartFlicker()
{
if (!m_timer->isActive())
m_timer->start(500);
}
QColor StatusIndicator::currentColor() const
{
return JobManager::instance()->isPaused() ? Qt::yellow : Qt::green;
}
void StatusIndicator::showToolTip(QHelpEvent *event)
{
const int activeCount = JobManager::instance()->activeJobCount();
const int pendingCount = JobManager::instance()->futureJobCount();
const QString text = i18n("Active jobs: %1
"
"Pending jobs: %2"
"
"
"Color codes:"
"- blinking green: Active background jobs
"
"- gray: No active jobs
"
"- solid yellow: Job queue is paused
"
"- blinking yellow: Job queue is paused for background jobs, but is executing a foreground job "
"(like extracting a thumbnail for a video file, which is currently shown in the thumbnail viewer)
",
activeCount, pendingCount);
QToolTip::showText(event->globalPos(), text);
}
} // namespace BackgroundTaskManager
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Browser/AbstractCategoryModel.cpp b/Browser/AbstractCategoryModel.cpp
index 1c206403..1ad94e29 100644
--- a/Browser/AbstractCategoryModel.cpp
+++ b/Browser/AbstractCategoryModel.cpp
@@ -1,177 +1,171 @@
-/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team
+/* 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.
*/
#include "AbstractCategoryModel.h"
#include "BrowserWidget.h"
#include "enums.h"
#include
#include
#include
#include
#include
Browser::AbstractCategoryModel::AbstractCategoryModel(const DB::CategoryPtr &category, const DB::ImageSearchInfo &info)
: m_category(category)
, m_info(info)
{
m_images = DB::ImageDB::instance()->classify(info, m_category->name(), DB::Image);
m_videos = DB::ImageDB::instance()->classify(info, m_category->name(), DB::Video);
}
bool Browser::AbstractCategoryModel::hasNoneEntry() const
{
int imageCount = m_images[DB::ImageDB::NONE()].count;
int videoCount = m_videos[DB::ImageDB::NONE()].count;
return (imageCount + videoCount != 0);
}
QString Browser::AbstractCategoryModel::text(const QString &name) const
{
if (name == DB::ImageDB::NONE()) {
if (m_info.categoryMatchText(m_category->name()).length() == 0)
return i18nc("As in No persons, no locations etc.", "None");
else
return i18nc("As in no other persons, or no other locations. ", "No other");
}
else {
if (m_category->type() == DB::Category::FolderCategory) {
QRegExp rx(QString::fromLatin1("(.*/)(.*)$"));
QString value = name;
value.replace(rx, QString::fromLatin1("\\2"));
return value;
} else {
return name;
}
}
}
QPixmap Browser::AbstractCategoryModel::icon(const QString &name) const
{
const int size = m_category->thumbnailSize();
- if (BrowserWidget::isResizing()) {
- QPixmap res(size, size * Settings::SettingsData::instance()->getThumbnailAspectRatio());
- res.fill(Qt::white);
- return res;
- }
if (m_category->viewType() == DB::Category::TreeView || m_category->viewType() == DB::Category::IconView) {
if (DB::ImageDB::instance()->memberMap().isGroup(m_category->name(), name)) {
- return QIcon::fromTheme(QString::fromUtf8("folder-image")).pixmap(22);
+ return QIcon::fromTheme(QString::fromUtf8("folder-image")).pixmap(size);
} else {
return m_category->icon();
}
} else {
// The category images are screenshot from the size of the viewer (Which might very well be considered a bug)
- // This is the reason for asking for the thumbnail height being 3/4 of its width.
return m_category->categoryImage(m_category->name(), name, size, size * Settings::SettingsData::instance()->getThumbnailAspectRatio());
}
}
QVariant Browser::AbstractCategoryModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const QString name = indexToName(index);
const int column = index.column();
if (role == Qt::DisplayRole) {
switch (column) {
case 0:
return text(name);
case 1:
return i18ncp("@item:intable number of images with a specific tag.", "1 image", "%1 images", m_images[name].count);
case 2:
return i18ncp("@item:intable number of videos with a specific tag.", "1 video", "%1 videos", m_videos[name].count);
case 3: {
DB::ImageDate range = m_images[name].range;
range.extendTo(m_videos[name].range);
return DB::ImageDate(range.start()).toString(false);
}
case 4: {
DB::ImageDate range = m_images[name].range;
range.extendTo(m_videos[name].range);
return DB::ImageDate(range.end()).toString(false);
}
}
}
else if (role == Qt::DecorationRole && column == 0) {
return icon(name);
}
else if (role == Qt::ToolTipRole)
return text(name);
else if (role == ItemNameRole)
return name;
else if (role == ValueRole) {
switch (column) {
case 0:
return name; // Notice we sort by **None** rather than None, which makes it show up at the top for less than searches.
case 1:
return m_images[name].count;
case 2:
return m_videos[name].count;
case 3: {
DB::ImageDate range = m_images[name].range;
range.extendTo(m_videos[name].range);
return range.start().toSecsSinceEpoch();
}
case 4: {
DB::ImageDate range = m_images[name].range;
range.extendTo(m_videos[name].range);
return range.end().toSecsSinceEpoch();
}
}
}
return QVariant();
}
Qt::ItemFlags Browser::AbstractCategoryModel::flags(const QModelIndex &index) const
{
return index.isValid() ? Qt::ItemIsSelectable | Qt::ItemIsEnabled : Qt::ItemFlags();
}
QVariant Browser::AbstractCategoryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical || role != Qt::DisplayRole)
return QVariant();
switch (section) {
case 0:
return m_category->name();
case 1:
return i18n("Images");
case 2:
return i18n("Videos");
case 3:
return i18n("Start Date");
case 4:
return i18n("End Date");
}
return QVariant();
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DateBar/DateBarWidget.cpp b/DateBar/DateBarWidget.cpp
index 0340da92..acbc9c2b 100644
--- a/DateBar/DateBarWidget.cpp
+++ b/DateBar/DateBarWidget.cpp
@@ -1,908 +1,919 @@
-/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team
+/* 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.
*/
#include "DateBarWidget.h"
#include "MouseHandler.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace
{
constexpr int BORDER_ABOVE_HISTOGRAM = 4;
constexpr int BORDER_AROUND_WIDGET = 0;
constexpr int BUTTON_WIDTH = 22;
constexpr int ARROW_LENGTH = 20;
constexpr int SCROLL_AMOUNT = 1;
constexpr int SCROLL_ACCELERATION = 10;
}
/**
* \class DateBar::DateBarWidget
* \brief This class represents the date bar at the bottom of the main window.
*
* The mouse interaction is handled by the classes which inherits \ref DateBar::MouseHandler, while the logic for
* deciding the length (in minutes, hours, days, etc) are handled by subclasses of \ref DateBar::ViewHandler.
*/
DateBar::DateBarWidget::DateBarWidget(QWidget *parent)
: QWidget(parent)
, m_currentHandler(&m_yearViewHandler)
, m_tp(YearView)
, m_currentMouseHandler(nullptr)
, m_currentUnit(0)
, m_currentDate(QDateTime::currentDateTime())
, m_includeFuzzyCounts(true)
, m_contextMenu(nullptr)
, m_showResolutionIndicator(true)
, m_doAutomaticRangeAdjustment(true)
{
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
m_barWidth = Settings::SettingsData::instance()->histogramSize().width();
m_barHeight = Settings::SettingsData::instance()->histogramSize().height();
m_rightArrow = new QToolButton(this);
m_rightArrow->setArrowType(Qt::RightArrow);
m_rightArrow->setAutoRepeat(true);
connect(m_rightArrow, &QToolButton::clicked, this, &DateBarWidget::scrollRight);
m_leftArrow = new QToolButton(this);
m_leftArrow->setArrowType(Qt::LeftArrow);
m_leftArrow->setAutoRepeat(true);
connect(m_leftArrow, &QToolButton::clicked, this, &DateBarWidget::scrollLeft);
m_zoomIn = new QToolButton(this);
m_zoomIn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
m_zoomIn->setToolTip(i18n("Zoom in"));
connect(m_zoomIn, &QToolButton::clicked, this, &DateBarWidget::zoomIn);
connect(this, &DateBarWidget::canZoomIn, m_zoomIn, &QToolButton::setEnabled);
m_zoomOut = new QToolButton(this);
m_zoomOut->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
m_zoomOut->setToolTip(i18n("Zoom out"));
connect(m_zoomOut, &QToolButton::clicked, this, &DateBarWidget::zoomOut);
connect(this, &DateBarWidget::canZoomOut, m_zoomOut, &QToolButton::setEnabled);
m_cancelSelection = new QToolButton(this);
m_cancelSelection->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
connect(m_cancelSelection, &QToolButton::clicked, this, &DateBarWidget::clearSelection);
m_cancelSelection->setEnabled(false);
m_cancelSelection->setToolTip(i18nc("The button clears the selection of a date range in the date bar.", "Clear date selection"));
placeAndSizeButtons();
m_focusItemDragHandler = new FocusItemDragHandler(this);
m_barDragHandler = new BarDragHandler(this);
m_selectionHandler = new SelectionHandler(this);
setWhatsThis(xi18nc("@info", "The date bar"
""
"- Scroll using the arrow buttons, the scrollwheel, or the middle mouse button.
"
"- Zoom using the +/- buttons or Ctrl + scrollwheel.
"
"- Restrict the view to a date range selection: Click/drag below the timeline.
"
"- Jump to a date by clicking on the histogram bar.
"
"
"));
setToolTip(whatsThis());
connect(Settings::SettingsData::instance(), &Settings::SettingsData::histogramScaleChanged, this, &DateBarWidget::redraw);
}
QSize DateBar::DateBarWidget::sizeHint() const
{
int height = qMax(dateAreaGeometry().bottom() + BORDER_AROUND_WIDGET,
m_barHeight + BUTTON_WIDTH + 2 * BORDER_AROUND_WIDGET + 7);
return QSize(800, height);
}
QSize DateBar::DateBarWidget::minimumSizeHint() const
{
int height = qMax(dateAreaGeometry().bottom() + BORDER_AROUND_WIDGET,
m_barHeight + BUTTON_WIDTH + 2 * BORDER_AROUND_WIDGET + 7);
return QSize(200, height);
}
+bool DateBar::DateBarWidget::event(QEvent *event)
+{
+ if (event->type() == QEvent::PaletteChange) {
+ QWidget::event(event);
+ redraw();
+ return true;
+ }
+ return QWidget::event(event);
+}
+
void DateBar::DateBarWidget::paintEvent(QPaintEvent * /*event*/)
{
QPainter painter(this);
painter.drawPixmap(0, 0, m_buffer);
}
void DateBar::DateBarWidget::redraw()
{
if (m_buffer.isNull())
return;
QPainter p(&m_buffer);
p.setRenderHint(QPainter::Antialiasing);
p.setFont(font());
// Fill with background pixels
p.save();
p.setPen(Qt::NoPen);
p.setBrush(palette().brush(QPalette::Background));
p.drawRect(rect());
if (!m_dates)
return;
// Draw the area with histograms
QRect barArea = barAreaGeometry();
p.setPen(palette().color(QPalette::Dark));
p.setBrush(palette().brush(QPalette::Base));
p.drawRect(barArea);
p.restore();
m_currentHandler->init(dateForUnit(-m_currentUnit, m_currentDate));
int right;
drawResolutionIndicator(p, &right);
QRect rect = dateAreaGeometry();
rect.setRight(right);
rect.setLeft(rect.left() + BUTTON_WIDTH + 2);
drawTickMarks(p, rect);
drawHistograms(p);
drawFocusRectangle(p);
updateArrowState();
repaint();
}
void DateBar::DateBarWidget::resizeEvent(QResizeEvent *event)
{
placeAndSizeButtons();
m_buffer = QPixmap(event->size());
m_currentUnit = numberOfUnits() / 2;
redraw();
}
void DateBar::DateBarWidget::drawTickMarks(QPainter &p, const QRect &textRect)
{
QRect rect = tickMarkGeometry();
p.save();
p.setPen(QPen(palette().color(QPalette::Text), 1));
QFont f(font());
QFontMetrics fm(f);
int fontHeight = fm.height();
int unit = 0;
QRect clip = rect;
clip.setHeight(rect.height() + 2 + fontHeight);
clip.setLeft(clip.left() + 2);
clip.setRight(clip.right() - 2);
p.setClipRect(clip);
for (int x = rect.x(); x < rect.right(); x += m_barWidth, unit += 1) {
// draw selection indication
p.save();
p.setPen(Qt::NoPen);
p.setBrush(palette().brush(QPalette::Highlight));
QDateTime date = dateForUnit(unit);
if (isUnitSelected(unit))
p.drawRect(QRect(x, rect.top(), m_barWidth, rect.height()));
p.restore();
// draw tickmarks
int h = rect.height();
if (m_currentHandler->isMajorUnit(unit)) {
QString text = m_currentHandler->text(unit);
int w = stringWidth(fm, text);
p.setFont(f);
if (textRect.right() > x + w / 2 && textRect.left() < x - w / 2)
p.drawText(x - w / 2, textRect.top(), w, fontHeight, Qt::TextSingleLine, text);
} else if (m_currentHandler->isMidUnit(unit))
h = (int)(2.0 / 3 * rect.height());
else
h = (int)(1.0 / 3 * rect.height());
p.drawLine(x, rect.top(), x, rect.top() + h);
}
p.restore();
}
void DateBar::DateBarWidget::setViewType(ViewType tp, bool redrawNow)
{
setViewHandlerForType(tp);
if (redrawNow)
redraw();
m_tp = tp;
}
void DateBar::DateBarWidget::setViewHandlerForType(ViewType tp)
{
switch (tp) {
case DecadeView:
m_currentHandler = &m_decadeViewHandler;
break;
case YearView:
m_currentHandler = &m_yearViewHandler;
break;
case MonthView:
m_currentHandler = &m_monthViewHandler;
break;
case WeekView:
m_currentHandler = &m_weekViewHandler;
break;
case DayView:
m_currentHandler = &m_dayViewHandler;
break;
case HourView:
m_currentHandler = &m_hourViewHandler;
break;
case TenMinuteView:
m_currentHandler = &m_tenMinuteViewHandler;
break;
case MinuteView:
m_currentHandler = &m_minuteViewHandler;
break;
}
}
void DateBar::DateBarWidget::setDate(const QDateTime &date)
{
m_currentDate = date;
if (hasSelection()) {
if (currentSelection().start() > m_currentDate)
m_currentDate = currentSelection().start();
if (currentSelection().end() < m_currentDate)
m_currentDate = currentSelection().end();
}
if (unitForDate(m_currentDate) != -1)
m_currentUnit = unitForDate(m_currentDate);
redraw();
}
void DateBar::DateBarWidget::setImageDateCollection(const QExplicitlySharedDataPointer &dates)
{
m_dates = dates;
if (m_doAutomaticRangeAdjustment && m_dates && !m_dates->lowerLimit().isNull()) {
QDateTime start = m_dates->lowerLimit();
QDateTime end = m_dates->upperLimit();
if (end.isNull())
end = QDateTime::currentDateTime();
m_currentDate = start;
m_currentUnit = 0;
// select suitable timeframe:
setViewType(MinuteView, false);
m_currentHandler->init(start);
while (m_tp != DecadeView && end > dateForUnit(numberOfUnits())) {
m_tp = (ViewType)(m_tp - 1);
setViewHandlerForType(m_tp);
m_currentHandler->init(start);
}
// center range in datebar:
int units = unitForDate(end);
if (units != -1) {
m_currentUnit = (numberOfUnits() - units) / 2;
}
}
redraw();
}
void DateBar::DateBarWidget::drawHistograms(QPainter &p)
{
QRect rect = barAreaGeometry();
p.save();
p.setClipping(true);
p.setClipRect(rect);
p.setPen(Qt::NoPen);
// determine maximum image count within visible units
int max = 0;
for (int unit = 0; unit <= numberOfUnits(); unit++) {
DB::ImageCount count = m_dates->count(rangeForUnit(unit));
int cnt = count.mp_exact;
if (m_includeFuzzyCounts)
cnt += count.mp_rangeMatch;
max = qMax(max, cnt);
}
// Calculate the font size for the largest number.
QFont f = font();
bool fontFound = false;
for (int i = f.pointSize(); i >= 6; i -= 2) {
f.setPointSize(i);
QFontMetrics fontMetrics(f);
int w = stringWidth(fontMetrics, QString::number(max));
if (w < rect.height() - 6) {
p.setFont(f);
fontFound = true;
break;
}
}
int unit = 0;
const bool linearScale = Settings::SettingsData::instance()->histogramUseLinearScale();
for (int x = rect.x(); x + m_barWidth < rect.right(); x += m_barWidth, unit += 1) {
const DB::ImageCount count = m_dates->count(rangeForUnit(unit));
int exactPx = 0;
int rangePx = 0;
if (max != 0) {
double exactScaled;
double rangeScaled;
if (linearScale) {
exactScaled = (double)count.mp_exact / max;
rangeScaled = (double)count.mp_rangeMatch / max;
} else {
exactScaled = sqrt(count.mp_exact) / sqrt(max);
rangeScaled = sqrt(count.mp_rangeMatch) / sqrt(max);
}
// convert to pixels:
exactPx = (int)((double)(rect.height() - 2) * exactScaled);
if (m_includeFuzzyCounts)
rangePx = (int)((double)(rect.height() - 2) * rangeScaled);
}
Qt::BrushStyle style = Qt::SolidPattern;
if (!isUnitSelected(unit) && hasSelection())
style = Qt::Dense5Pattern;
p.setBrush(QBrush(Qt::yellow, style));
p.drawRect(x + 1, rect.bottom() - rangePx, m_barWidth - 2, rangePx);
p.setBrush(QBrush(Qt::green, style));
p.drawRect(x + 1, rect.bottom() - rangePx - exactPx, m_barWidth - 2, exactPx);
// Draw the numbers, if they fit.
if (fontFound) {
int tot = count.mp_exact;
if (m_includeFuzzyCounts)
tot += count.mp_rangeMatch;
p.save();
p.translate(x + m_barWidth - 3, rect.bottom() - 2);
p.rotate(-90);
QFontMetrics fontMetrics(f);
int w = stringWidth(fontMetrics, QString::number(tot));
if (w < exactPx + rangePx - 2) {
+ // don't use a palette color here - otherwise it may have bad contrast with green and yellow:
p.setPen(Qt::black);
p.drawText(0, 0, QString::number(tot));
}
p.restore();
}
}
p.restore();
}
void DateBar::DateBarWidget::scrollLeft()
{
int scrollAmount = -SCROLL_AMOUNT;
if (QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
scrollAmount *= SCROLL_ACCELERATION;
scroll(scrollAmount);
}
void DateBar::DateBarWidget::scrollRight()
{
int scrollAmount = SCROLL_AMOUNT;
if (QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
scrollAmount *= SCROLL_ACCELERATION;
scroll(scrollAmount);
}
void DateBar::DateBarWidget::scroll(int units)
{
m_currentDate = dateForUnit(units, m_currentDate);
redraw();
emit dateSelected(currentDateRange(), includeFuzzyCounts());
}
void DateBar::DateBarWidget::drawFocusRectangle(QPainter &p)
{
QRect rect = barAreaGeometry();
p.save();
int x = rect.left() + m_currentUnit * m_barWidth;
QRect inner(QPoint(x - 1, BORDER_ABOVE_HISTOGRAM),
QPoint(x + m_barWidth, BORDER_ABOVE_HISTOGRAM + m_barHeight - 1));
p.setPen(QPen(palette().color(QPalette::Dark), 1));
// Inner rect
p.drawRect(inner);
QRect outer = inner;
outer.adjust(-2, -2, 2, 2);
// Outer rect
QRegion region = outer;
region -= inner;
p.setClipping(true);
p.setClipRegion(region);
- QColor col = Qt::gray;
+ QColor col = palette().highlight().color();
if (!hasFocus())
- col = Qt::white;
+ col = palette().window().color();
p.setBrush(col);
p.setPen(col);
p.drawRect(outer);
// Shadow below
QRect shadow = outer;
shadow.adjust(-1, -1, 1, 1);
region = shadow;
region -= outer;
p.setPen(palette().color(QPalette::Shadow));
p.setClipRegion(region);
p.drawRect(shadow);
// Light above
QRect hide = shadow;
hide.translate(1, 1);
region = shadow;
region -= hide;
p.setPen(palette().color(QPalette::Light));
p.setClipRegion(region);
p.drawRect(shadow);
p.restore();
}
void DateBar::DateBarWidget::zoomIn()
{
if (m_tp == MinuteView)
return;
zoom(+1);
}
void DateBar::DateBarWidget::zoomOut()
{
if (m_tp == DecadeView)
return;
zoom(-1);
}
void DateBar::DateBarWidget::zoom(int steps)
{
ViewType tp = (ViewType)(m_tp + steps);
setViewType(tp);
emit canZoomIn(tp != MinuteView);
emit canZoomOut(tp != DecadeView);
}
void DateBar::DateBarWidget::mousePressEvent(QMouseEvent *event)
{
if ((event->button() & (Qt::MidButton | Qt::LeftButton)) == 0 || event->x() > barAreaGeometry().right() || event->x() < barAreaGeometry().left())
return;
if ((event->button() & Qt::MidButton)
|| event->modifiers() & Qt::ControlModifier) {
m_currentMouseHandler = m_barDragHandler;
} else {
bool onBar = event->y() > barAreaGeometry().bottom();
if (onBar)
m_currentMouseHandler = m_selectionHandler;
else {
m_currentMouseHandler = m_focusItemDragHandler;
}
}
m_currentMouseHandler->mousePressEvent(event->x());
m_cancelSelection->setEnabled(hasSelection());
emit dateSelected(currentDateRange(), includeFuzzyCounts());
showStatusBarTip(event->pos());
redraw();
}
void DateBar::DateBarWidget::mouseReleaseEvent(QMouseEvent *)
{
if (m_currentMouseHandler == nullptr)
return;
m_currentMouseHandler->endAutoScroll();
m_currentMouseHandler->mouseReleaseEvent();
m_currentMouseHandler = nullptr;
}
void DateBar::DateBarWidget::mouseMoveEvent(QMouseEvent *event)
{
showStatusBarTip(event->pos());
if (m_currentMouseHandler == nullptr)
return;
if ((event->buttons() & (Qt::MidButton | Qt::LeftButton)) == 0)
return;
m_currentMouseHandler->endAutoScroll();
m_currentMouseHandler->mouseMoveEvent(event->pos().x());
}
QRect DateBar::DateBarWidget::barAreaGeometry() const
{
QRect barArea;
barArea.setTopLeft(QPoint(BORDER_AROUND_WIDGET, BORDER_ABOVE_HISTOGRAM));
barArea.setRight(width() - BORDER_AROUND_WIDGET - 2 * BUTTON_WIDTH - 2 * 3); // 2 pixels between button and bar + 1 pixel as the pen is one pixel
barArea.setHeight(m_barHeight);
return barArea;
}
int DateBar::DateBarWidget::numberOfUnits() const
{
return barAreaGeometry().width() / m_barWidth - 1;
}
void DateBar::DateBarWidget::setHistogramBarSize(const QSize &size)
{
m_barWidth = size.width();
m_barHeight = size.height();
m_currentUnit = numberOfUnits() / 2;
Q_ASSERT(parentWidget());
updateGeometry();
Q_ASSERT(parentWidget());
placeAndSizeButtons();
redraw();
}
void DateBar::DateBarWidget::setIncludeFuzzyCounts(bool b)
{
m_includeFuzzyCounts = b;
redraw();
if (hasSelection())
emitRangeSelection(m_selectionHandler->dateRange());
emit dateSelected(currentDateRange(), includeFuzzyCounts());
}
DB::ImageDate DateBar::DateBarWidget::rangeAt(const QPoint &p)
{
int unit = (p.x() - barAreaGeometry().x()) / m_barWidth;
return rangeForUnit(unit);
}
DB::ImageDate DateBar::DateBarWidget::rangeForUnit(int unit)
{
// Note on the use of setTimeSpec.
// It came to my attention that addSec would create a QDateTime with internal type LocalStandard, while all the others would have type LocalUnknown,
// this resulted in that QDateTime::operator<() would call getUTC(), which took 90% of the time for populating the datebar.
QDateTime toUnit = dateForUnit(unit + 1).addSecs(-1);
toUnit.setTimeSpec(Qt::LocalTime);
return DB::ImageDate(dateForUnit(unit), toUnit);
}
bool DateBar::DateBarWidget::includeFuzzyCounts() const
{
return m_includeFuzzyCounts;
}
void DateBar::DateBarWidget::contextMenuEvent(QContextMenuEvent *event)
{
if (!m_contextMenu) {
m_contextMenu = new QMenu(this);
QAction *action = new QAction(i18n("Show Ranges"), this);
action->setCheckable(true);
m_contextMenu->addAction(action);
action->setChecked(m_includeFuzzyCounts);
connect(action, &QAction::toggled, this, &DateBarWidget::setIncludeFuzzyCounts);
action = new QAction(i18n("Show Resolution Indicator"), this);
action->setCheckable(true);
m_contextMenu->addAction(action);
action->setChecked(m_showResolutionIndicator);
connect(action, &QAction::toggled, this, &DateBarWidget::setShowResolutionIndicator);
}
m_contextMenu->exec(event->globalPos());
event->setAccepted(true);
}
QRect DateBar::DateBarWidget::tickMarkGeometry() const
{
QRect rect;
rect.setTopLeft(barAreaGeometry().bottomLeft());
rect.setWidth(barAreaGeometry().width());
rect.setHeight(12);
return rect;
}
void DateBar::DateBarWidget::drawResolutionIndicator(QPainter &p, int *leftEdge)
{
QRect rect = dateAreaGeometry();
// For real small bars, we do not want to show the resolution.
if (rect.width() < 400 || !m_showResolutionIndicator) {
*leftEdge = rect.right();
return;
}
QString text = m_currentHandler->unitText();
QFontMetrics fontMetrics(font());
int textWidth = stringWidth(fontMetrics, text);
int height = fontMetrics.height();
int endUnitPos = rect.right() - textWidth - ARROW_LENGTH - 3;
// Round to nearest unit mark
endUnitPos = ((endUnitPos - rect.left()) / m_barWidth) * m_barWidth + rect.left();
int startUnitPos = endUnitPos - m_barWidth;
int midLine = rect.top() + height / 2;
p.save();
- p.setPen(Qt::red);
+ p.setPen(palette().windowText().color());
// draw arrows
drawArrow(p, QPoint(startUnitPos - ARROW_LENGTH, midLine), QPoint(startUnitPos, midLine));
drawArrow(p, QPoint(endUnitPos + ARROW_LENGTH, midLine), QPoint(endUnitPos, midLine));
p.drawLine(startUnitPos, rect.top(), startUnitPos, rect.top() + height);
p.drawLine(endUnitPos, rect.top(), endUnitPos, rect.top() + height);
// draw text
QFontMetrics fm(font());
p.drawText(endUnitPos + ARROW_LENGTH + 3, rect.top(), stringWidth(fm, text), fm.height(), Qt::TextSingleLine, text);
p.restore();
*leftEdge = startUnitPos - ARROW_LENGTH - 3;
}
QRect DateBar::DateBarWidget::dateAreaGeometry() const
{
QRect rect = tickMarkGeometry();
rect.setTop(rect.bottom() + 2);
rect.setHeight(QFontMetrics(font()).height());
return rect;
}
void DateBar::DateBarWidget::drawArrow(QPainter &p, const QPoint &start, const QPoint &end)
{
p.save();
p.drawLine(start, end);
QPoint diff = QPoint(end.x() - start.x(), end.y() - start.y());
double dx = diff.x();
double dy = diff.y();
if (dx != 0 || dy != 0) {
if (dy < 0)
dx = -dx;
double angle = acos(dx / sqrt(dx * dx + dy * dy)) * 180. / M_PI;
if (dy < 0)
angle += 180.;
// angle is now the angle of the line.
angle = angle + 180 - 15;
p.translate(end.x(), end.y());
p.rotate(angle);
p.drawLine(QPoint(0, 0), QPoint(10, 0));
p.rotate(30);
p.drawLine(QPoint(0, 0), QPoint(10, 0));
}
p.restore();
}
void DateBar::DateBarWidget::setShowResolutionIndicator(bool b)
{
m_showResolutionIndicator = b;
redraw();
}
void DateBar::DateBarWidget::setAutomaticRangeAdjustment(bool b)
{
m_doAutomaticRangeAdjustment = b;
}
void DateBar::DateBarWidget::updateArrowState()
{
m_leftArrow->setEnabled(m_dates->lowerLimit() <= dateForUnit(0));
m_rightArrow->setEnabled(m_dates->upperLimit() > dateForUnit(numberOfUnits()));
}
DB::ImageDate DateBar::DateBarWidget::currentDateRange() const
{
return DB::ImageDate(dateForUnit(m_currentUnit), dateForUnit(m_currentUnit + 1));
}
void DateBar::DateBarWidget::showStatusBarTip(const QPoint &pos)
{
DB::ImageDate range = rangeAt(pos);
DB::ImageCount count = m_dates->count(range);
QString cnt;
if (count.mp_rangeMatch != 0 && includeFuzzyCounts())
cnt = i18ncp("@info:status images that fall in the given date range", "1 exact", "%1 exact", count.mp_exact)
+ i18ncp("@info:status additional images captured in a date range that overlaps with the given date range,", " + 1 range", " + %1 ranges", count.mp_rangeMatch)
+ i18ncp("@info:status total image count", " = 1 total", " = %1 total", count.mp_exact + count.mp_rangeMatch);
else
cnt = i18ncp("@info:status image count", "%1 image/video", "%1 images/videos", count.mp_exact);
QString res = i18nc("@info:status Time range vs. image count (e.g. 'Jun 2012 | 4 images/videos').", "%1 | %2", range.toString(), cnt);
static QString lastTip;
if (lastTip != res)
emit toolTipInfo(res);
lastTip = res;
}
void DateBar::DateBarWidget::placeAndSizeButtons()
{
m_zoomIn->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
m_zoomOut->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
m_rightArrow->setFixedSize(QSize(BUTTON_WIDTH, m_barHeight));
m_leftArrow->setFixedSize(QSize(BUTTON_WIDTH, m_barHeight));
m_rightArrow->move(size().width() - m_rightArrow->width() - BORDER_AROUND_WIDGET, BORDER_ABOVE_HISTOGRAM);
m_leftArrow->move(m_rightArrow->pos().x() - m_leftArrow->width() - 2, BORDER_ABOVE_HISTOGRAM);
int x = m_leftArrow->pos().x();
int y = height() - BUTTON_WIDTH;
m_zoomOut->move(x, y);
x = m_rightArrow->pos().x();
m_zoomIn->move(x, y);
m_cancelSelection->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
m_cancelSelection->move(0, y);
}
void DateBar::DateBarWidget::keyPressEvent(QKeyEvent *event)
{
int offset = 0;
if (event->key() == Qt::Key_Plus) {
if (m_tp != MinuteView)
zoom(1);
return;
}
if (event->key() == Qt::Key_Minus) {
if (m_tp != DecadeView)
zoom(-1);
return;
}
if (event->key() == Qt::Key_Left)
offset = -1;
else if (event->key() == Qt::Key_Right)
offset = 1;
else if (event->key() == Qt::Key_PageDown)
offset = -10;
else if (event->key() == Qt::Key_PageUp)
offset = 10;
else
return;
QDateTime newDate = dateForUnit(offset, m_currentDate);
if ((offset < 0 && newDate >= m_dates->lowerLimit()) || (offset > 0 && newDate <= m_dates->upperLimit())) {
m_currentDate = newDate;
m_currentUnit += offset;
if (m_currentUnit < 0)
m_currentUnit = 0;
if (m_currentUnit > numberOfUnits())
m_currentUnit = numberOfUnits();
if (!currentSelection().includes(m_currentDate))
clearSelection();
}
redraw();
emit dateSelected(currentDateRange(), includeFuzzyCounts());
}
void DateBar::DateBarWidget::focusInEvent(QFocusEvent *)
{
redraw();
}
void DateBar::DateBarWidget::focusOutEvent(QFocusEvent *)
{
redraw();
}
int DateBar::DateBarWidget::unitAtPos(int x) const
{
Q_ASSERT_X(x - barAreaGeometry().left() >= 0, "DateBarWidget::unitAtPos", "horizontal offset cannot be negative!");
Q_ASSERT_X(x - barAreaGeometry().left() <= m_barWidth, "DateBarWidget::unitAtPos", "horizontal offset larger than m_barWidth!");
return (x - barAreaGeometry().left()) / m_barWidth;
}
QDateTime DateBar::DateBarWidget::dateForUnit(int unit, const QDateTime &offset) const
{
return m_currentHandler->date(unit, offset);
}
bool DateBar::DateBarWidget::isUnitSelected(int unit) const
{
QDateTime minDate = m_selectionHandler->min();
QDateTime maxDate = m_selectionHandler->max();
QDateTime date = dateForUnit(unit);
return (minDate <= date && date < maxDate && !minDate.isNull());
}
bool DateBar::DateBarWidget::hasSelection() const
{
return !m_selectionHandler->min().isNull();
}
DB::ImageDate DateBar::DateBarWidget::currentSelection() const
{
return DB::ImageDate(m_selectionHandler->min(), m_selectionHandler->max());
}
void DateBar::DateBarWidget::clearSelection()
{
if (m_selectionHandler->hasSelection()) {
m_selectionHandler->clearSelection();
emit dateRangeCleared();
redraw();
}
m_cancelSelection->setEnabled(false);
}
void DateBar::DateBarWidget::emitRangeSelection(const DB::ImageDate &range)
{
emit dateRangeChange(range);
}
int DateBar::DateBarWidget::unitForDate(const QDateTime &date) const
{
for (int unit = 0; unit < numberOfUnits(); ++unit) {
if (m_currentHandler->date(unit) <= date && date < m_currentHandler->date(unit + 1))
return unit;
}
return -1;
}
void DateBar::DateBarWidget::emitDateSelected()
{
emit dateSelected(currentDateRange(), includeFuzzyCounts());
}
void DateBar::DateBarWidget::wheelEvent(QWheelEvent *e)
{
if (e->modifiers() & Qt::ControlModifier) {
if (e->delta() > 0)
zoomIn();
else
zoomOut();
return;
}
int scrollAmount = e->delta() > 0 ? SCROLL_AMOUNT : -SCROLL_AMOUNT;
if (e->modifiers() & Qt::ShiftModifier)
scrollAmount *= SCROLL_ACCELERATION;
scroll(scrollAmount);
}
int DateBar::DateBarWidget::stringWidth(const QFontMetrics &fontMetrics, const QString &text) const
{
// This is a workaround for the deprecation warnings emerged with Qt 5.13.
// QFontMetrics::horizontalAdvance wasn't introduced until Qt 5.11. As soon as we drop support
// for Qt versions before 5.11, this can be removed in favor of calling horizontalAdvance
// directly.
#if (QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
return fontMetrics.width(text);
#else
return fontMetrics.horizontalAdvance(text);
#endif
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/DateBar/DateBarWidget.h b/DateBar/DateBarWidget.h
index f8fea548..3088fc71 100644
--- a/DateBar/DateBarWidget.h
+++ b/DateBar/DateBarWidget.h
@@ -1,234 +1,235 @@
-/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team
+/* 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.
*/
#ifndef DATEBAR_H
#define DATEBAR_H
#include "ViewHandler.h"
#include
#include
#include
#include
class QMenu;
class QKeyEvent;
class QMouseEvent;
class QFocusEvent;
class QResizeEvent;
class QPaintEvent;
class QWheelEvent;
class QContextMenuEvent;
class QToolButton;
class QFontMetrics;
namespace DB
{
class ImageDateCollection;
class ImageDate;
}
namespace DateBar
{
class MouseHandler;
class FocusItemDragHandler;
class BarDragHandler;
class SelectionHandler;
/**
* @brief The DateBarWidget class provides a histogram-like depiction of the image distribution over time.
* If \ref includeFuzzyCounts() is \c true,
* then both exact and fuzzy dates are taken into account and shown in different style (currently yellow and green).
* If enough space is available, the number of images within each time period is printed inside each box.
*
* ## "unit" concept
* A central concept in the widget design is that of a time \c unit.
* Units are an integer offset into the number of available "boxes".
* The number of units (or boxes) is calculated according to the available space (see \ref numberOfUnits()).
* Each unit corresponds to a time period at the current resolution and offset.
*
* The time resulution is represented by the \c ViewHandler, and the offset is stored in \c m_currentDate.
*
*/
class DateBarWidget : public QWidget
{
Q_OBJECT
public:
explicit DateBarWidget(QWidget *parent);
enum ViewType { DecadeView,
YearView,
MonthView,
WeekView,
DayView,
HourView,
TenMinuteView,
MinuteView };
/**
* @brief includeFuzzyCounts
* @return \c true if date ranges are shown, \c false otherwise.
* @see setIncludeFuzzyCounts
*/
bool includeFuzzyCounts() const;
public slots:
void clearSelection();
void setViewType(ViewType tp, bool redrawNow = true);
void setDate(const QDateTime &date);
void setImageDateCollection(const QExplicitlySharedDataPointer &);
void scrollLeft();
void scrollRight();
void scroll(int units);
void zoomIn();
void zoomOut();
void setHistogramBarSize(const QSize &size);
void setIncludeFuzzyCounts(bool);
/**
* @brief setShowResolutionIndicator
* If set to \c true, an indicator is shown to indicate the current ViewType.
* The indicator indicates the size of one unit / histogram box alongside a text
* indicating the respective temporal resolution (e.g. "10 minutes").
*/
void setShowResolutionIndicator(bool);
/**
* @brief setAutomaticRangeAdjustment
* If set to \c true, the ViewType and range is adjusted automatically to best
* match the current set of images.
*/
void setAutomaticRangeAdjustment(bool);
signals:
void canZoomIn(bool);
void canZoomOut(bool);
void dateSelected(const DB::ImageDate &, bool includeRanges);
void toolTipInfo(const QString &);
void dateRangeChange(const DB::ImageDate &);
void dateRangeCleared();
public:
// Overridden methods for internal purpose
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
protected:
+ bool event(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
void focusInEvent(QFocusEvent *) override;
void focusOutEvent(QFocusEvent *) override;
void wheelEvent(QWheelEvent *e) override;
/**
* @brief redraw the widget
* This method creates a QPainter and then uses the draw* methods to draw the different parts of the widget.
* \see drawTickMarks
* \see drawHistograms
* \see drawFocusRectangle
* \see drawResolutionIndicator
*/
void redraw();
void drawTickMarks(QPainter &p, const QRect &textRect);
void drawHistograms(QPainter &p);
void drawFocusRectangle(QPainter &p);
void drawResolutionIndicator(QPainter &p, int *leftEdge);
/**
* @brief zoom in or out by a number of steps.
* One steps corresponds to one step in the ViewType.
* @param steps positive steps to increase temporal resolution, negative to decrease.
*/
void zoom(int steps);
QRect barAreaGeometry() const;
QRect tickMarkGeometry() const;
QRect dateAreaGeometry() const;
int numberOfUnits() const;
void drawArrow(QPainter &, const QPoint &start, const QPoint &end);
void updateArrowState();
DB::ImageDate currentDateRange() const;
void showStatusBarTip(const QPoint &pos);
DB::ImageDate rangeAt(const QPoint &);
DB::ImageDate rangeForUnit(int unit);
void placeAndSizeButtons();
/**
* @brief unitAtPos maps horizontal screen coordinates to units.
* @param x a valid pixel offset in the histogram area
* @return a unit index between 0 and numberOfUnits
*/
int unitAtPos(int x) const;
QDateTime dateForUnit(int unit, const QDateTime &offset = QDateTime()) const;
/**
* @brief unitForDate return the unit index corresponding to the date/time.
* @param date a valid QDateTime.
* @return An integer greater or equal to 0 if \p date is in view, -1 otherwise.
*/
int unitForDate(const QDateTime &date) const;
bool isUnitSelected(int unit) const;
bool hasSelection() const;
DB::ImageDate currentSelection() const;
void emitDateSelected();
void emitRangeSelection(const DB::ImageDate &);
private:
void setViewHandlerForType(ViewType tp);
int stringWidth(const QFontMetrics &fontMetrics, const QString &text) const;
QPixmap m_buffer;
friend class DateBarTip;
QExplicitlySharedDataPointer m_dates;
DecadeViewHandler m_decadeViewHandler;
YearViewHandler m_yearViewHandler;
MonthViewHandler m_monthViewHandler;
WeekViewHandler m_weekViewHandler;
DayViewHandler m_dayViewHandler;
HourViewHandler m_hourViewHandler;
TenMinuteViewHandler m_tenMinuteViewHandler;
MinuteViewHandler m_minuteViewHandler;
ViewHandler *m_currentHandler;
ViewType m_tp;
MouseHandler *m_currentMouseHandler;
FocusItemDragHandler *m_focusItemDragHandler;
BarDragHandler *m_barDragHandler;
SelectionHandler *m_selectionHandler;
friend class Handler;
friend class FocusItemDragHandler;
friend class BarDragHandler;
friend class SelectionHandler;
QToolButton *m_rightArrow;
QToolButton *m_leftArrow;
QToolButton *m_zoomIn;
QToolButton *m_zoomOut;
QToolButton *m_cancelSelection;
int m_currentUnit;
QDateTime m_currentDate;
int m_barWidth;
int m_barHeight;
bool m_includeFuzzyCounts;
QMenu *m_contextMenu;
bool m_showResolutionIndicator;
bool m_doAutomaticRangeAdjustment;
};
}
#endif /* DATEBAR_H */
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/Exif/Grid.cpp b/Exif/Grid.cpp
index bfe56aa4..9b020904 100644
--- a/Exif/Grid.cpp
+++ b/Exif/Grid.cpp
@@ -1,212 +1,213 @@
-/* Copyright (C) 2012-2019 The KPhotoAlbum Development Team
+/* Copyright (C) 2012-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.
*/
#include "Grid.h"
#include "Info.h"
#include
#include
#include
#include
#include
#include
#include
#include
Exif::Grid::Grid(QWidget *parent)
: QScrollArea(parent)
{
setFocusPolicy(Qt::WheelFocus);
setWidgetResizable(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
viewport()->installEventFilter(this);
setMinimumSize(800, 400);
}
class Background : public QWidget
{
protected:
void paintEvent(QPaintEvent *event) override
{
QPainter painter(this);
- painter.fillRect(event->rect(), QColor(Qt::white));
+ painter.fillRect(event->rect(), QColor(palette().background().color()));
}
};
void Exif::Grid::setupUI(const QString &charset)
{
delete this->widget();
m_labels.clear();
Background *widget = new Background;
QGridLayout *layout = new QGridLayout(widget);
layout->setSpacing(0);
int row = 0;
const QMap map = Exif::Info::instance()->infoForDialog(m_fileName, charset);
const StringSet groups = exifGroups(map);
for (const QString &group : groups) {
layout->addWidget(headerLabel(group), row++, 0, 1, 4);
// Items of group
const QMap items = itemsForGroup(group, map);
QStringList sorted = items.keys();
sorted.sort();
int elements = sorted.size();
int perCol = (elements + 1) / 2;
int count = 0;
for (const QString &key : sorted) {
const int subrow = (count % perCol);
- const QColor color = (subrow & 1) ? Qt::white : QColor(226, 235, 250);
+ const QColor color = (subrow & 1) ? palette().base().color() : palette().alternateBase().color();
QPair pair = infoLabelPair(exifNameNoGroup(key), items[key].join(QLatin1String(", ")), color);
int col = (count / perCol) * 2;
layout->addWidget(pair.first, row + subrow, col);
layout->addWidget(pair.second, row + subrow, col + 1);
count++;
}
row += perCol;
}
setWidget(widget);
widget->show();
QTimer::singleShot(0, this, SLOT(updateWidgetSize()));
}
QLabel *Exif::Grid::headerLabel(const QString &title)
{
QLabel *label = new QLabel(title);
QPalette pal;
- pal.setBrush(QPalette::Background, Qt::lightGray);
+ pal.setBrush(QPalette::Window, palette().dark());
+ pal.setBrush(QPalette::WindowText, palette().brightText());
label->setPalette(pal);
label->setAutoFillBackground(true);
label->setAlignment(Qt::AlignCenter);
return label;
}
QPair Exif::Grid::infoLabelPair(const QString &title, const QString &value, const QColor &color)
{
QLabel *keyLabel = new QLabel(title);
QLabel *valueLabel = new QLabel(value);
QPalette pal;
pal.setBrush(QPalette::Background, color);
keyLabel->setPalette(pal);
valueLabel->setPalette(pal);
keyLabel->setAutoFillBackground(true);
valueLabel->setAutoFillBackground(true);
m_labels.append(qMakePair(keyLabel, valueLabel));
return qMakePair(keyLabel, valueLabel);
}
void Exif::Grid::updateWidgetSize()
{
widget()->setFixedSize(viewport()->width(), widget()->height());
}
StringSet Exif::Grid::exifGroups(const QMap &exifInfo)
{
StringSet result;
for (QMap::ConstIterator it = exifInfo.begin(); it != exifInfo.end(); ++it) {
result.insert(groupName(it.key()));
}
return result;
}
QMap Exif::Grid::itemsForGroup(const QString &group, const QMap &exifInfo)
{
QMap result;
for (QMap::ConstIterator it = exifInfo.begin(); it != exifInfo.end(); ++it) {
if (groupName(it.key()) == group)
result.insert(it.key(), it.value());
}
return result;
}
QString Exif::Grid::groupName(const QString &exifName)
{
QStringList list = exifName.split(QString::fromLatin1("."));
list.pop_back();
return list.join(QString::fromLatin1("."));
}
QString Exif::Grid::exifNameNoGroup(const QString &fullName)
{
return fullName.split(QString::fromLatin1(".")).last();
}
void Exif::Grid::scroll(int dy)
{
verticalScrollBar()->setValue(verticalScrollBar()->value() + dy);
}
void Exif::Grid::updateSearchString(const QString &search)
{
for (QPair tuple : m_labels) {
const bool matches = tuple.first->text().contains(search, Qt::CaseInsensitive) && search.length() != 0;
QPalette pal = tuple.first->palette();
- pal.setBrush(QPalette::Foreground, matches ? Qt::red : Qt::black);
+ pal.setBrush(QPalette::Foreground, matches ? Qt::red : palette().text());
tuple.first->setPalette(pal);
tuple.second->setPalette(pal);
QFont fnt = tuple.first->font();
fnt.setBold(matches);
tuple.first->setFont(fnt);
tuple.second->setFont(fnt);
}
}
void Exif::Grid::keyPressEvent(QKeyEvent *e)
{
switch (e->key()) {
case Qt::Key_Down:
scroll(20);
return;
case Qt::Key_Up:
scroll(-20);
return;
case Qt::Key_PageDown:
scroll(viewport()->height() - 20);
return;
case Qt::Key_PageUp:
scroll(-(viewport()->height() - 20));
return;
case Qt::Key_Escape:
QScrollArea::keyPressEvent(e); // Propagate to close dialog.
return;
}
}
bool Exif::Grid::eventFilter(QObject *object, QEvent *event)
{
if (object == viewport() && event->type() == QEvent::Resize) {
QResizeEvent *re = static_cast(event);
widget()->setFixedSize(re->size().width(), widget()->height());
}
return false;
}
void Exif::Grid::setFileName(const DB::FileName &fileName)
{
m_fileName = fileName;
setupUI(Settings::SettingsData::instance()->iptcCharset());
}
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/MainWindow/Window.cpp b/MainWindow/Window.cpp
index f6bf94ab..a6a4444a 100644
--- a/MainWindow/Window.cpp
+++ b/MainWindow/Window.cpp
@@ -1,1998 +1,2019 @@
/* 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.
*/
#include "Window.h"
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for #if KIO_VERSION...
#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
#include
#ifdef HASKIPI
#include
#endif
#ifdef KF5Purpose_FOUND
#include
#endif
#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"
#ifdef HAVE_MARBLE
#include