File indexing completed on 2025-01-19 03:50:44
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2009-12-05 0007 * Description : Side Bar Widget for the time-line view. 0008 * 0009 * SPDX-FileCopyrightText: 2009-2010 by Johannes Wienke <languitar at semipol dot de> 0010 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2012 by Andi Clemens <andi dot clemens at gmail dot com> 0012 * SPDX-FileCopyrightText: 2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com> 0013 * SPDX-FileCopyrightText: 2010 by Aditya Bhatt <adityabhatt1991 at gmail dot com> 0014 * 0015 * SPDX-License-Identifier: GPL-2.0-or-later 0016 * 0017 * ============================================================ */ 0018 0019 #include "timelinesidebarwidget.h" 0020 0021 // Qt includes 0022 0023 #include <QButtonGroup> 0024 #include <QLabel> 0025 #include <QScrollBar> 0026 #include <QTimer> 0027 #include <QToolButton> 0028 #include <QRadioButton> 0029 #include <QApplication> 0030 #include <QStyle> 0031 #include <QComboBox> 0032 #include <QPushButton> 0033 #include <QLineEdit> 0034 #include <QIcon> 0035 0036 // KDE includes 0037 0038 #include <kconfiggroup.h> 0039 #include <klocalizedstring.h> 0040 0041 // Local includes 0042 0043 #include "digikam_debug.h" 0044 #include "albumpointer.h" 0045 #include "albummodificationhelper.h" 0046 #include "albumselectiontreeview.h" 0047 #include "applicationsettings.h" 0048 #include "coredbsearchxml.h" 0049 #include "datefolderview.h" 0050 #include "dexpanderbox.h" 0051 #include "editablesearchtreeview.h" 0052 #include "timelinewidget.h" 0053 #include "searchfolderview.h" 0054 #include "searchtabheader.h" 0055 #include "searchtextbardb.h" 0056 #include "dtextedit.h" 0057 0058 namespace Digikam 0059 { 0060 0061 class Q_DECL_HIDDEN TimelineSideBarWidget::Private 0062 { 0063 public: 0064 0065 explicit Private() 0066 : scaleBG (nullptr), 0067 cursorCountLabel (nullptr), 0068 scrollBar (nullptr), 0069 timer (nullptr), 0070 resetButton (nullptr), 0071 saveButton (nullptr), 0072 timeUnitCB (nullptr), 0073 nameEdit (nullptr), 0074 cursorDateLabel (nullptr), 0075 searchDateBar (nullptr), 0076 timeLineFolderView (nullptr), 0077 timeLineWidget (nullptr), 0078 searchModificationHelper(nullptr) 0079 { 0080 } 0081 0082 static const QString configHistogramTimeUnitEntry; 0083 static const QString configHistogramScaleEntry; 0084 static const QString configCursorPositionEntry; 0085 0086 QButtonGroup* scaleBG; 0087 QLabel* cursorCountLabel; 0088 QScrollBar* scrollBar; 0089 QTimer* timer; 0090 QToolButton* resetButton; 0091 QToolButton* saveButton; 0092 0093 QComboBox* timeUnitCB; 0094 DTextEdit* nameEdit; 0095 DAdjustableLabel* cursorDateLabel; 0096 0097 SearchTextBarDb* searchDateBar; 0098 EditableSearchTreeView* timeLineFolderView; 0099 TimeLineWidget* timeLineWidget; 0100 0101 SearchModificationHelper* searchModificationHelper; 0102 0103 AlbumPointer<SAlbum> currentTimelineSearch; 0104 }; 0105 0106 const QString TimelineSideBarWidget::Private::configHistogramTimeUnitEntry(QLatin1String("Histogram TimeUnit")); 0107 const QString TimelineSideBarWidget::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale")); 0108 const QString TimelineSideBarWidget::Private::configCursorPositionEntry(QLatin1String("Cursor Position")); 0109 0110 // -------------------------------------------------------- 0111 0112 TimelineSideBarWidget::TimelineSideBarWidget(QWidget* const parent, 0113 SearchModel* const searchModel, 0114 SearchModificationHelper* const searchModificationHelper) 0115 : SidebarWidget(parent), 0116 d (new Private) 0117 { 0118 setObjectName(QLatin1String("TimeLine Sidebar")); 0119 setProperty("Shortcut", QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_F5)); 0120 0121 d->searchModificationHelper = searchModificationHelper; 0122 d->timer = new QTimer(this); 0123 setAttribute(Qt::WA_DeleteOnClose); 0124 0125 const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0126 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0127 0128 QVBoxLayout* const vlay = new QVBoxLayout(this); 0129 QFrame* const panel = new QFrame(this); 0130 panel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); 0131 panel->setLineWidth(1); 0132 0133 QGridLayout* const grid = new QGridLayout(panel); 0134 0135 // --------------------------------------------------------------- 0136 0137 QWidget* const hbox1 = new QWidget(panel); 0138 QHBoxLayout* const hlay = new QHBoxLayout(hbox1); 0139 0140 QLabel* const label1 = new QLabel(i18n("Time Unit:"), hbox1); 0141 d->timeUnitCB = new QComboBox(hbox1); 0142 d->timeUnitCB->addItem(i18n("Day"), TimeLineWidget::Day); 0143 d->timeUnitCB->addItem(i18n("Week"), TimeLineWidget::Week); 0144 d->timeUnitCB->addItem(i18n("Month"), TimeLineWidget::Month); 0145 d->timeUnitCB->addItem(i18n("Year"), TimeLineWidget::Year); 0146 d->timeUnitCB->setCurrentIndex((int)TimeLineWidget::Month); 0147 d->timeUnitCB->setFocusPolicy(Qt::NoFocus); 0148 d->timeUnitCB->setWhatsThis(i18n("<p>Select the histogram time unit.</p>" 0149 "<p>You can change the graph decade to zoom in or zoom out over time.</p>")); 0150 0151 QWidget* const scaleBox = new QWidget(hbox1); 0152 QHBoxLayout* const hlay2 = new QHBoxLayout(scaleBox); 0153 d->scaleBG = new QButtonGroup(scaleBox); 0154 d->scaleBG->setExclusive(true); 0155 scaleBox->setWhatsThis( i18n("<p>Select the histogram scale.</p>" 0156 "<p>If the date's maximal counts are small, you can use the linear scale.</p>" 0157 "<p>Logarithmic scale can be used when the maximal counts are big; " 0158 "if it is used, all values (small and large) will be visible on the " 0159 "graph.</p>")); 0160 0161 QToolButton* const linHistoButton = new QToolButton(scaleBox); 0162 linHistoButton->setToolTip(i18nc("@info: timeline sidebar", "Linear")); 0163 linHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-linear"))); 0164 linHistoButton->setCheckable(true); 0165 d->scaleBG->addButton(linHistoButton, TimeLineWidget::LinScale); 0166 0167 QToolButton* const logHistoButton = new QToolButton(scaleBox); 0168 logHistoButton->setToolTip(i18nc("@info: timeline sidebar", "Logarithmic")); 0169 logHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-logarithmic"))); 0170 logHistoButton->setCheckable(true); 0171 d->scaleBG->addButton(logHistoButton, TimeLineWidget::LogScale); 0172 0173 hlay2->setContentsMargins(QMargins()); 0174 hlay2->setSpacing(0); 0175 hlay2->addWidget(linHistoButton); 0176 hlay2->addWidget(logHistoButton); 0177 0178 hlay->setContentsMargins(QMargins()); 0179 hlay->setSpacing(spacing); 0180 hlay->addWidget(label1); 0181 hlay->addWidget(d->timeUnitCB); 0182 hlay->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum)); 0183 hlay->addWidget(scaleBox); 0184 0185 // --------------------------------------------------------------- 0186 0187 d->timeLineWidget = new TimeLineWidget(panel); 0188 d->scrollBar = new QScrollBar(panel); 0189 d->scrollBar->setOrientation(Qt::Horizontal); 0190 d->scrollBar->setMinimum(0); 0191 d->scrollBar->setSingleStep(1); 0192 0193 d->cursorDateLabel = new DAdjustableLabel(panel); 0194 d->cursorCountLabel = new QLabel(panel); 0195 d->cursorCountLabel->setAlignment(Qt::AlignRight); 0196 0197 // --------------------------------------------------------------- 0198 0199 DHBox* const hbox2 = new DHBox(panel); 0200 hbox2->setContentsMargins(QMargins()); 0201 hbox2->setSpacing(spacing); 0202 0203 d->resetButton = new QToolButton(hbox2); 0204 d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); 0205 d->resetButton->setToolTip(i18n("Clear current selection")); 0206 d->resetButton->setWhatsThis(i18n("If you press this button, the current date selection on the time-line will be cleared.")); 0207 d->nameEdit = new DTextEdit(hbox2); 0208 d->nameEdit->setLinesVisible(1); 0209 d->nameEdit->setWhatsThis(i18n("Enter the name of the current dates search to save in the " 0210 "\"Searches\" view")); 0211 0212 d->saveButton = new QToolButton(hbox2); 0213 d->saveButton->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); 0214 d->saveButton->setEnabled(false); 0215 d->saveButton->setToolTip(i18n("Save current selection to a new virtual Album")); 0216 d->saveButton->setWhatsThis(i18n("If you press this button, the dates selected on the time-line will be " 0217 "saved to a new search virtual Album using the name set on the left.")); 0218 0219 // --------------------------------------------------------------- 0220 0221 grid->addWidget(hbox1, 0, 0, 1, 4); 0222 grid->addWidget(d->cursorDateLabel, 1, 0, 1, 3); 0223 grid->addWidget(d->cursorCountLabel, 1, 3, 1, 1); 0224 grid->addWidget(d->timeLineWidget, 2, 0, 1, 4); 0225 grid->addWidget(d->scrollBar, 3, 0, 1, 4); 0226 grid->addWidget(hbox2, 4, 0, 1, 4); 0227 grid->setColumnStretch(2, 10); 0228 grid->setContentsMargins(spacing, spacing, spacing, spacing); 0229 grid->setSpacing(spacing); 0230 0231 // --------------------------------------------------------------- 0232 0233 d->timeLineFolderView = new EditableSearchTreeView(this, searchModel, searchModificationHelper); 0234 d->timeLineFolderView->setConfigGroup(getConfigGroup()); 0235 d->timeLineFolderView->filteredModel()->listTimelineSearches(); 0236 d->timeLineFolderView->filteredModel()->setListTemporarySearches(false); 0237 d->timeLineFolderView->setAlbumManagerCurrentAlbum(false); 0238 d->searchDateBar = new SearchTextBarDb(this, QLatin1String("TimeLineViewSearchDateBar")); 0239 d->searchDateBar->setModel(d->timeLineFolderView->filteredModel(), 0240 AbstractAlbumModel::AlbumIdRole, 0241 AbstractAlbumModel::AlbumTitleRole); 0242 d->searchDateBar->setFilterModel(d->timeLineFolderView->albumFilterModel()); 0243 0244 vlay->addWidget(panel); 0245 vlay->addWidget(d->timeLineFolderView); 0246 vlay->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Minimum, QSizePolicy::Minimum)); 0247 vlay->addWidget(d->searchDateBar); 0248 vlay->setContentsMargins(0, 0, spacing, 0); 0249 vlay->setSpacing(0); 0250 0251 // --------------------------------------------------------------- 0252 0253 connect(AlbumManager::instance(), SIGNAL(signalDatesHashDirty(QHash<QDateTime,int>)), 0254 d->timeLineWidget, SLOT(slotDatesHash(QHash<QDateTime,int>))); 0255 0256 connect(d->timeLineFolderView, SIGNAL(currentAlbumChanged(Album*)), 0257 this, SLOT(slotAlbumSelected(Album*))); 0258 0259 connect(d->timeUnitCB, SIGNAL(activated(int)), 0260 this, SLOT(slotTimeUnitChanged(int))); 0261 0262 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) 0263 0264 connect(d->scaleBG, SIGNAL(idReleased(int)), 0265 this, SLOT(slotScaleChanged(int))); 0266 0267 #else 0268 0269 connect(d->scaleBG, SIGNAL(buttonReleased(int)), 0270 this, SLOT(slotScaleChanged(int))); 0271 0272 #endif 0273 0274 connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), 0275 this, SLOT(slotInit())); 0276 0277 connect(d->timeLineWidget, SIGNAL(signalCursorPositionChanged()), 0278 this, SLOT(slotCursorPositionChanged())); 0279 0280 connect(d->timeLineWidget, SIGNAL(signalSelectionChanged()), 0281 this, SLOT(slotSelectionChanged())); 0282 0283 connect(d->timeLineWidget, SIGNAL(signalRefDateTimeChanged()), 0284 this, SLOT(slotRefDateTimeChanged())); 0285 0286 connect(d->timer, SIGNAL(timeout()), 0287 this, SLOT(slotUpdateCurrentDateSearchAlbum())); 0288 0289 connect(d->resetButton, SIGNAL(clicked()), 0290 this, SLOT(slotResetSelection())); 0291 0292 connect(d->saveButton, SIGNAL(clicked()), 0293 this, SLOT(slotSaveSelection())); 0294 0295 connect(d->scrollBar, SIGNAL(valueChanged(int)), 0296 this, SLOT(slotScrollBarValueChanged(int))); 0297 0298 connect(d->nameEdit, SIGNAL(textChanged()), 0299 this, SLOT(slotCheckAboutSelection())); 0300 0301 connect(d->nameEdit, SIGNAL(returnPressed()), 0302 d->saveButton, SLOT(animateClick())); 0303 } 0304 0305 TimelineSideBarWidget::~TimelineSideBarWidget() 0306 { 0307 delete d; 0308 } 0309 0310 void TimelineSideBarWidget::slotInit() 0311 { 0312 // Date Maps are loaded from AlbumManager to TimeLineWidget after than GUI is initialized. 0313 // AlbumManager query Date KIO slave to stats items from database and it can take a while. 0314 // We waiting than TimeLineWidget is ready before to set last config from users. 0315 0316 loadState(); 0317 0318 disconnect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), 0319 this, SLOT(slotInit())); 0320 0321 connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), 0322 this, SLOT(slotCursorPositionChanged())); 0323 } 0324 0325 void TimelineSideBarWidget::setActive(bool active) 0326 { 0327 if (active) 0328 { 0329 if (!d->currentTimelineSearch) 0330 { 0331 d->currentTimelineSearch = d->timeLineFolderView->currentAlbum(); 0332 } 0333 0334 if (d->currentTimelineSearch) 0335 { 0336 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << d->currentTimelineSearch); 0337 } 0338 else 0339 { 0340 slotUpdateCurrentDateSearchAlbum(); 0341 } 0342 } 0343 } 0344 0345 void TimelineSideBarWidget::doLoadState() 0346 { 0347 0348 KConfigGroup group = getConfigGroup(); 0349 0350 d->timeUnitCB->setCurrentIndex(group.readEntry(d->configHistogramTimeUnitEntry, (int)TimeLineWidget::Month)); 0351 slotTimeUnitChanged(d->timeUnitCB->currentIndex()); 0352 0353 int id = group.readEntry(d->configHistogramScaleEntry, (int)TimeLineWidget::LinScale); 0354 0355 if (d->scaleBG->button(id)) 0356 { 0357 d->scaleBG->button(id)->setChecked(true); 0358 } 0359 0360 slotScaleChanged(d->scaleBG->checkedId()); 0361 0362 QDateTime now = QDateTime::currentDateTime(); 0363 d->timeLineWidget->setCursorDateTime(group.readEntry(d->configCursorPositionEntry, now)); 0364 d->timeLineWidget->setCurrentIndex(d->timeLineWidget->indexForCursorDateTime()); 0365 0366 d->timeLineFolderView->loadState(); 0367 } 0368 0369 void TimelineSideBarWidget::doSaveState() 0370 { 0371 KConfigGroup group = getConfigGroup(); 0372 0373 group.writeEntry(d->configHistogramTimeUnitEntry, d->timeUnitCB->currentIndex()); 0374 group.writeEntry(d->configHistogramScaleEntry, d->scaleBG->checkedId()); 0375 group.writeEntry(d->configCursorPositionEntry, d->timeLineWidget->cursorDateTime()); 0376 0377 d->timeLineFolderView->saveState(); 0378 0379 group.sync(); 0380 } 0381 0382 void TimelineSideBarWidget::applySettings() 0383 { 0384 // nothing to do here right now 0385 } 0386 0387 void TimelineSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album) 0388 { 0389 d->timeLineFolderView->setCurrentAlbums(album); 0390 } 0391 0392 const QIcon TimelineSideBarWidget::getIcon() 0393 { 0394 return QIcon::fromTheme(QLatin1String("player-time")); 0395 } 0396 0397 const QString TimelineSideBarWidget::getCaption() 0398 { 0399 return i18n("Timeline"); 0400 } 0401 0402 void TimelineSideBarWidget::slotRefDateTimeChanged() 0403 { 0404 d->scrollBar->blockSignals(true); 0405 d->scrollBar->setMaximum(d->timeLineWidget->totalIndex()-1); 0406 d->scrollBar->setValue(d->timeLineWidget->indexForRefDateTime()-1); 0407 d->scrollBar->blockSignals(false); 0408 } 0409 0410 void TimelineSideBarWidget::slotTimeUnitChanged(int mode) 0411 { 0412 d->timeLineWidget->setTimeUnit((TimeLineWidget::TimeUnit)mode); 0413 } 0414 0415 void TimelineSideBarWidget::slotScrollBarValueChanged(int val) 0416 { 0417 d->timeLineWidget->setCurrentIndex(val); 0418 } 0419 0420 void TimelineSideBarWidget::slotScaleChanged(int mode) 0421 { 0422 d->timeLineWidget->setScaleMode((TimeLineWidget::ScaleMode)mode); 0423 } 0424 0425 void TimelineSideBarWidget::slotCursorPositionChanged() 0426 { 0427 QString txt; 0428 int val = d->timeLineWidget->cursorInfo(txt); 0429 d->cursorDateLabel->setAdjustedText(txt); 0430 d->cursorCountLabel->setText((val == 0) ? i18n("no item") 0431 : i18np("1 item", "%1 items", val)); 0432 } 0433 0434 void TimelineSideBarWidget::slotSelectionChanged() 0435 { 0436 d->timer->setSingleShot(true); 0437 d->timer->start(500); 0438 } 0439 0440 /** 0441 * Called from d->timer event. 0442 */ 0443 void TimelineSideBarWidget::slotUpdateCurrentDateSearchAlbum() 0444 { 0445 slotCheckAboutSelection(); 0446 int totalCount = 0; 0447 DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); 0448 d->currentTimelineSearch = d->searchModificationHelper-> 0449 slotCreateTimeLineSearch(SAlbum::getTemporaryTitle(DatabaseSearch::TimeLineSearch), dateRanges, true); 0450 0451 // NOTE: "temporary" search is not listed in view 0452 d->timeLineFolderView->setCurrentAlbum(0); 0453 } 0454 0455 void TimelineSideBarWidget::slotSaveSelection() 0456 { 0457 QString name = d->nameEdit->text(); 0458 int totalCount = 0; 0459 DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); 0460 d->currentTimelineSearch = d->searchModificationHelper->slotCreateTimeLineSearch(name, dateRanges); 0461 } 0462 0463 void TimelineSideBarWidget::slotAlbumSelected(Album* album) 0464 { 0465 if (d->currentTimelineSearch == album) 0466 { 0467 return; 0468 } 0469 0470 SAlbum* const salbum = dynamic_cast<SAlbum*>(album); 0471 0472 if (!salbum) 0473 { 0474 return; 0475 } 0476 0477 d->currentTimelineSearch = salbum; 0478 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << salbum); 0479 0480 SearchXmlReader reader(salbum->query()); 0481 0482 // The timeline query consists of groups, with two date time fields each 0483 DateRangeList list; 0484 0485 while (!reader.atEnd()) 0486 { 0487 // read groups 0488 if (reader.readNext() == SearchXml::Group) 0489 { 0490 QDateTime start, end; 0491 int numberOfFields = 0; 0492 0493 while (!reader.atEnd()) 0494 { 0495 // read fields 0496 reader.readNext(); 0497 0498 if (reader.isEndElement()) 0499 { 0500 break; 0501 } 0502 0503 if (reader.isFieldElement()) 0504 { 0505 if (numberOfFields == 0) 0506 { 0507 start = reader.valueToDateTime(); 0508 } 0509 else if (numberOfFields == 1) 0510 { 0511 end = reader.valueToDateTime(); 0512 } 0513 0514 ++numberOfFields; 0515 } 0516 } 0517 0518 if (numberOfFields) 0519 { 0520 list << DateRange(start, end); 0521 } 0522 } 0523 } 0524 0525 d->timeLineWidget->setSelectedDateRange(list); 0526 } 0527 0528 void TimelineSideBarWidget::slotResetSelection() 0529 { 0530 d->timeLineWidget->slotResetSelection(); 0531 slotCheckAboutSelection(); 0532 AlbumManager::instance()->clearCurrentAlbums(); 0533 } 0534 0535 void TimelineSideBarWidget::slotCheckAboutSelection() 0536 { 0537 int totalCount = 0; 0538 DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount); 0539 0540 if (!list.isEmpty()) 0541 { 0542 d->nameEdit->setEnabled(true); 0543 0544 if (!d->nameEdit->text().isEmpty()) 0545 { 0546 d->saveButton->setEnabled(true); 0547 } 0548 } 0549 else 0550 { 0551 d->nameEdit->setEnabled(false); 0552 d->saveButton->setEnabled(false); 0553 } 0554 } 0555 0556 } // namespace Digikam 0557 0558 #include "moc_timelinesidebarwidget.cpp"