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: