File indexing completed on 2024-04-28 04:20:56

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "DateBarWidget.h"
0007 
0008 #include "MouseHandler.h"
0009 
0010 #include <DB/ImageDateCollection.h>
0011 #include <DB/ImageInfoList.h>
0012 #include <kpabase/SettingsData.h>
0013 
0014 #include <Utilities/FastDateTime.h>
0015 #include <KActionCollection>
0016 #include <KLocalizedString>
0017 #include <QAction>
0018 #include <QContextMenuEvent>
0019 #include <QDebug>
0020 #include <QFontMetrics>
0021 #include <QGuiApplication>
0022 #include <QIcon>
0023 #include <QLocale>
0024 #include <QMenu>
0025 #include <QPainter>
0026 #include <QToolButton>
0027 #include <math.h>
0028 
0029 namespace
0030 {
0031 constexpr int BORDER_ABOVE_HISTOGRAM = 4;
0032 constexpr int BORDER_AROUND_WIDGET = 0;
0033 constexpr int BUTTON_WIDTH = 22;
0034 constexpr int ARROW_LENGTH = 20;
0035 
0036 constexpr int SCROLL_AMOUNT = 1;
0037 constexpr int SCROLL_ACCELERATION = 10;
0038 }
0039 
0040 /**
0041  * \class DateBar::DateBarWidget
0042  * \brief This class represents the date bar at the bottom of the main window.
0043  *
0044  * The mouse interaction is handled by the classes which inherits \ref DateBar::MouseHandler, while the logic for
0045  * deciding the length (in minutes, hours, days, etc) are handled by subclasses of \ref DateBar::ViewHandler.
0046  */
0047 
0048 DateBar::DateBarWidget::DateBarWidget(QWidget *parent)
0049     : QWidget(parent)
0050     , m_currentHandler(&m_yearViewHandler)
0051     , m_tp(YearView)
0052     , m_currentMouseHandler(nullptr)
0053     , m_currentUnit(0)
0054     , m_currentDate(Utilities::FastDateTime::currentDateTime())
0055     , m_includeFuzzyCounts(true)
0056     , m_contextMenu(nullptr)
0057     , m_showResolutionIndicator(true)
0058     , m_doAutomaticRangeAdjustment(true)
0059     , m_actionCollection(new KActionCollection(this))
0060 {
0061     setAccessibleName(i18nc("Accessible name for the date bar widget", "Datebar"));
0062     setMouseTracking(true);
0063     setFocusPolicy(Qt::StrongFocus);
0064 
0065     m_barWidth = Settings::SettingsData::instance()->histogramSize().width();
0066     m_barHeight = Settings::SettingsData::instance()->histogramSize().height();
0067 
0068     auto scrollRightAction = m_actionCollection->addAction(QString::fromLatin1("datebar-scroll-right"));
0069     scrollRightAction->setShortcutContext(Qt::ApplicationShortcut);
0070     scrollRightAction->setText(i18n("Scroll right"));
0071     scrollRightAction->setAutoRepeat(true);
0072     connect(scrollRightAction, &QAction::triggered, this, &DateBarWidget::scrollRight);
0073     m_rightArrow = new QToolButton(this);
0074     m_rightArrow->setDefaultAction(scrollRightAction);
0075     m_rightArrow->setArrowType(Qt::RightArrow);
0076 
0077     auto scrollLeftAction = m_actionCollection->addAction(QString::fromLatin1("datebar-scroll-left"));
0078     scrollLeftAction->setShortcutContext(Qt::ApplicationShortcut);
0079     scrollLeftAction->setText(i18n("Scroll left"));
0080     scrollLeftAction->setAutoRepeat(true);
0081     connect(scrollLeftAction, &QAction::triggered, this, &DateBarWidget::scrollLeft);
0082     m_leftArrow = new QToolButton(this);
0083     m_leftArrow->setDefaultAction(scrollLeftAction);
0084     m_leftArrow->setArrowType(Qt::LeftArrow);
0085 
0086     auto zoomInAction = m_actionCollection->addAction(QString::fromLatin1("datebar-zoom-in"));
0087     zoomInAction->setShortcutContext(Qt::ApplicationShortcut);
0088     zoomInAction->setText(i18n("Zoom in"));
0089     zoomInAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
0090     zoomInAction->setEnabled(canZoomIn());
0091     connect(zoomInAction, &QAction::triggered, this, &DateBarWidget::zoomIn);
0092     connect(this, &DateBarWidget::zoomInEnabled, zoomInAction, &QAction::setEnabled);
0093     m_zoomIn = new QToolButton(this);
0094     m_zoomIn->setDefaultAction(zoomInAction);
0095     m_zoomIn->setFocusPolicy(Qt::ClickFocus);
0096 
0097     auto zoomOutAction = m_actionCollection->addAction(QString::fromLatin1("datebar-zoom-out"));
0098     zoomOutAction->setShortcutContext(Qt::ApplicationShortcut);
0099     zoomOutAction->setText(i18n("Zoom out"));
0100     zoomOutAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
0101     zoomOutAction->setEnabled(canZoomOut());
0102     connect(zoomOutAction, &QAction::triggered, this, &DateBarWidget::zoomOut);
0103     connect(this, &DateBarWidget::zoomOutEnabled, zoomOutAction, &QAction::setEnabled);
0104     m_zoomOut = new QToolButton(this);
0105     m_zoomOut->setDefaultAction(zoomOutAction);
0106     m_zoomOut->setFocusPolicy(Qt::ClickFocus);
0107 
0108     auto clearSelectionAction = m_actionCollection->addAction(QString::fromLatin1("datebar-clear-selection"));
0109     clearSelectionAction->setShortcutContext(Qt::ApplicationShortcut);
0110     clearSelectionAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
0111     clearSelectionAction->setText(i18nc("The button clears the selection of a date range in the date bar.", "Clear date selection"));
0112     clearSelectionAction->setEnabled(false);
0113     connect(clearSelectionAction, &QAction::triggered, this, &DateBarWidget::clearSelection);
0114     connect(this, &DateBarWidget::dateRangeSelected, clearSelectionAction, &QAction::setEnabled);
0115     m_cancelSelection = new QToolButton(this);
0116     m_cancelSelection->setDefaultAction(clearSelectionAction);
0117 
0118     placeAndSizeButtons();
0119 
0120     m_focusItemDragHandler = new FocusItemDragHandler(this);
0121     m_barDragHandler = new BarDragHandler(this);
0122     m_selectionHandler = new SelectionHandler(this);
0123 
0124     setWhatsThis(xi18nc("@info", "<title>The date bar</title>"
0125                                  "<para><list>"
0126                                  "<item>Scroll using the arrow buttons, the scrollwheel, or the middle mouse button.</item>"
0127                                  "<item>Zoom using the +/- buttons or Ctrl + scrollwheel.</item>"
0128                                  "<item>Restrict the view to a date range selection: Click/drag below the timeline.</item>"
0129                                  "<item>Jump to a date by clicking on the histogram bar.</item>"
0130                                  "</list></para>"));
0131     setToolTip(whatsThis());
0132 
0133     connect(Settings::SettingsData::instance(), &Settings::SettingsData::histogramScaleChanged, this, &DateBarWidget::redraw);
0134     m_actionCollection->readSettings();
0135 }
0136 
0137 QSize DateBar::DateBarWidget::sizeHint() const
0138 {
0139     int height = qMax(dateAreaGeometry().bottom() + BORDER_AROUND_WIDGET,
0140                       m_barHeight + BUTTON_WIDTH + 2 * BORDER_AROUND_WIDGET + 7);
0141     return QSize(800, height);
0142 }
0143 
0144 QSize DateBar::DateBarWidget::minimumSizeHint() const
0145 {
0146     int height = qMax(dateAreaGeometry().bottom() + BORDER_AROUND_WIDGET,
0147                       m_barHeight + BUTTON_WIDTH + 2 * BORDER_AROUND_WIDGET + 7);
0148     return QSize(200, height);
0149 }
0150 
0151 bool DateBar::DateBarWidget::event(QEvent *event)
0152 {
0153     if (event->type() == QEvent::PaletteChange) {
0154         QWidget::event(event);
0155         redraw();
0156         return true;
0157     }
0158     return QWidget::event(event);
0159 }
0160 
0161 void DateBar::DateBarWidget::paintEvent(QPaintEvent * /*event*/)
0162 {
0163     QPainter painter(this);
0164     painter.drawPixmap(0, 0, m_buffer);
0165 }
0166 
0167 void DateBar::DateBarWidget::redraw()
0168 {
0169     if (m_buffer.isNull())
0170         return;
0171 
0172     QPainter p(&m_buffer);
0173     p.setRenderHint(QPainter::Antialiasing);
0174     p.setFont(font());
0175 
0176     // Fill with background pixels
0177     p.save();
0178     p.setPen(Qt::NoPen);
0179     p.setBrush(palette().brush(QPalette::Window));
0180     p.drawRect(rect());
0181 
0182     if (!m_dates) {
0183         p.restore();
0184         return;
0185     }
0186 
0187     // Draw the area with histograms
0188     QRect barArea = barAreaGeometry();
0189 
0190     p.setPen(palette().color(QPalette::Dark));
0191     p.setBrush(palette().brush(QPalette::Base));
0192     p.drawRect(barArea);
0193     p.restore();
0194 
0195     // shift the date bar by m_currentUnit units
0196     m_currentHandler->init(dateForUnit(-m_currentUnit, m_currentDate));
0197 
0198     int right;
0199     drawResolutionIndicator(p, &right);
0200     QRect rect = dateAreaGeometry();
0201     rect.setRight(right);
0202     rect.setLeft(rect.left() + BUTTON_WIDTH + 2);
0203 
0204     drawTickMarks(p, rect);
0205     drawHistograms(p);
0206     drawFocusRectangle(p);
0207     updateArrowState();
0208     repaint();
0209 }
0210 
0211 void DateBar::DateBarWidget::resizeEvent(QResizeEvent *event)
0212 {
0213     placeAndSizeButtons();
0214     m_buffer = QPixmap(event->size());
0215     m_currentUnit = numberOfUnits() / 2;
0216     redraw();
0217 }
0218 
0219 void DateBar::DateBarWidget::drawTickMarks(QPainter &p, const QRect &textRect)
0220 {
0221     QRect rect = tickMarkGeometry();
0222     p.save();
0223     p.setPen(QPen(palette().color(QPalette::Text), 1));
0224 
0225     QFont f(font());
0226     QFontMetrics fm(f);
0227     int fontHeight = fm.height();
0228     int unit = 0;
0229     QRect clip = rect;
0230     clip.setHeight(rect.height() + 2 + fontHeight);
0231     clip.setLeft(clip.left() + 2);
0232     clip.setRight(clip.right() - 2);
0233     p.setClipRect(clip);
0234 
0235     for (int x = rect.x(); x < rect.right(); x += m_barWidth, unit += 1) {
0236         // draw selection indication
0237         p.save();
0238         p.setPen(Qt::NoPen);
0239         p.setBrush(palette().brush(QPalette::Highlight));
0240         Utilities::FastDateTime date = dateForUnit(unit);
0241         if (isUnitSelected(unit))
0242             p.drawRect(QRect(x, rect.top(), m_barWidth, rect.height()));
0243         p.restore();
0244 
0245         // draw tickmarks
0246         int h = rect.height();
0247         if (m_currentHandler->isMajorUnit(unit)) {
0248             QString text = m_currentHandler->text(unit);
0249             int w = fm.horizontalAdvance(text);
0250             p.setFont(f);
0251             if (textRect.right() > x + w / 2 && textRect.left() < x - w / 2)
0252                 p.drawText(x - w / 2, textRect.top(), w, fontHeight, Qt::TextSingleLine, text);
0253         } else if (m_currentHandler->isMidUnit(unit))
0254             h = (int)(2.0 / 3 * rect.height());
0255         else
0256             h = (int)(1.0 / 3 * rect.height());
0257 
0258         p.drawLine(x, rect.top(), x, rect.top() + h);
0259     }
0260 
0261     p.restore();
0262 }
0263 
0264 void DateBar::DateBarWidget::setViewType(ViewType tp, bool redrawNow)
0265 {
0266     setViewHandlerForType(tp);
0267     if (hasSelection()) {
0268         centerDateRange(m_selectionHandler->min(), m_selectionHandler->max());
0269     }
0270     if (redrawNow)
0271         redraw();
0272     m_tp = tp;
0273 }
0274 
0275 void DateBar::DateBarWidget::setViewHandlerForType(ViewType tp)
0276 {
0277     switch (tp) {
0278     case DecadeView:
0279         m_currentHandler = &m_decadeViewHandler;
0280         break;
0281     case YearView:
0282         m_currentHandler = &m_yearViewHandler;
0283         break;
0284     case MonthView:
0285         m_currentHandler = &m_monthViewHandler;
0286         break;
0287     case WeekView:
0288         m_currentHandler = &m_weekViewHandler;
0289         break;
0290     case DayView:
0291         m_currentHandler = &m_dayViewHandler;
0292         break;
0293     case HourView:
0294         m_currentHandler = &m_hourViewHandler;
0295         break;
0296     case TenMinuteView:
0297         m_currentHandler = &m_tenMinuteViewHandler;
0298         break;
0299     case MinuteView:
0300         m_currentHandler = &m_minuteViewHandler;
0301         break;
0302     }
0303 }
0304 
0305 void DateBar::DateBarWidget::setDate(const Utilities::FastDateTime &date)
0306 {
0307     m_currentDate = date;
0308     if (hasSelection()) {
0309         if (currentSelection().start() > m_currentDate)
0310             m_currentDate = currentSelection().start();
0311         if (currentSelection().end() < m_currentDate)
0312             m_currentDate = currentSelection().end();
0313     }
0314 
0315     if (unitForDate(m_currentDate) != -1)
0316         m_currentUnit = unitForDate(m_currentDate);
0317 
0318     redraw();
0319 }
0320 
0321 void DateBar::DateBarWidget::setImageCollection(const DB::ImageInfoList &images)
0322 {
0323     setImageDateCollection(QExplicitlySharedDataPointer<DB::ImageDateCollection>(
0324         new DB::ImageDateCollection(images)));
0325 }
0326 
0327 void DateBar::DateBarWidget::setImageDateCollection(const QExplicitlySharedDataPointer<DB::ImageDateCollection> &dates)
0328 {
0329     m_dates = dates;
0330     if (m_doAutomaticRangeAdjustment && m_dates && !m_dates->lowerLimit().isNull()) {
0331         const Utilities::FastDateTime start = m_dates->lowerLimit();
0332         Utilities::FastDateTime end = m_dates->upperLimit();
0333         if (end.isNull())
0334             end = Utilities::FastDateTime::currentDateTime();
0335 
0336         m_currentDate = start;
0337         m_currentUnit = 0;
0338         // select suitable timeframe:
0339         setViewType(MinuteView, false);
0340         m_currentHandler->init(start);
0341         while (canZoomOut() && end > dateForUnit(numberOfUnits())) {
0342             m_tp = (ViewType)(m_tp - 1);
0343             setViewHandlerForType(m_tp);
0344             m_currentHandler->init(start);
0345         }
0346         // center range in datebar:
0347         int units = unitForDate(end);
0348         if (units != -1) {
0349             m_currentUnit = (numberOfUnits() - units) / 2;
0350         }
0351     }
0352     redraw();
0353 }
0354 
0355 void DateBar::DateBarWidget::drawHistograms(QPainter &p)
0356 {
0357     QRect rect = barAreaGeometry();
0358     p.save();
0359     p.setClipping(true);
0360     p.setClipRect(rect);
0361     p.setPen(Qt::NoPen);
0362 
0363     // determine maximum image count within visible units
0364     int max = 0;
0365     for (int unit = 0; unit <= numberOfUnits(); unit++) {
0366         DB::ImageCount count = m_dates->count(rangeForUnit(unit));
0367         int cnt = count.mp_exact;
0368         if (m_includeFuzzyCounts)
0369             cnt += count.mp_rangeMatch;
0370         max = qMax(max, cnt);
0371     }
0372 
0373     // Calculate the font size for the largest number.
0374     QFont f = font();
0375     bool fontFound = false;
0376     for (int i = f.pointSize(); i >= 6; i -= 2) {
0377         f.setPointSize(i);
0378         QFontMetrics fontMetrics(f);
0379         int w = fontMetrics.horizontalAdvance(QString::number(max));
0380         if (w < rect.height() - 6) {
0381             p.setFont(f);
0382             fontFound = true;
0383             break;
0384         }
0385     }
0386 
0387     int unit = 0;
0388     const int minUnit = unitForDate(m_dates->lowerLimit()); // first non-empty unit
0389     const int maxUnit = (unitForDate(m_dates->upperLimit()) != -1) ? unitForDate(m_dates->upperLimit()) : numberOfUnits(); // last non-empty unit
0390     const bool linearScale = Settings::SettingsData::instance()->histogramUseLinearScale();
0391     for (int x = rect.x(); x + m_barWidth < rect.right(); x += m_barWidth, unit += 1) {
0392         if (unit < minUnit || unit > maxUnit) {
0393             Qt::BrushStyle style = Qt::SolidPattern;
0394 
0395             p.setBrush(QBrush(Qt::lightGray, style));
0396             p.drawRect(x, 1, m_barWidth, rect.height() + 2);
0397             continue;
0398         }
0399         const auto unitRange = rangeForUnit(unit);
0400         const DB::ImageCount count = m_dates->count(unitRange);
0401         int exactPx = 0;
0402         int rangePx = 0;
0403         if (max != 0) {
0404             double exactScaled;
0405             double rangeScaled;
0406             if (linearScale) {
0407                 exactScaled = (double)count.mp_exact / max;
0408                 rangeScaled = (double)count.mp_rangeMatch / max;
0409             } else {
0410                 exactScaled = sqrt(count.mp_exact) / sqrt(max);
0411                 rangeScaled = sqrt(count.mp_rangeMatch) / sqrt(max);
0412             }
0413             // convert to pixels:
0414             exactPx = (int)((double)(rect.height() - 2) * exactScaled);
0415             if (m_includeFuzzyCounts)
0416                 rangePx = (int)((double)(rect.height() - 2) * rangeScaled);
0417         }
0418 
0419         Qt::BrushStyle style = Qt::SolidPattern;
0420         if (!isUnitSelected(unit) && hasSelection())
0421             style = Qt::Dense5Pattern;
0422 
0423         p.setBrush(QBrush(Qt::yellow, style));
0424         p.drawRect(x + 1, rect.bottom() - rangePx, m_barWidth - 2, rangePx);
0425         p.setBrush(QBrush(Qt::green, style));
0426         p.drawRect(x + 1, rect.bottom() - rangePx - exactPx, m_barWidth - 2, exactPx);
0427 
0428         // Draw the numbers, if they fit.
0429         if (fontFound) {
0430             int tot = count.mp_exact;
0431             if (m_includeFuzzyCounts)
0432                 tot += count.mp_rangeMatch;
0433             p.save();
0434             p.translate(x + m_barWidth - 3, rect.bottom() - 2);
0435             p.rotate(-90);
0436             QFontMetrics fontMetrics(f);
0437             int w = fontMetrics.horizontalAdvance(QString::number(tot));
0438             if (w < exactPx + rangePx - 2) {
0439                 // don't use a palette color here - otherwise it may have bad contrast with green and yellow:
0440                 p.setPen(Qt::black);
0441                 p.drawText(0, 0, QString::number(tot));
0442             }
0443             p.restore();
0444         }
0445     }
0446 
0447     p.restore();
0448 }
0449 
0450 void DateBar::DateBarWidget::scrollLeft()
0451 {
0452     int scrollAmount = -SCROLL_AMOUNT;
0453     if (QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
0454         scrollAmount *= SCROLL_ACCELERATION;
0455     scroll(scrollAmount);
0456 }
0457 
0458 void DateBar::DateBarWidget::scrollRight()
0459 {
0460     int scrollAmount = SCROLL_AMOUNT;
0461     if (QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier))
0462         scrollAmount *= SCROLL_ACCELERATION;
0463     scroll(scrollAmount);
0464 }
0465 
0466 void DateBar::DateBarWidget::scroll(int units)
0467 {
0468     if ((m_dates->lowerLimit() <= dateForUnit(0) && units > 0)
0469         || (m_dates->upperLimit() > dateForUnit(numberOfUnits()) && units < 0)) {
0470         return;
0471     }
0472 
0473     m_currentDate = dateForUnit(units, m_currentDate);
0474     redraw();
0475     Q_EMIT dateSelected(currentDateRange(), includeFuzzyCounts());
0476 }
0477 
0478 void DateBar::DateBarWidget::drawFocusRectangle(QPainter &p)
0479 {
0480     QRect rect = barAreaGeometry();
0481     p.save();
0482     int x = rect.left() + m_currentUnit * m_barWidth;
0483     QRect inner(QPoint(x - 1, BORDER_ABOVE_HISTOGRAM),
0484                 QPoint(x + m_barWidth, BORDER_ABOVE_HISTOGRAM + m_barHeight - 1));
0485 
0486     p.setPen(QPen(palette().color(QPalette::Dark), 1));
0487 
0488     // Inner rect
0489     p.drawRect(inner);
0490     QRect outer = inner;
0491     outer.adjust(-2, -2, 2, 2);
0492 
0493     // Outer rect
0494     QRegion region = outer;
0495     region -= inner;
0496     p.setClipping(true);
0497     p.setClipRegion(region);
0498 
0499     QColor col = palette().highlight().color();
0500     if (!hasFocus())
0501         col = palette().window().color();
0502 
0503     p.setBrush(col);
0504     p.setPen(col);
0505     p.drawRect(outer);
0506 
0507     // Shadow below
0508     QRect shadow = outer;
0509     shadow.adjust(-1, -1, 1, 1);
0510     region = shadow;
0511     region -= outer;
0512     p.setPen(palette().color(QPalette::Shadow));
0513     p.setClipRegion(region);
0514     p.drawRect(shadow);
0515 
0516     // Light above
0517     QRect hide = shadow;
0518     hide.translate(1, 1);
0519     region = shadow;
0520     region -= hide;
0521     p.setPen(palette().color(QPalette::Light));
0522     p.setClipRegion(region);
0523     p.drawRect(shadow);
0524 
0525     p.restore();
0526 }
0527 
0528 void DateBar::DateBarWidget::zoomIn()
0529 {
0530     if (!canZoomIn())
0531         return;
0532     zoom(+1);
0533 }
0534 
0535 void DateBar::DateBarWidget::zoomOut()
0536 {
0537     if (!canZoomOut())
0538         return;
0539     zoom(-1);
0540 }
0541 
0542 void DateBar::DateBarWidget::zoom(int steps)
0543 {
0544     ViewType tp = (ViewType)(m_tp + steps);
0545     const bool couldZoomIn = canZoomIn();
0546     const bool couldZoomOut = canZoomOut();
0547     setViewType(tp);
0548     if (couldZoomIn != canZoomIn())
0549         Q_EMIT zoomInEnabled(canZoomIn());
0550     if (couldZoomOut != canZoomOut())
0551         Q_EMIT zoomOutEnabled(canZoomOut());
0552 }
0553 
0554 void DateBar::DateBarWidget::mousePressEvent(QMouseEvent *event)
0555 {
0556     if ((event->button() & (Qt::MiddleButton | Qt::LeftButton)) == 0 || event->x() > barAreaGeometry().right() || event->x() < barAreaGeometry().left())
0557         return;
0558 
0559     if ((event->button() & Qt::MiddleButton)
0560         || event->modifiers() & Qt::ControlModifier) {
0561         m_currentMouseHandler = m_barDragHandler;
0562     } else {
0563         bool onBar = event->y() > barAreaGeometry().bottom();
0564         if (onBar)
0565             m_currentMouseHandler = m_selectionHandler;
0566         else {
0567             m_currentMouseHandler = m_focusItemDragHandler;
0568         }
0569     }
0570     m_currentMouseHandler->mousePressEvent(event->x());
0571     Q_EMIT dateSelected(currentDateRange(), includeFuzzyCounts());
0572     showStatusBarTip(event->pos());
0573     redraw();
0574 }
0575 
0576 void DateBar::DateBarWidget::mouseReleaseEvent(QMouseEvent *)
0577 {
0578     if (m_currentMouseHandler == nullptr)
0579         return;
0580 
0581     m_currentMouseHandler->endAutoScroll();
0582     m_currentMouseHandler->mouseReleaseEvent();
0583     m_currentMouseHandler = nullptr;
0584 }
0585 
0586 void DateBar::DateBarWidget::mouseMoveEvent(QMouseEvent *event)
0587 {
0588     showStatusBarTip(event->pos());
0589 
0590     if (m_currentMouseHandler == nullptr)
0591         return;
0592 
0593     if ((event->buttons() & (Qt::MiddleButton | Qt::LeftButton)) == 0)
0594         return;
0595 
0596     m_currentMouseHandler->endAutoScroll();
0597     m_currentMouseHandler->mouseMoveEvent(event->pos().x());
0598 }
0599 
0600 QRect DateBar::DateBarWidget::barAreaGeometry() const
0601 {
0602     QRect barArea;
0603     barArea.setTopLeft(QPoint(BORDER_AROUND_WIDGET, BORDER_ABOVE_HISTOGRAM));
0604     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
0605     barArea.setHeight(m_barHeight);
0606     return barArea;
0607 }
0608 
0609 int DateBar::DateBarWidget::numberOfUnits() const
0610 {
0611     return barAreaGeometry().width() / m_barWidth - 1;
0612 }
0613 
0614 void DateBar::DateBarWidget::setHistogramBarSize(const QSize &size)
0615 {
0616     m_barWidth = size.width();
0617     m_barHeight = size.height();
0618     m_currentUnit = numberOfUnits() / 2;
0619     Q_ASSERT(parentWidget());
0620     updateGeometry();
0621     Q_ASSERT(parentWidget());
0622     placeAndSizeButtons();
0623     redraw();
0624 }
0625 
0626 void DateBar::DateBarWidget::setIncludeFuzzyCounts(bool b)
0627 {
0628     m_includeFuzzyCounts = b;
0629     redraw();
0630     if (hasSelection())
0631         emitRangeSelection(m_selectionHandler->dateRange());
0632 
0633     Q_EMIT dateSelected(currentDateRange(), includeFuzzyCounts());
0634 }
0635 
0636 DB::ImageDate DateBar::DateBarWidget::rangeAt(const QPoint &p)
0637 {
0638     int unit = (p.x() - barAreaGeometry().x()) / m_barWidth;
0639     return rangeForUnit(unit);
0640 }
0641 
0642 DB::ImageDate DateBar::DateBarWidget::rangeForUnit(int unit)
0643 {
0644     Utilities::FastDateTime toUnit = dateForUnit(unit + 1).addSecs(-1);
0645     return DB::ImageDate(dateForUnit(unit), toUnit);
0646 }
0647 
0648 bool DateBar::DateBarWidget::includeFuzzyCounts() const
0649 {
0650     return m_includeFuzzyCounts;
0651 }
0652 
0653 KActionCollection *DateBar::DateBarWidget::actions()
0654 {
0655     return m_actionCollection;
0656 }
0657 
0658 bool DateBar::DateBarWidget::canZoomIn() const
0659 {
0660     return (m_tp != MinuteView);
0661 }
0662 
0663 bool DateBar::DateBarWidget::canZoomOut() const
0664 {
0665     return (m_tp != DecadeView);
0666 }
0667 
0668 void DateBar::DateBarWidget::centerDateRange(const DB::ImageDate &range)
0669 {
0670     centerDateRange(range.start(), range.end());
0671 }
0672 
0673 void DateBar::DateBarWidget::centerDateRange(const Utilities::FastDateTime &min, const Utilities::FastDateTime &max)
0674 {
0675     m_currentDate = min;
0676     // update reference frame for unitForDate:
0677     m_currentHandler->init(m_currentDate);
0678     const int maxUnit = unitForDate(max);
0679     if (maxUnit != -1) {
0680         // center selection if it fits within the date bar
0681         const int paddingUnits = (numberOfUnits() - maxUnit) / 2;
0682         m_currentUnit = paddingUnits;
0683     }
0684 }
0685 
0686 void DateBar::DateBarWidget::contextMenuEvent(QContextMenuEvent *event)
0687 {
0688     if (!m_contextMenu) {
0689         m_contextMenu = new QMenu(this);
0690         QAction *action = new QAction(i18n("Show Ranges"), this);
0691         action->setCheckable(true);
0692         m_contextMenu->addAction(action);
0693         action->setChecked(m_includeFuzzyCounts);
0694         connect(action, &QAction::toggled, this, &DateBarWidget::setIncludeFuzzyCounts);
0695 
0696         action = new QAction(i18n("Show Resolution Indicator"), this);
0697         action->setCheckable(true);
0698         m_contextMenu->addAction(action);
0699         action->setChecked(m_showResolutionIndicator);
0700         connect(action, &QAction::toggled, this, &DateBarWidget::setShowResolutionIndicator);
0701     }
0702 
0703     m_contextMenu->exec(event->globalPos());
0704     event->setAccepted(true);
0705 }
0706 
0707 QRect DateBar::DateBarWidget::tickMarkGeometry() const
0708 {
0709     QRect rect;
0710     rect.setTopLeft(barAreaGeometry().bottomLeft());
0711     rect.setWidth(barAreaGeometry().width());
0712     rect.setHeight(12);
0713     return rect;
0714 }
0715 
0716 void DateBar::DateBarWidget::drawResolutionIndicator(QPainter &p, int *leftEdge)
0717 {
0718     QRect rect = dateAreaGeometry();
0719 
0720     // For real small bars, we do not want to show the resolution.
0721     if (rect.width() < 400 || !m_showResolutionIndicator) {
0722         *leftEdge = rect.right();
0723         return;
0724     }
0725 
0726     QString text = m_currentHandler->unitText();
0727     QFontMetrics fontMetrics(font());
0728     int textWidth = fontMetrics.horizontalAdvance(text);
0729     int height = fontMetrics.height();
0730 
0731     int endUnitPos = rect.right() - textWidth - ARROW_LENGTH - 3;
0732     // Round to nearest unit mark
0733     endUnitPos = ((endUnitPos - rect.left()) / m_barWidth) * m_barWidth + rect.left();
0734     int startUnitPos = endUnitPos - m_barWidth;
0735     int midLine = rect.top() + height / 2;
0736 
0737     p.save();
0738     p.setPen(palette().windowText().color());
0739 
0740     // draw arrows
0741     drawArrow(p, QPoint(startUnitPos - ARROW_LENGTH, midLine), QPoint(startUnitPos, midLine));
0742     drawArrow(p, QPoint(endUnitPos + ARROW_LENGTH, midLine), QPoint(endUnitPos, midLine));
0743     p.drawLine(startUnitPos, rect.top(), startUnitPos, rect.top() + height);
0744     p.drawLine(endUnitPos, rect.top(), endUnitPos, rect.top() + height);
0745 
0746     // draw text
0747     QFontMetrics fm(font());
0748     p.drawText(endUnitPos + ARROW_LENGTH + 3, rect.top(), fm.horizontalAdvance(text), fm.height(), Qt::TextSingleLine, text);
0749     p.restore();
0750 
0751     *leftEdge = startUnitPos - ARROW_LENGTH - 3;
0752 }
0753 
0754 QRect DateBar::DateBarWidget::dateAreaGeometry() const
0755 {
0756     QRect rect = tickMarkGeometry();
0757     rect.setTop(rect.bottom() + 2);
0758     rect.setHeight(QFontMetrics(font()).height());
0759     return rect;
0760 }
0761 
0762 void DateBar::DateBarWidget::drawArrow(QPainter &p, const QPoint &start, const QPoint &end)
0763 {
0764     p.save();
0765     p.drawLine(start, end);
0766 
0767     QPoint diff = QPoint(end.x() - start.x(), end.y() - start.y());
0768     double dx = diff.x();
0769     double dy = diff.y();
0770 
0771     if (dx != 0 || dy != 0) {
0772         if (dy < 0)
0773             dx = -dx;
0774         double angle = acos(dx / sqrt(dx * dx + dy * dy)) * 180. / M_PI;
0775         if (dy < 0)
0776             angle += 180.;
0777 
0778         // angle is now the angle of the line.
0779 
0780         angle = angle + 180 - 15;
0781         p.translate(end.x(), end.y());
0782         p.rotate(angle);
0783         p.drawLine(QPoint(0, 0), QPoint(10, 0));
0784 
0785         p.rotate(30);
0786         p.drawLine(QPoint(0, 0), QPoint(10, 0));
0787     }
0788 
0789     p.restore();
0790 }
0791 
0792 void DateBar::DateBarWidget::setShowResolutionIndicator(bool b)
0793 {
0794     m_showResolutionIndicator = b;
0795     redraw();
0796 }
0797 
0798 void DateBar::DateBarWidget::setAutomaticRangeAdjustment(bool b)
0799 {
0800     m_doAutomaticRangeAdjustment = b;
0801 }
0802 
0803 void DateBar::DateBarWidget::updateArrowState()
0804 {
0805     m_leftArrow->setEnabled(m_dates->lowerLimit() <= dateForUnit(0));
0806     m_rightArrow->setEnabled(m_dates->upperLimit() > dateForUnit(numberOfUnits()));
0807 }
0808 
0809 DB::ImageDate DateBar::DateBarWidget::currentDateRange() const
0810 {
0811     return DB::ImageDate(dateForUnit(m_currentUnit), dateForUnit(m_currentUnit + 1));
0812 }
0813 
0814 void DateBar::DateBarWidget::showStatusBarTip(const QPoint &pos)
0815 {
0816     DB::ImageDate range = rangeAt(pos);
0817     DB::ImageCount count = m_dates->count(range);
0818 
0819     QString cnt;
0820     if (count.mp_rangeMatch != 0 && includeFuzzyCounts())
0821         cnt = i18ncp("@info:status images that fall in the given date range", "1 exact", "%1 exact", count.mp_exact)
0822             + i18ncp("@info:status additional images captured in a date range that overlaps with the given date range,", " + 1 range", " + %1 ranges", count.mp_rangeMatch)
0823             + i18ncp("@info:status total image count", " = 1 total", " = %1 total", count.mp_exact + count.mp_rangeMatch);
0824     else
0825         cnt = i18ncp("@info:status image count", "%1 image/video", "%1 images/videos", count.mp_exact);
0826 
0827     QString res = i18nc("@info:status Time range vs. image count (e.g. 'Jun 2012 | 4 images/videos').", "%1 | %2", range.toString(), cnt);
0828 
0829     static QString lastTip;
0830     if (lastTip != res)
0831         Q_EMIT toolTipInfo(res);
0832     lastTip = res;
0833 }
0834 
0835 void DateBar::DateBarWidget::placeAndSizeButtons()
0836 {
0837     m_zoomIn->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
0838     m_zoomOut->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
0839     m_rightArrow->setFixedSize(QSize(BUTTON_WIDTH, m_barHeight));
0840     m_leftArrow->setFixedSize(QSize(BUTTON_WIDTH, m_barHeight));
0841 
0842     m_rightArrow->move(size().width() - m_rightArrow->width() - BORDER_AROUND_WIDGET, BORDER_ABOVE_HISTOGRAM);
0843     m_leftArrow->move(m_rightArrow->pos().x() - m_leftArrow->width() - 2, BORDER_ABOVE_HISTOGRAM);
0844 
0845     int x = m_leftArrow->pos().x();
0846     int y = height() - BUTTON_WIDTH;
0847     m_zoomOut->move(x, y);
0848 
0849     x = m_rightArrow->pos().x();
0850     m_zoomIn->move(x, y);
0851 
0852     m_cancelSelection->setFixedSize(BUTTON_WIDTH, BUTTON_WIDTH);
0853     m_cancelSelection->move(0, y);
0854 }
0855 
0856 void DateBar::DateBarWidget::keyPressEvent(QKeyEvent *event)
0857 {
0858     int offset = 0;
0859 
0860     switch (event->key()) {
0861     case Qt::Key_Left:
0862         offset = -1;
0863         break;
0864     case Qt::Key_Right:
0865         offset = 1;
0866         break;
0867     case Qt::Key_PageDown:
0868         offset = -10;
0869         break;
0870     case Qt::Key_PageUp:
0871         offset = 10;
0872         break;
0873     case Qt::Key_Plus:
0874         if (canZoomIn())
0875             zoom(1);
0876         return;
0877     case Qt::Key_Minus:
0878         if (canZoomOut())
0879             zoom(-1);
0880         return;
0881     case Qt::Key_Escape:
0882         clearSelection();
0883         return;
0884     default:
0885         return;
0886     }
0887 
0888     const bool selectionMode = event->modifiers() & Qt::ShiftModifier;
0889 
0890     Utilities::FastDateTime newDate = dateForUnit(offset, m_currentDate);
0891     if ((offset < 0 && newDate >= m_dates->lowerLimit()) || (offset > 0 && newDate <= m_dates->upperLimit())) {
0892         m_currentDate = newDate;
0893         m_currentUnit += offset;
0894         if (m_currentUnit < 0)
0895             m_currentUnit = 0;
0896         if (m_currentUnit > numberOfUnits())
0897             m_currentUnit = numberOfUnits();
0898 
0899         if (selectionMode) {
0900             m_selectionHandler->setOrExtendSelection(newDate);
0901         } else if (!currentSelection().includes(m_currentDate))
0902             clearSelection();
0903     }
0904     redraw();
0905     Q_EMIT dateSelected(currentDateRange(), includeFuzzyCounts());
0906 }
0907 
0908 void DateBar::DateBarWidget::focusInEvent(QFocusEvent *)
0909 {
0910     redraw();
0911 }
0912 
0913 void DateBar::DateBarWidget::focusOutEvent(QFocusEvent *)
0914 {
0915     redraw();
0916 }
0917 
0918 int DateBar::DateBarWidget::unitAtPos(int x) const
0919 {
0920     const bool invalidOffset_before = x - barAreaGeometry().left() < 0;
0921     const bool invalidOffset_after = x - barAreaGeometry().left() > barAreaGeometry().width();
0922     if (invalidOffset_before || invalidOffset_after) {
0923         return -1;
0924     }
0925 
0926     return (x - barAreaGeometry().left()) / m_barWidth;
0927 }
0928 
0929 Utilities::FastDateTime DateBar::DateBarWidget::dateForUnit(int unit, const Utilities::FastDateTime &offset) const
0930 {
0931     return m_currentHandler->date(unit, offset);
0932 }
0933 
0934 bool DateBar::DateBarWidget::isUnitSelected(int unit) const
0935 {
0936     Utilities::FastDateTime minDate = m_selectionHandler->min();
0937     Utilities::FastDateTime maxDate = m_selectionHandler->max();
0938     Utilities::FastDateTime date = dateForUnit(unit);
0939     return (minDate <= date && date < maxDate && !minDate.isNull());
0940 }
0941 
0942 bool DateBar::DateBarWidget::hasSelection() const
0943 {
0944     return !m_selectionHandler->min().isNull();
0945 }
0946 
0947 DB::ImageDate DateBar::DateBarWidget::currentSelection() const
0948 {
0949     return DB::ImageDate(m_selectionHandler->min(), m_selectionHandler->max());
0950 }
0951 
0952 void DateBar::DateBarWidget::clearSelection()
0953 {
0954     if (m_selectionHandler->hasSelection()) {
0955         m_selectionHandler->clearSelection();
0956         Q_EMIT dateRangeCleared();
0957         Q_EMIT dateRangeSelected(false);
0958         redraw();
0959     }
0960 }
0961 
0962 void DateBar::DateBarWidget::emitRangeSelection(const DB::ImageDate &range)
0963 {
0964     Q_EMIT dateRangeChange(range);
0965     Q_EMIT dateRangeSelected(true);
0966 }
0967 
0968 int DateBar::DateBarWidget::unitForDate(const Utilities::FastDateTime &date) const
0969 {
0970     for (int unit = 0; unit < numberOfUnits(); ++unit) {
0971         if (m_currentHandler->date(unit) <= date && date < m_currentHandler->date(unit + 1))
0972             return unit;
0973     }
0974     return -1;
0975 }
0976 
0977 void DateBar::DateBarWidget::emitDateSelected()
0978 {
0979     Q_EMIT dateSelected(currentDateRange(), includeFuzzyCounts());
0980 }
0981 
0982 void DateBar::DateBarWidget::wheelEvent(QWheelEvent *e)
0983 {
0984     const auto angleDelta = e->angleDelta();
0985     const bool isHorizontal = (qAbs(angleDelta.x()) > qAbs(angleDelta.y()));
0986     const int delta = isHorizontal ? angleDelta.x() : angleDelta.y();
0987     if (e->modifiers() & Qt::ControlModifier) {
0988         if (delta > 0)
0989             zoomIn();
0990         else
0991             zoomOut();
0992         return;
0993     }
0994     int scrollAmount = delta > 0 ? SCROLL_AMOUNT : -SCROLL_AMOUNT;
0995     if (e->modifiers() & Qt::ShiftModifier)
0996         scrollAmount *= SCROLL_ACCELERATION;
0997     scroll(scrollAmount);
0998 }
0999 
1000 #include "moc_DateBarWidget.cpp"
1001 
1002 // vi:expandtab:tabstop=4 shiftwidth=4: