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"